diff --git a/src/main/java/hlf/java/rest/client/controller/FabricOperationsController.java b/src/main/java/hlf/java/rest/client/controller/FabricOperationsController.java index 22a9ed5e..dd032f07 100644 --- a/src/main/java/hlf/java/rest/client/controller/FabricOperationsController.java +++ b/src/main/java/hlf/java/rest/client/controller/FabricOperationsController.java @@ -1,5 +1,6 @@ package hlf.java.rest.client.controller; +import hlf.java.rest.client.model.AnchorPeerParamsDTO; import hlf.java.rest.client.model.ClientResponseModel; import hlf.java.rest.client.model.CommitChannelParamsDTO; import hlf.java.rest.client.model.NewOrgParamsDTO; @@ -105,7 +106,6 @@ public ResponseEntity addOrgToChannel( @RequestBody @Validated NewOrgParamsDTO organizationDetails) { return networkStatus.addOrgToChannel(channelName, organizationDetails); } - /** * Use to decode an base64 encoded json file, with options to also decode the interior elements * and/or print the output in a cleaner format @@ -126,4 +126,21 @@ public ResponseEntity getDeserializedJson( @RequestParam(name = "prettyPrint", required = true) boolean prettyPrint) { return serializationUtil.decodeContents(encodedJson, decodeInterior, prettyPrint); } + + /** + * Add anchor peer(s) of an organization to a channel. Anchor peer addition should be done once + * the peer nodes of the organization have joined the channel. + * + * @param channelName - the name of the channel for which you wish to add the anchor peer nodes + * to. + * @param anchorPeerParamsDTO - contains the details for the organization peers you wish to be + * added to the channel as anchor peer. + * @return ResponseEntity - contains the result of the operation. + */ + @PostMapping(value = "/channel/{channelName}/add_anchor_peer") + public ResponseEntity addAnchorPeersToChannel( + @PathVariable @Validated String channelName, + @RequestBody @Validated AnchorPeerParamsDTO anchorPeerParamsDTO) { + return networkStatus.addAnchorPeersToChannel(channelName, anchorPeerParamsDTO); + } } diff --git a/src/main/java/hlf/java/rest/client/model/AnchorPeerParamsDTO.java b/src/main/java/hlf/java/rest/client/model/AnchorPeerParamsDTO.java new file mode 100644 index 00000000..9aa45188 --- /dev/null +++ b/src/main/java/hlf/java/rest/client/model/AnchorPeerParamsDTO.java @@ -0,0 +1,10 @@ +package hlf.java.rest.client.model; + +import java.util.List; +import lombok.Data; + +@Data +public class AnchorPeerParamsDTO { + private String organizationMspId; + private List anchorPeerDTOs; +} diff --git a/src/main/java/hlf/java/rest/client/service/AddAnchorPeerToChannelWriteSetBuilder.java b/src/main/java/hlf/java/rest/client/service/AddAnchorPeerToChannelWriteSetBuilder.java new file mode 100644 index 00000000..668135b8 --- /dev/null +++ b/src/main/java/hlf/java/rest/client/service/AddAnchorPeerToChannelWriteSetBuilder.java @@ -0,0 +1,11 @@ +package hlf.java.rest.client.service; + +import hlf.java.rest.client.exception.ServiceException; +import hlf.java.rest.client.model.AnchorPeerParamsDTO; +import org.hyperledger.fabric.protos.common.Configtx.ConfigGroup; + +public interface AddAnchorPeerToChannelWriteSetBuilder { + + ConfigGroup buildWriteSetForAnchorPeers( + ConfigGroup readset, AnchorPeerParamsDTO anchorPeerParamsDTO) throws ServiceException; +} diff --git a/src/main/java/hlf/java/rest/client/service/NetworkStatus.java b/src/main/java/hlf/java/rest/client/service/NetworkStatus.java index e09c6a0d..ce9c5c5b 100644 --- a/src/main/java/hlf/java/rest/client/service/NetworkStatus.java +++ b/src/main/java/hlf/java/rest/client/service/NetworkStatus.java @@ -1,5 +1,6 @@ package hlf.java.rest.client.service; +import hlf.java.rest.client.model.AnchorPeerParamsDTO; import hlf.java.rest.client.model.ClientResponseModel; import hlf.java.rest.client.model.CommitChannelParamsDTO; import hlf.java.rest.client.model.NewOrgParamsDTO; @@ -20,4 +21,7 @@ ResponseEntity commitChannelConfigTransaction( ResponseEntity addOrgToChannel( String channelName, NewOrgParamsDTO organizationDetails); + + ResponseEntity addAnchorPeersToChannel( + String channelName, AnchorPeerParamsDTO anchorPeerParamsDTO); } diff --git a/src/main/java/hlf/java/rest/client/service/impl/AddAnchorPeerToChannelWriteSetBuilderImpl.java b/src/main/java/hlf/java/rest/client/service/impl/AddAnchorPeerToChannelWriteSetBuilderImpl.java new file mode 100644 index 00000000..3e5fafbb --- /dev/null +++ b/src/main/java/hlf/java/rest/client/service/impl/AddAnchorPeerToChannelWriteSetBuilderImpl.java @@ -0,0 +1,91 @@ +package hlf.java.rest.client.service.impl; + +import hlf.java.rest.client.exception.ServiceException; +import hlf.java.rest.client.model.AnchorPeerDTO; +import hlf.java.rest.client.model.AnchorPeerParamsDTO; +import hlf.java.rest.client.service.AddAnchorPeerToChannelWriteSetBuilder; +import hlf.java.rest.client.util.FabricChannelUtil; +import hlf.java.rest.client.util.FabricClientConstants; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.hyperledger.fabric.protos.common.Configtx.ConfigGroup; +import org.hyperledger.fabric.protos.common.Configtx.ConfigValue; +import org.hyperledger.fabric.protos.peer.Configuration.AnchorPeer; +import org.hyperledger.fabric.protos.peer.Configuration.AnchorPeers; +import org.springframework.stereotype.Service; + +@Service +public class AddAnchorPeerToChannelWriteSetBuilderImpl + implements AddAnchorPeerToChannelWriteSetBuilder { + + private AnchorPeerParamsDTO anchorPeerParamsDTO; + + @Override + public ConfigGroup buildWriteSetForAnchorPeers( + ConfigGroup readset, AnchorPeerParamsDTO anchorPeerParamsDTO) throws ServiceException { + this.anchorPeerParamsDTO = anchorPeerParamsDTO; + String orgMspId = anchorPeerParamsDTO.getOrganizationMspId(); + Map existingOrganizations = + FabricChannelUtil.getExistingOrgsFromReadset(readset); + // The "Application" group + ConfigGroup applicationGroup = + ConfigGroup.newBuilder() + .setModPolicy(FabricClientConstants.CHANNEL_CONFIG_MOD_POLICY_ADMINS) + .putAllPolicies(FabricChannelUtil.setApplicationPolicies(readset)) + .putGroups(orgMspId, setAnchorPeerInGroup(orgMspId, readset)) + // putAllGroups excludes new organization + .putAllGroups(existingOrganizations) + // Application group version + .setVersion( + FabricChannelUtil.retrieveMSPGroupVersionFromReadset( + readset, FabricClientConstants.CHANNEL_CONFIG_GROUP_APPLICATION) + + 1) // will be tied to current version + 1 for this level + .build(); + // the "/Channel" group + return ConfigGroup.newBuilder() + .putGroups(FabricClientConstants.CHANNEL_CONFIG_GROUP_APPLICATION, applicationGroup) + .setModPolicy(FabricClientConstants.CHANNEL_CONFIG_MOD_POLICY_ADMINS) + // Channel group version + .setVersion(readset.getVersion()) + .build(); + } + + private ConfigGroup setAnchorPeerInGroup(String orgMspId, ConfigGroup readSet) { + Map valueMap = new HashMap<>(); + if (anchorPeerParamsDTO.getAnchorPeerDTOs() != null) { + valueMap.put( + FabricClientConstants.CHANNEL_CONFIG_GROUP_VALUE_ANCHORPEERS, setNewOrgAnchorPeerValue()); + } + return ConfigGroup.newBuilder() + .setModPolicy(FabricClientConstants.CHANNEL_CONFIG_MOD_POLICY_ADMINS) + .putAllPolicies(FabricChannelUtil.getDefaultRolePolicy(orgMspId)) + .putAllValues(valueMap) + .setVersion( + FabricChannelUtil.retrieveMSPGroupVersionFromReadset( + readSet, FabricClientConstants.CHANNEL_CONFIG_GROUP_APPLICATION) + + 1) + .build(); + } + + private ConfigValue setNewOrgAnchorPeerValue() { + return ConfigValue.newBuilder() + .setModPolicy(FabricClientConstants.CHANNEL_CONFIG_MOD_POLICY_ADMINS) + .setValue(setAnchorPeers().toByteString()) + .setVersion(0) + .build(); + } + + private AnchorPeers setAnchorPeers() { + List anchorPeerList = new ArrayList<>(); + for (AnchorPeerDTO anchorPeerDTO : anchorPeerParamsDTO.getAnchorPeerDTOs()) { + anchorPeerList.add( + AnchorPeer.newBuilder() + .setHost(anchorPeerDTO.getHostname()) + .setPort(anchorPeerDTO.getPort()) + .build()); + } + return AnchorPeers.newBuilder().addAllAnchorPeers(anchorPeerList).build(); + } +} diff --git a/src/main/java/hlf/java/rest/client/service/impl/AddOrgToChannelWriteSetBuilderImpl.java b/src/main/java/hlf/java/rest/client/service/impl/AddOrgToChannelWriteSetBuilderImpl.java index fce578c9..5ab80518 100644 --- a/src/main/java/hlf/java/rest/client/service/impl/AddOrgToChannelWriteSetBuilderImpl.java +++ b/src/main/java/hlf/java/rest/client/service/impl/AddOrgToChannelWriteSetBuilderImpl.java @@ -1,7 +1,6 @@ package hlf.java.rest.client.service.impl; import com.google.protobuf.ByteString; -import hlf.java.rest.client.exception.ErrorCode; import hlf.java.rest.client.exception.ServiceException; import hlf.java.rest.client.model.AnchorPeerDTO; import hlf.java.rest.client.model.NewOrgParamsDTO; @@ -12,9 +11,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import org.hyperledger.fabric.protos.common.Configtx.ConfigGroup; -import org.hyperledger.fabric.protos.common.Configtx.ConfigPolicy; import org.hyperledger.fabric.protos.common.Configtx.ConfigValue; import org.hyperledger.fabric.protos.msp.MspConfigPackage.FabricCryptoConfig; import org.hyperledger.fabric.protos.msp.MspConfigPackage.FabricMSPConfig; @@ -29,48 +26,28 @@ public class AddOrgToChannelWriteSetBuilderImpl implements AddOrgToChannelWriteSetBuilder { private NewOrgParamsDTO organizationDetails; - private static final int DEFAULT_VERSION = 0; @Override public ConfigGroup buildWriteset(ConfigGroup readset, NewOrgParamsDTO organizationDetails) throws ServiceException { this.organizationDetails = organizationDetails; String newOrgMspId = organizationDetails.getOrganizationMspId(); - // Get existing organizations in the channel and set with as objects and their - // version to prevent deletion or modification - // Omitting existing groups results in their deletion. - Map existingOrganizations = new HashMap<>(); - ConfigGroup applicationConfigGroup = - readset.getGroupsOrThrow(FabricClientConstants.CHANNEL_CONFIG_GROUP_APPLICATION); - applicationConfigGroup - .getGroupsMap() - .forEach( - (k, v) -> - existingOrganizations.put( - k, - setEmptyGroup(retrieveMSPGroupVersionFromReadset(applicationConfigGroup, k)))); + Map existingOrganizations = + FabricChannelUtil.getExistingOrgsFromReadset(readset); + // The "Application" group ConfigGroup applicationGroup = ConfigGroup.newBuilder() .setModPolicy(FabricClientConstants.CHANNEL_CONFIG_MOD_POLICY_ADMINS) - .putAllPolicies(setApplicationPolicies(readset)) + .putAllPolicies(FabricChannelUtil.setApplicationPolicies(readset)) .putGroups(newOrgMspId, setNewOrgGroup(newOrgMspId)) // putAllGroups excludes new organization .putAllGroups(existingOrganizations) // Application group version .setVersion( - retrieveMSPGroupVersionFromReadset( + FabricChannelUtil.retrieveMSPGroupVersionFromReadset( readset, FabricClientConstants.CHANNEL_CONFIG_GROUP_APPLICATION) - + 1) // will - // be - // tied - // to - // current - // version - // + 1 - // for - // this - // level + + 1) // will be tied to current version + 1 for this level .build(); // the "/Channel" group return ConfigGroup.newBuilder() @@ -81,89 +58,6 @@ public ConfigGroup buildWriteset(ConfigGroup readset, NewOrgParamsDTO organizati .build(); } - private long retrieveMSPGroupVersionFromReadset(ConfigGroup readset, String mspId) - throws ServiceException { - long versionLong = DEFAULT_VERSION; - try { - ConfigGroup group = readset.getGroupsOrThrow(mspId); - versionLong = group.getVersion(); - } catch (IllegalArgumentException e) { - throw new ServiceException( - ErrorCode.NOT_FOUND, - "WriteBuilder version iteration error: ConfigGroup with name - \"" - + mspId - + "\" - not found in Readset", - e); - } - return versionLong; - } - - private Map retrievePolicyVersionFromReadset(ConfigGroup readset, String groupName) - throws ServiceException { - Map map = new HashMap<>(); - try { - ConfigGroup group = readset.getGroupsOrThrow(groupName); - for (Entry entry : group.getPoliciesMap().entrySet()) { - map.put(entry.getKey(), entry.getValue().getVersion()); - } - } catch (IllegalArgumentException e) { - throw new ServiceException( - ErrorCode.NOT_FOUND, - "WriteBuilder version iteration error: ConfigGroup with name - \"" - + groupName - + "\" - not found in Readset", - e); - } - return map; - } - - private Map setApplicationPolicies(ConfigGroup readset) { - Map map = - retrievePolicyVersionFromReadset( - readset, FabricClientConstants.CHANNEL_CONFIG_GROUP_APPLICATION); - ConfigPolicy adminPolicy = - ConfigPolicy.newBuilder() - .setModPolicy("") - .setVersion(map.get(FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_ADMINS)) - .build(); - ConfigPolicy endorsementPolicy = - ConfigPolicy.newBuilder() - .setModPolicy("") - .setVersion(map.get(FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_ENDORSEMENT)) - .build(); - ConfigPolicy lifeCycleEndorsementPolicy = - ConfigPolicy.newBuilder() - .setModPolicy("") - .setVersion( - map.get(FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_LIFECYCLE_ENDORSEMENT)) - .build(); - ConfigPolicy readerPolicy = - ConfigPolicy.newBuilder() - .setModPolicy("") - .setVersion(map.get(FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_READERS)) - .build(); - ConfigPolicy writerPolicy = - ConfigPolicy.newBuilder() - .setModPolicy("") - .setVersion(map.get(FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_WRITERS)) - .build(); - Map applicationPoliciesMap = new HashMap<>(); - // add Admins, Readers, Writers, Endorsement and LifeCycle Endorsement policies at the channel - // level - applicationPoliciesMap.put( - FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_ADMINS, adminPolicy); - applicationPoliciesMap.put( - FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_ENDORSEMENT, endorsementPolicy); - applicationPoliciesMap.put( - FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_LIFECYCLE_ENDORSEMENT, - lifeCycleEndorsementPolicy); - applicationPoliciesMap.put( - FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_READERS, readerPolicy); - applicationPoliciesMap.put( - FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_WRITERS, writerPolicy); - return applicationPoliciesMap; - } - private ConfigGroup setNewOrgGroup(String newOrgMspId) { Map valueMap = new HashMap<>(); valueMap.put( @@ -181,10 +75,6 @@ private ConfigGroup setNewOrgGroup(String newOrgMspId) { .build(); } - private ConfigGroup setEmptyGroup(long version) { - return ConfigGroup.newBuilder().setModPolicy("").setVersion(version).build(); - } - private ConfigValue setNewOrgMspValue(String newOrgMspId) { return ConfigValue.newBuilder() .setModPolicy(FabricClientConstants.CHANNEL_CONFIG_MOD_POLICY_ADMINS) diff --git a/src/main/java/hlf/java/rest/client/service/impl/ChannelServiceImpl.java b/src/main/java/hlf/java/rest/client/service/impl/ChannelServiceImpl.java index 72cdc419..738c4a55 100644 --- a/src/main/java/hlf/java/rest/client/service/impl/ChannelServiceImpl.java +++ b/src/main/java/hlf/java/rest/client/service/impl/ChannelServiceImpl.java @@ -249,7 +249,9 @@ private Common.Envelope getChannelCreationTransaction( */ private Configtx.ConfigUpdate newConfigUpdate(ChannelOperationRequest channelOperationRequest) { Map mspMap = new HashMap<>(); - channelOperationRequest.getPeers().forEach(p -> mspMap.putIfAbsent(p.getMspid(), p.getMspDTO())); + channelOperationRequest + .getPeers() + .forEach(p -> mspMap.putIfAbsent(p.getMspid(), p.getMspDTO())); return Configtx.ConfigUpdate.newBuilder() .setChannelId(channelOperationRequest.getChannelName()) .setReadSet(newChannelGroup(channelOperationRequest.getConsortiumName(), mspMap, false)) diff --git a/src/main/java/hlf/java/rest/client/service/impl/NetworkStatusImpl.java b/src/main/java/hlf/java/rest/client/service/impl/NetworkStatusImpl.java index a276877e..d1106f79 100644 --- a/src/main/java/hlf/java/rest/client/service/impl/NetworkStatusImpl.java +++ b/src/main/java/hlf/java/rest/client/service/impl/NetworkStatusImpl.java @@ -8,9 +8,11 @@ import hlf.java.rest.client.exception.ErrorConstants; import hlf.java.rest.client.exception.FabricTransactionException; import hlf.java.rest.client.exception.ServiceException; +import hlf.java.rest.client.model.AnchorPeerParamsDTO; import hlf.java.rest.client.model.ClientResponseModel; import hlf.java.rest.client.model.CommitChannelParamsDTO; import hlf.java.rest.client.model.NewOrgParamsDTO; +import hlf.java.rest.client.service.AddAnchorPeerToChannelWriteSetBuilder; import hlf.java.rest.client.service.AddOrgToChannelWriteSetBuilder; import hlf.java.rest.client.service.ChannelConfigDeserialization; import hlf.java.rest.client.service.NetworkStatus; @@ -39,8 +41,21 @@ public class NetworkStatusImpl implements NetworkStatus { @Autowired private Gateway gateway; @Autowired private User user; @Autowired private AddOrgToChannelWriteSetBuilder addOrgToChannelWriteSetBuilder; + + @Autowired private AddAnchorPeerToChannelWriteSetBuilder addAnchorPeerToChannelWriteSetBuilder; @Autowired private ChannelConfigDeserialization channelConfigDeserialization; + private String getDeserializedConfig(MessageOrBuilder message) { + String channelConfigString; + try { + channelConfigString = JsonFormat.printer().print(message); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + log.info(channelConfigString); + return Base64.getEncoder().encodeToString(channelConfigString.getBytes()); + } + @Override /** * Obtains the channel configuration @@ -49,17 +64,13 @@ public class NetworkStatusImpl implements NetworkStatus { * @return ResponseEntity - Contains the channel configuration */ public ResponseEntity getChannelFromNetwork(String channelName) { - Network network = null; + Network network; try { network = gateway.getNetwork(channelName); if (network != null) { Channel selectedChannel = network.getChannel(); - Config channelConfig = Config.parseFrom(selectedChannel.getChannelConfigurationBytes()); - MessageOrBuilder message = channelConfig; - String channelConfigString = JsonFormat.printer().print(message); - log.info(channelConfigString); - String base64EncodedByteArraySerialized = - Base64.getEncoder().encodeToString(channelConfigString.getBytes()); + MessageOrBuilder message = Config.parseFrom(selectedChannel.getChannelConfigurationBytes()); + String base64EncodedByteArraySerialized = getDeserializedConfig(message); return new ResponseEntity<>( new ClientResponseModel(ErrorConstants.NO_ERROR, base64EncodedByteArraySerialized), HttpStatus.OK); @@ -95,62 +106,85 @@ public ResponseEntity generateConfigUpdate( String channelName, NewOrgParamsDTO organizationDetails) { Network network = gateway.getNetwork(channelName); + if (network != null) { + ConfigUpdate.Builder configUpdateBuilder = createConfigUpdate(channelName); + ConfigUpdate configUpdate = + configUpdateBuilder + .setWriteSet( + addOrgToChannelWriteSetBuilder.buildWriteset( + configUpdateBuilder.getReadSet(), organizationDetails)) + .build(); + MessageOrBuilder message = configUpdate; + String base64EncodedByteArrayDeserialized = getDeserializedConfig(configUpdate); + return new ResponseEntity<>( + new ClientResponseModel(ErrorConstants.NO_ERROR, base64EncodedByteArrayDeserialized), + HttpStatus.OK); + } else { + log.warn( + "Error generating the Config Update: Network and User cannot be NULL: " + + "Network = " + + network); + return new ResponseEntity<>( + new ClientResponseModel( + ErrorCode.NOT_FOUND.getValue(), + "Network and User cannot be NULL: " + "Network = " + network), + HttpStatus.OK); + } + } + + private ConfigUpdate.Builder createConfigUpdate(String channelName) { + Network network = gateway.getNetwork(channelName); if (network != null) { try { Channel selectedChannel = network.getChannel(); - ConfigUpdate selectedChannelConfigUpdate = - ConfigUpdate.parseFrom(selectedChannel.getChannelConfigurationBytes()); - // ReadSet must have version, all other fields ignored; WriteSet must have changes - ConfigGroup readset = selectedChannelConfigUpdate.getReadSet(); - // ConfigGroups consist of: groups, modPolicy, policies, values, and version. - ConfigUpdate configUpdate = - ConfigUpdate.newBuilder() - .setChannelId(channelName) - .setReadSet(readset) - .setWriteSet( - addOrgToChannelWriteSetBuilder.buildWriteset(readset, organizationDetails)) - .build(); - MessageOrBuilder message = configUpdate; - String channelConfigString = JsonFormat.printer().print(message); - log.info(channelConfigString); - String base64EncodedByteArrayDeserialized = - Base64.getEncoder().encodeToString(channelConfigString.getBytes()); - return new ResponseEntity<>( - new ClientResponseModel(ErrorConstants.NO_ERROR, base64EncodedByteArrayDeserialized), - HttpStatus.OK); + byte[] channelConfigBytes = selectedChannel.getChannelConfigurationBytes(); + if (channelConfigBytes != null) { + ConfigUpdate selectedChannelConfigUpdate = ConfigUpdate.parseFrom(channelConfigBytes); + // ReadSet must have version, all other fields ignored; WriteSet must have changes + // Check if readSet is not null before accessing it + if (selectedChannelConfigUpdate.getReadSet() != null) { + ConfigGroup readSet = selectedChannelConfigUpdate.getReadSet(); + // ConfigGroups consist of: groups, modPolicy, policies, values, and version. + return ConfigUpdate.newBuilder().setChannelId(channelName).setReadSet(readSet); + } else { + log.warn("Error fetching channel config: ReadSet is null"); + // Handle the case where readSet is null appropriately + // You might want to throw an exception or return a default value + return ConfigUpdate.newBuilder(); + } + } else { + log.warn("Error fetching channel config: Channel configuration bytes are null"); + // Handle the case where channelConfigBytes is null appropriately + // You might want to throw an exception or return a default value + return ConfigUpdate.newBuilder(); + } + } catch (InvalidArgumentException e) { log.warn( - "Error while committing channel config: One or more arguments included in the config update are invalid"); + "Error while fetching channel config: Channel has no peer or orderers defined. Can not get configuration block"); throw new ServiceException( ErrorCode.HYPERLEDGER_FABRIC_TRANSACTION_ERROR, - "One or more arguments included in the config update are invalid", + "Channel has no peer or orderers defined. Can not get configuration block", e); } catch (TransactionException e) { - log.warn("Error while committing channel config: " + e.getMessage()); + log.warn("Error while fetching channel config: " + e.getMessage()); throw new FabricTransactionException( ErrorCode.HYPERLEDGER_FABRIC_TRANSACTION_ERROR, ErrorCode.HYPERLEDGER_FABRIC_TRANSACTION_ERROR.name(), e); - } catch (IOException e) { - log.warn("Error while establishing connection to the gateway"); + } catch (InvalidProtocolBufferException e) { + log.warn("Error while parsing channel config"); throw new ServiceException( - ErrorCode.HYPERLEDGER_FABRIC_CONNECTION_ERROR, - "Error while establishing connection to the gateway", + ErrorCode.HYPERLEDGER_FABRIC_TRANSACTION_ERROR, + "Error while parsing channel config", e); } } else { log.warn( - "Error generating the Config Update: Network and User cannot be NULL: " - + "Network = " - + network); - return new ResponseEntity<>( - new ClientResponseModel( - ErrorCode.NOT_FOUND.getValue(), - "Network and User cannot be NULL: " + "Network = " + network), - HttpStatus.OK); + "Error fetching the channel config: Network cannot be NULL: " + "Network = " + network); + return ConfigUpdate.newBuilder(); } } - /** * Signs the channel configuration json using the credentials owned by this application's * organization @@ -293,17 +327,69 @@ public ResponseEntity addOrgToChannel( if (network != null && user != null) { try { Channel selectedChannel = network.getChannel(); - ConfigUpdate selectedChannelConfigUpdate = - ConfigUpdate.parseFrom(selectedChannel.getChannelConfigurationBytes()); - // ReadSet must have version, all other fields ignored; WriteSet must have changes - ConfigGroup readset = selectedChannelConfigUpdate.getReadSet(); - // ConfigGroups consist of: groups, modPolicy, policies, values, and version. + ConfigUpdate.Builder configUpdateBuilder = createConfigUpdate(channelName); + ConfigUpdate configUpdate = + configUpdateBuilder + .setWriteSet( + addOrgToChannelWriteSetBuilder.buildWriteset( + configUpdateBuilder.getReadSet(), organizationDetails)) + .build(); + MessageOrBuilder message = configUpdate; + String channelConfigString = JsonFormat.printer().print(message); + log.info(channelConfigDeserialization.deserializeValueFields(channelConfigString)); + UpdateChannelConfiguration updateChannelConfiguration = new UpdateChannelConfiguration(); + updateChannelConfiguration.setUpdateChannelConfiguration( + configUpdate.toByteString().toByteArray()); + selectedChannel.updateChannelConfiguration( + updateChannelConfiguration, + selectedChannel.getUpdateChannelConfigurationSignature( + updateChannelConfiguration, user)); + } catch (InvalidArgumentException e) { + log.warn( + "Error while committing channel config: One or more arguments included in the config update are invalid"); + throw new ServiceException( + ErrorCode.HYPERLEDGER_FABRIC_TRANSACTION_ERROR, + "One or more arguments included in the config update are invalid", + e); + } catch (TransactionException e) { + log.warn("Error while committing channel config: " + e.getMessage()); + throw new FabricTransactionException( + ErrorCode.HYPERLEDGER_FABRIC_TRANSACTION_ERROR, + ErrorCode.HYPERLEDGER_FABRIC_TRANSACTION_ERROR.name(), + e); + } catch (IOException e) { + log.warn("Error while establishing connection to the gateway"); + throw new ServiceException( + ErrorCode.HYPERLEDGER_FABRIC_CONNECTION_ERROR, + "Error while establishing connection to the gateway", + e); + } + return new ResponseEntity<>( + new ClientResponseModel(ErrorConstants.NO_ERROR, ErrorCode.SUCCESS.getReason()), + HttpStatus.OK); + } else { + log.warn("Network and User cannot be NULL: " + "Network = " + network + "and User = " + user); + return new ResponseEntity<>( + new ClientResponseModel( + ErrorCode.NOT_FOUND.getValue(), + "Network and User cannot be NULL: " + "Network = " + network + "and User = " + user), + HttpStatus.OK); + } + } + + @Override + public ResponseEntity addAnchorPeersToChannel( + String channelName, AnchorPeerParamsDTO anchorPeerParamsDTO) { + Network network = gateway.getNetwork(channelName); + if (network != null && user != null) { + try { + Channel selectedChannel = network.getChannel(); + ConfigUpdate.Builder configUpdateBuilder = createConfigUpdate(channelName); ConfigUpdate configUpdate = - ConfigUpdate.newBuilder() - .setChannelId(channelName) - .setReadSet(readset) + configUpdateBuilder .setWriteSet( - addOrgToChannelWriteSetBuilder.buildWriteset(readset, organizationDetails)) + addAnchorPeerToChannelWriteSetBuilder.buildWriteSetForAnchorPeers( + configUpdateBuilder.getReadSet(), anchorPeerParamsDTO)) .build(); MessageOrBuilder message = configUpdate; String channelConfigString = JsonFormat.printer().print(message); diff --git a/src/main/java/hlf/java/rest/client/util/FabricChannelUtil.java b/src/main/java/hlf/java/rest/client/util/FabricChannelUtil.java index 8d551020..a9aa86e0 100644 --- a/src/main/java/hlf/java/rest/client/util/FabricChannelUtil.java +++ b/src/main/java/hlf/java/rest/client/util/FabricChannelUtil.java @@ -1,8 +1,11 @@ package hlf.java.rest.client.util; +import hlf.java.rest.client.exception.ErrorCode; +import hlf.java.rest.client.exception.ServiceException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import lombok.experimental.UtilityClass; import org.hyperledger.fabric.protos.common.Configtx; import org.hyperledger.fabric.protos.common.MspPrincipal; @@ -10,6 +13,7 @@ @UtilityClass public class FabricChannelUtil { + private final int DEFAULT_VERSION = 0; /** * get default configuration policy for organization that maps the roles. The policy type is @@ -19,7 +23,7 @@ public class FabricChannelUtil { * @param orgMSPId Org MSP ID * @return HashMap with role and the configuration policy */ - public static HashMap getDefaultRolePolicy(String orgMSPId) { + public HashMap getDefaultRolePolicy(String orgMSPId) { HashMap defaultOrgRolePolicy = new HashMap<>(); // add Admins, Readers, Writers and Endorsement policies defaultOrgRolePolicy.put( @@ -48,7 +52,7 @@ public static HashMap getDefaultRolePolicy(String * @param orgMSPId new org MSP ID * @return configuration policy */ - private static Configtx.ConfigPolicy getDefaultRoleConfigPolicyForMSP( + private Configtx.ConfigPolicy getDefaultRoleConfigPolicyForMSP( String policyFor, String orgMSPId) { List mspPrincipals = getRolesFor(policyFor, orgMSPId); // loop through each entry and apply the n out of policy @@ -97,7 +101,7 @@ private static Configtx.ConfigPolicy getDefaultRoleConfigPolicyForMSP( // } // } - private static MspPrincipal.MSPPrincipal createMSPPrincipal( + private MspPrincipal.MSPPrincipal createMSPPrincipal( String orgMSPId, MspPrincipal.MSPRole.MSPRoleType roleType) { MspPrincipal.MSPRole mspRole = MspPrincipal.MSPRole.newBuilder().setMspIdentifier(orgMSPId).setRole(roleType).build(); @@ -107,7 +111,7 @@ private static MspPrincipal.MSPPrincipal createMSPPrincipal( .build(); } - private static List getRolesFor(String policyFor, String orgMSPId) { + private List getRolesFor(String policyFor, String orgMSPId) { List mspPrincipals = new ArrayList<>(); switch (policyFor) { case FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_ADMINS: @@ -130,4 +134,110 @@ private static List getRolesFor(String policyFor, Str } return mspPrincipals; } + + /* Get existing organizations in the channel and set with as objects and their + version to prevent deletion or modification. + Omitting existing groups results in their deletion.*/ + public Map getExistingOrgsFromReadset( + Configtx.ConfigGroup readset) { + Map existingOrganizations = new HashMap<>(); + Configtx.ConfigGroup applicationConfigGroup = + readset.getGroupsOrThrow(FabricClientConstants.CHANNEL_CONFIG_GROUP_APPLICATION); + applicationConfigGroup + .getGroupsMap() + .forEach( + (k, v) -> + existingOrganizations.put( + k, + setEmptyGroup(retrieveMSPGroupVersionFromReadset(applicationConfigGroup, k)))); + + return existingOrganizations; + } + + private Configtx.ConfigGroup setEmptyGroup(long version) { + return Configtx.ConfigGroup.newBuilder().setModPolicy("").setVersion(version).build(); + } + + public long retrieveMSPGroupVersionFromReadset(Configtx.ConfigGroup readset, String mspId) + throws ServiceException { + long versionLong = DEFAULT_VERSION; + try { + Configtx.ConfigGroup group = readset.getGroupsOrThrow(mspId); + versionLong = group.getVersion(); + } catch (IllegalArgumentException e) { + throw new ServiceException( + ErrorCode.NOT_FOUND, + "WriteBuilder version iteration error: ConfigGroup with name - \"" + + mspId + + "\" - not found in Readset", + e); + } + return versionLong; + } + + private Map retrievePolicyVersionFromReadset( + Configtx.ConfigGroup readset, String groupName) throws ServiceException { + Map map = new HashMap<>(); + try { + Configtx.ConfigGroup group = readset.getGroupsOrThrow(groupName); + for (Map.Entry entry : group.getPoliciesMap().entrySet()) { + map.put(entry.getKey(), entry.getValue().getVersion()); + } + } catch (IllegalArgumentException e) { + throw new ServiceException( + ErrorCode.NOT_FOUND, + "WriteBuilder version iteration error: ConfigGroup with name - \"" + + groupName + + "\" - not found in Readset", + e); + } + return map; + } + + public Map setApplicationPolicies(Configtx.ConfigGroup readset) { + Map map = + retrievePolicyVersionFromReadset( + readset, FabricClientConstants.CHANNEL_CONFIG_GROUP_APPLICATION); + Configtx.ConfigPolicy adminPolicy = + Configtx.ConfigPolicy.newBuilder() + .setModPolicy("") + .setVersion(map.get(FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_ADMINS)) + .build(); + Configtx.ConfigPolicy endorsementPolicy = + Configtx.ConfigPolicy.newBuilder() + .setModPolicy("") + .setVersion(map.get(FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_ENDORSEMENT)) + .build(); + Configtx.ConfigPolicy lifeCycleEndorsementPolicy = + Configtx.ConfigPolicy.newBuilder() + .setModPolicy("") + .setVersion( + map.get(FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_LIFECYCLE_ENDORSEMENT)) + .build(); + Configtx.ConfigPolicy readerPolicy = + Configtx.ConfigPolicy.newBuilder() + .setModPolicy("") + .setVersion(map.get(FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_READERS)) + .build(); + Configtx.ConfigPolicy writerPolicy = + Configtx.ConfigPolicy.newBuilder() + .setModPolicy("") + .setVersion(map.get(FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_WRITERS)) + .build(); + Map applicationPoliciesMap = new HashMap<>(); + // add Admins, Readers, Writers, Endorsement and LifeCycle Endorsement policies at the channel + // level + applicationPoliciesMap.put( + FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_ADMINS, adminPolicy); + applicationPoliciesMap.put( + FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_ENDORSEMENT, endorsementPolicy); + applicationPoliciesMap.put( + FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_LIFECYCLE_ENDORSEMENT, + lifeCycleEndorsementPolicy); + applicationPoliciesMap.put( + FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_READERS, readerPolicy); + applicationPoliciesMap.put( + FabricClientConstants.CHANNEL_CONFIG_POLICY_TYPE_WRITERS, writerPolicy); + return applicationPoliciesMap; + } } diff --git a/src/test/java/hlf/java/rest/client/IT/ChannelIT.java b/src/test/java/hlf/java/rest/client/IT/ChannelIT.java index 7119d72c..4492642c 100644 --- a/src/test/java/hlf/java/rest/client/IT/ChannelIT.java +++ b/src/test/java/hlf/java/rest/client/IT/ChannelIT.java @@ -1,5 +1,7 @@ package hlf.java.rest.client.IT; +import hlf.java.rest.client.model.AnchorPeerDTO; +import hlf.java.rest.client.model.AnchorPeerParamsDTO; import hlf.java.rest.client.model.ChannelOperationRequest; import hlf.java.rest.client.model.ClientResponseModel; import hlf.java.rest.client.model.MSPDTO; @@ -328,8 +330,8 @@ public void addOrgToChannelTest() { mspdto.setAdminOUCert(cacert); mspdto.setClientOUCert(cacert); mspdto.setPeerOUCert(cacert); - newOrgParamsDTO.setMspDTO(mspdto); + newOrgParamsDTO.setOrganizationMspId(ORG_2_MSP); } catch (IOException e) { throw new RuntimeException(e); } @@ -337,4 +339,20 @@ public void addOrgToChannelTest() { networkStatus.addOrgToChannel(CHANNEL_NAME, newOrgParamsDTO); Assertions.assertEquals(new Integer(200), responseModel.getStatusCodeValue()); } + + @Test + @Order(7) + public void addAnchorPeersToChannelTest() { + AnchorPeerParamsDTO anchorPeerParamsDTO = new AnchorPeerParamsDTO(); + anchorPeerParamsDTO.setOrganizationMspId(ORG_1_MSP); + List anchorPeerDTOs = new ArrayList<>(); + AnchorPeerDTO anchorPeerDTO = new AnchorPeerDTO(); + anchorPeerDTO.setHostname("peer1"); + anchorPeerDTO.setPort(7051); + anchorPeerDTOs.add(anchorPeerDTO); + anchorPeerParamsDTO.setAnchorPeerDTOs(anchorPeerDTOs); + ResponseEntity responseModel = + networkStatus.addAnchorPeersToChannel(CHANNEL_NAME, anchorPeerParamsDTO); + Assertions.assertEquals(new Integer(200), responseModel.getStatusCodeValue()); + } } diff --git a/src/test/java/hlf/java/rest/client/controller/FabricOperationsControllerTest.java b/src/test/java/hlf/java/rest/client/controller/FabricOperationsControllerTest.java index 6ea9696a..6318b725 100644 --- a/src/test/java/hlf/java/rest/client/controller/FabricOperationsControllerTest.java +++ b/src/test/java/hlf/java/rest/client/controller/FabricOperationsControllerTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import hlf.java.rest.client.model.AnchorPeerParamsDTO; import hlf.java.rest.client.model.CommitChannelParamsDTO; import hlf.java.rest.client.model.NewOrgParamsDTO; import hlf.java.rest.client.service.NetworkStatus; @@ -91,4 +92,17 @@ public void getDeserializedJsonTest() { assertEquals( responseEntity, serializationUtil.decodeContents("channel_update_file", true, true)); } + + @Test + public void addAnchorPeersToChannelTest() { + ResponseEntity responseEntity = new ResponseEntity(HttpStatus.OK); + Mockito.when( + networkStatus.addAnchorPeersToChannel( + Mockito.anyString(), Mockito.any(AnchorPeerParamsDTO.class))) + .thenReturn(responseEntity); + assertEquals( + responseEntity, + fabricOperationsController.addAnchorPeersToChannel( + "some_channel_name", new AnchorPeerParamsDTO())); + } } diff --git a/src/test/java/hlf/java/rest/client/service/impl/NetworkStatusImplTest.java b/src/test/java/hlf/java/rest/client/service/impl/NetworkStatusImplTest.java index 43ec4196..0ca9caf2 100644 --- a/src/test/java/hlf/java/rest/client/service/impl/NetworkStatusImplTest.java +++ b/src/test/java/hlf/java/rest/client/service/impl/NetworkStatusImplTest.java @@ -10,9 +10,11 @@ import com.google.protobuf.util.JsonFormat.Printer; import hlf.java.rest.client.exception.ErrorCode; import hlf.java.rest.client.exception.ErrorConstants; +import hlf.java.rest.client.model.AnchorPeerParamsDTO; import hlf.java.rest.client.model.ClientResponseModel; import hlf.java.rest.client.model.CommitChannelParamsDTO; import hlf.java.rest.client.model.NewOrgParamsDTO; +import hlf.java.rest.client.service.AddAnchorPeerToChannelWriteSetBuilder; import hlf.java.rest.client.service.AddOrgToChannelWriteSetBuilder; import hlf.java.rest.client.service.ChannelConfigDeserialization; import java.util.ArrayList; @@ -61,6 +63,7 @@ public class NetworkStatusImplTest { @Mock private AddOrgToChannelWriteSetBuilder addOrgToChannelWriteSetBuilder; @Mock private ChannelConfigDeserialization channelConfigDeserialization; + @Mock private AddAnchorPeerToChannelWriteSetBuilder addAnchorPeerToChannelWriteSetBuilder; @Mock private MockedStatic staticConfigUpdate; @@ -82,6 +85,8 @@ public class NetworkStatusImplTest { @Mock private ByteString byteString; + @Mock private ConfigUpdate.Builder configUpdateBuilder; + @Test public void getChannelFromNetworkTest() throws InvalidProtocolBufferException, InvalidArgumentException, TransactionException { @@ -101,26 +106,27 @@ public void getChannelFromNetworkTest() @Test public void generateConfigUpdateTest() - throws InvalidArgumentException, TransactionException, InvalidProtocolBufferException { + throws InvalidProtocolBufferException, InvalidArgumentException, TransactionException { ResponseEntity responseEntity = new ResponseEntity<>( new ClientResponseModel(ErrorConstants.NO_ERROR, "dGhlX2NvbmZpZw=="), HttpStatus.OK); Mockito.when(gateway.getNetwork(Mockito.anyString())).thenReturn(network); Mockito.when(network.getChannel()).thenReturn(channel); + + Mockito.when(channel.getChannelConfigurationBytes()).thenReturn(new byte[0]); staticConfigUpdate .when(() -> ConfigUpdate.parseFrom(Mockito.any(byte[].class))) .thenReturn(configUpdate); - Mockito.when(channel.getChannelConfigurationBytes()).thenReturn(new byte[0]); - staticConfigUpdate.when(ConfigUpdate::newBuilder).thenReturn(builder); + staticConfigUpdate.when(() -> ConfigUpdate.newBuilder()).thenReturn(builder); + Mockito.when(configUpdate.getReadSet()).thenReturn(readset); Mockito.when(builder.setChannelId(Mockito.anyString())).thenReturn(builder); Mockito.when(builder.setReadSet(Mockito.any(ConfigGroup.class))).thenReturn(builder); - Mockito.when(builder.setWriteSet(Mockito.any(ConfigGroup.class))).thenReturn(builder); - Mockito.when(builder.build()).thenReturn(configUpdate); - Mockito.when(configUpdate.getReadSet()).thenReturn(readset); Mockito.when( addOrgToChannelWriteSetBuilder.buildWriteset( - Mockito.any(ConfigGroup.class), Mockito.any(NewOrgParamsDTO.class))) - .thenReturn(writeset); + Mockito.any(), Mockito.any(NewOrgParamsDTO.class))) + .thenReturn(readset); + Mockito.when(builder.setWriteSet(Mockito.any(ConfigGroup.class))).thenReturn(builder); + Mockito.when(builder.build()).thenReturn(configUpdate); staticJsonFormat.when(JsonFormat::printer).thenReturn(printer); Mockito.when(printer.print(Mockito.any(MessageOrBuilder.class))).thenReturn("the_config"); assertEquals( @@ -158,7 +164,7 @@ public void signChannelConfigTransactionTest() throws InvalidArgumentException { } @Test - public void commitChannelConfigTransactionTest() throws InvalidArgumentException { + public void commitChannelConfigTransactionTest() { List byteArrayList = new ArrayList(); byte[] outputByteArray = new byte[0]; byteArrayList.add(outputByteArray); @@ -186,32 +192,31 @@ public void commitChannelConfigTransactionTest() throws InvalidArgumentException } @Test - public void addOrgToChannelTest() - throws InvalidArgumentException, TransactionException, InvalidProtocolBufferException { + public void addOrgToChannelTest() throws InvalidArgumentException, TransactionException { byte[] outputByteArray = new byte[0]; ResponseEntity responseEntity = new ResponseEntity<>( new ClientResponseModel(ErrorConstants.NO_ERROR, ErrorCode.SUCCESS.getReason()), HttpStatus.OK); + Mockito.when(gateway.getNetwork(Mockito.anyString())).thenReturn(network); Mockito.when(network.getChannel()).thenReturn(channel); + Mockito.when(channel.getChannelConfigurationBytes()).thenReturn(new byte[0]); staticConfigUpdate .when(() -> ConfigUpdate.parseFrom(Mockito.any(byte[].class))) .thenReturn(configUpdate); staticConfigUpdate.when(() -> ConfigUpdate.newBuilder()).thenReturn(builder); - staticJsonFormat.when(JsonFormat::printer).thenReturn(printer); - Mockito.when(printer.print(Mockito.any(MessageOrBuilder.class))).thenReturn("the_config"); - staticConfigUpdate.when(ConfigUpdate::newBuilder).thenReturn(builder); Mockito.when(configUpdate.getReadSet()).thenReturn(readset); Mockito.when(builder.setChannelId(Mockito.anyString())).thenReturn(builder); Mockito.when(builder.setReadSet(Mockito.any(ConfigGroup.class))).thenReturn(builder); Mockito.when( addOrgToChannelWriteSetBuilder.buildWriteset( - Mockito.any(ConfigGroup.class), Mockito.any(NewOrgParamsDTO.class))) + Mockito.any(), Mockito.any(NewOrgParamsDTO.class))) .thenReturn(writeset); - Mockito.when(builder.setWriteSet(Mockito.any(ConfigGroup.class))).thenReturn(builder); + Mockito.when(builder.setWriteSet(writeset)).thenReturn(builder); Mockito.when(builder.build()).thenReturn(configUpdate); + staticJsonFormat.when(JsonFormat::printer).thenReturn(printer); Mockito.when(configUpdate.toByteString()).thenReturn(byteString); Mockito.when(byteString.toByteArray()).thenReturn(new byte[1]); Mockito.when(builder.build()).thenReturn(configUpdate); @@ -219,6 +224,7 @@ public void addOrgToChannelTest() channel.getUpdateChannelConfigurationSignature( Mockito.any(UpdateChannelConfiguration.class), Mockito.any(User.class))) .thenReturn(outputByteArray); + assertEquals( responseEntity.getBody().getMessage(), networkStatus @@ -226,4 +232,47 @@ public void addOrgToChannelTest() .getBody() .getMessage()); } + + @Test + public void addAnchorPeersToChannelTest() + throws InvalidArgumentException, TransactionException, InvalidProtocolBufferException { + byte[] outputByteArray = new byte[0]; + ResponseEntity responseEntity = + new ResponseEntity<>( + new ClientResponseModel(ErrorConstants.NO_ERROR, ErrorCode.SUCCESS.getReason()), + HttpStatus.OK); + + Mockito.when(gateway.getNetwork(Mockito.anyString())).thenReturn(network); + Mockito.when(network.getChannel()).thenReturn(channel); + + Mockito.when(channel.getChannelConfigurationBytes()).thenReturn(new byte[0]); + staticConfigUpdate + .when(() -> ConfigUpdate.parseFrom(Mockito.any(byte[].class))) + .thenReturn(configUpdate); + staticConfigUpdate.when(() -> ConfigUpdate.newBuilder()).thenReturn(builder); + Mockito.when(configUpdate.getReadSet()).thenReturn(readset); + Mockito.when(builder.setChannelId(Mockito.anyString())).thenReturn(builder); + Mockito.when(builder.setReadSet(Mockito.any(ConfigGroup.class))).thenReturn(builder); + Mockito.doReturn(writeset) + .when(addAnchorPeerToChannelWriteSetBuilder) + .buildWriteSetForAnchorPeers(Mockito.any(), Mockito.any(AnchorPeerParamsDTO.class)); + Mockito.when(builder.setWriteSet(writeset)).thenReturn(builder); + Mockito.when(builder.build()).thenReturn(configUpdate); + staticJsonFormat.when(JsonFormat::printer).thenReturn(printer); + Mockito.when(printer.print(Mockito.any(MessageOrBuilder.class))).thenReturn("the_config"); + Mockito.when(configUpdate.toByteString()).thenReturn(byteString); + Mockito.when(byteString.toByteArray()).thenReturn(new byte[1]); + Mockito.when(builder.build()).thenReturn(configUpdate); + Mockito.when( + channel.getUpdateChannelConfigurationSignature( + Mockito.any(UpdateChannelConfiguration.class), Mockito.any(User.class))) + .thenReturn(outputByteArray); + + assertEquals( + responseEntity.getBody().getMessage(), + networkStatus + .addAnchorPeersToChannel("some_channel_name", new AnchorPeerParamsDTO()) + .getBody() + .getMessage()); + } }