diff --git a/pkg/client/common/discovery/dynamicdiscovery/service.go b/pkg/client/common/discovery/dynamicdiscovery/service.go index 8267292e5a..cfd552251c 100644 --- a/pkg/client/common/discovery/dynamicdiscovery/service.go +++ b/pkg/client/common/discovery/dynamicdiscovery/service.go @@ -116,7 +116,7 @@ func asPeers(ctx contextAPI.Client, endpoints []*discclient.Peer) []fab.Peer { peerConfig, err := ctx.EndpointConfig().PeerConfig(url) if err != nil { - logger.Warnf("Error getting peer config for url [%s]: %s", err) + logger.Warnf("Error getting peer config for url [%s]: %s", url, err) continue } diff --git a/test/fixtures/config/config_test_multiorg_bootstrap.yaml b/test/fixtures/config/config_test_multiorg_bootstrap.yaml new file mode 100755 index 0000000000..58e7d96cd3 --- /dev/null +++ b/test/fixtures/config/config_test_multiorg_bootstrap.yaml @@ -0,0 +1,198 @@ +# +# Copyright SecureKey Technologies Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# +# The network connection profile provides client applications the information about the target +# blockchain network that are necessary for the applications to interact with it. These are all +# knowledge that must be acquired from out-of-band sources. This file provides such a source. +# + + +# +# Schema version of the content. Used by the SDK to apply the corresponding parsing rules. +# +version: 1.0.0 + +# +# The client section used by GO SDK. +# +client: + + organization: org1 + + logging: + level: info + + # Root of the MSP directories with keys and certs. + cryptoconfig: + path: ${GOPATH}/src/github.com/hyperledger/fabric-sdk-go/${CRYPTOCONFIG_FIXTURES_PATH} + + # Some SDKs support pluggable KV stores, the properties under "credentialStore" + # are implementation specific + credentialStore: + # [Optional]. Used by user store. Not needed if all credentials are embedded in configuration + # and enrollments are performed elswhere. + path: "/tmp/state-store" + + # [Optional]. Specific to the CryptoSuite implementation used by GO SDK. Software-based implementations + # requiring a key store. PKCS#11 based implementations does not. + cryptoStore: + # Specific to the underlying KeyValueStore that backs the crypto key store. + path: /tmp/msp + + # BCCSP config for the client. Used by GO SDK. + BCCSP: + security: + enabled: true + default: + provider: "SW" + hashAlgorithm: "SHA2" + softVerify: true + level: 256 + + tlsCerts: + # [Optional]. Use system certificate pool when connecting to peers, orderers (for negotiating TLS) Default: false + systemCertPool: true + + # [Optional]. Client key and cert for TLS handshake with peers and orderers + client: + key: + path: ${GOPATH}/src/github.com/hyperledger/fabric-sdk-go/test/fixtures/config/mutual_tls/client_sdk_go-key.pem + cert: + path: ${GOPATH}/src/github.com/hyperledger/fabric-sdk-go/test/fixtures/config/mutual_tls/client_sdk_go.pem + +channels: + # multi-org test channel + orgchannel: + + # anchor peers only for the bootstrap config is required, other org's peers will be discovered + peers: + peer0.org1.example.com: + endorsingPeer: true + chaincodeQuery: true + ledgerQuery: true + eventSource: true + + peer0.org2.example.com: + endorsingPeer: true + chaincodeQuery: true + ledgerQuery: true + eventSource: true + + policies: + queryChannelConfig: + minResponses: 1 + maxTargets: 1 + retryOpts: + attempts: 5 + initialBackoff: 500ms + maxBackoff: 5s + backoffFactor: 2.0 + + +# +# list of participating organizations in this network +# +organizations: + org1: + mspid: Org1MSP + + # This org's MSP store (absolute path or relative to client.cryptoconfig) + cryptoPath: peerOrganizations/org1.example.com/users/{username}@org1.example.com/msp + + peers: + - peer0.org1.example.com + + org2: + mspid: Org2MSP + + # This org's MSP store (absolute path or relative to client.cryptoconfig) + cryptoPath: peerOrganizations/org2.example.com/users/{username}@org2.example.com/msp + + peers: + - peer0.org2.example.com + + # Orderer Org name + ordererorg: + # Membership Service Provider ID for this organization + mspID: "OrdererOrg" + + # Needed to load users crypto keys and certs for this org (absolute path or relative to global crypto path, DEV mode) + cryptoPath: ordererOrganizations/example.com/users/{username}@example.com/msp + +# +# List of peers to send various requests to, including endorsement, query +# and event listener registration. +# +peers: + # defining bootstrap peers only + peer0.org1.example.com: + url: peer0.org1.example.com:7051 + eventUrl: peer0.org1.example.com:7053 + + grpcOptions: + ssl-target-name-override: peer0.org1.example.com + keep-alive-time: 0s + keep-alive-timeout: 20s + keep-alive-permit: false + fail-fast: false + allow-insecure: false + + tlsCACerts: + path: ${GOPATH}/src/github.com/hyperledger/fabric-sdk-go/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem + + peer0.org2.example.com: + url: peer0.org2.example.com:8051 + eventUrl: peer0.org2.example.com:8053 + grpcOptions: + ssl-target-name-override: peer0.org2.example.com + keep-alive-time: 0s + keep-alive-timeout: 20s + keep-alive-permit: false + fail-fast: false + allow-insecure: false + + tlsCACerts: + path: ${GOPATH}/src/github.com/hyperledger/fabric-sdk-go/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem + +# +# List of orderers to send transaction and channel create/update requests to. For the time +# being only one orderer is needed. If more than one is defined, which one get used by the +# SDK is implementation specific. Consult each SDK's documentation for its handling of orderers. +# +orderers: + # needed to fetch the ordrerer config for create channel + orderer.example.com: + url: orderer.example.com:7050 + + grpcOptions: + ssl-target-name-override: orderer.example.com + keep-alive-time: 0s + keep-alive-timeout: 20s + keep-alive-permit: false + fail-fast: false + allow-insecure: false + + tlsCACerts: + path: ${GOPATH}/src/github.com/hyperledger/fabric-sdk-go/${CRYPTOCONFIG_FIXTURES_PATH}/ordererOrganizations/example.com/tlsca/tlsca.example.com-cert.pem + + +# EntityMatchers enable substitution of network hostnames with static configurations + # so that properties can be mapped. Regex can be used for this purpose +# UrlSubstitutionExp can be empty which means the same network hostname will be used +# UrlSubstitutionExp can be given same as mapped peer url, so that mapped peer url can be used +# UrlSubstitutionExp can have golang regex matchers like $1.local.example.$2:$3 for pattern + # like peer0.org1.example.com:1234 which converts peer0.org1.example.com to peer0.org1.local.example.com:1234 +# EventUrlSubstitutionExp and sslTargetOverrideUrlSubstitutionExp follow in the same lines as + # SubstitutionExp for the fields eventUrl and gprcOptions.ssl-target-name-override respectively +# In any case mappedHost's config will be used, so mapped host cannot be empty, if entityMatchers are used +entityMatchers: + peer: + # the below matcher will allow dynamic discovery to use the anchor peer (peer0.org1.example.com) + # as a template for all org1 discovered peers config + - pattern: (\w+).org1.example.com:(\d+) + urlSubstitutionExp: $1.org1.example.com:$2 + sslTargetOverrideUrlSubstitutionExp: $1.org1.example.com + mappedHost: peer0.org1.example.com \ No newline at end of file diff --git a/test/fixtures/dockerenv/docker-compose.yaml b/test/fixtures/dockerenv/docker-compose.yaml index 1a29ba5352..ef376d926c 100644 --- a/test/fixtures/dockerenv/docker-compose.yaml +++ b/test/fixtures/dockerenv/docker-compose.yaml @@ -107,13 +107,13 @@ services: - CORE_VM_DOCKER_ATTACHSTDOUT=true - CORE_PEER_LOCALMSPID=Org1MSP - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/peer/ - - CORE_PEER_LISTENADDRESS=0.0.0.0:7051 - - CORE_PEER_ADDRESS=0.0.0.0:7051 + - CORE_PEER_LISTENADDRESS=peer0.org1.example.com:7051 + - CORE_PEER_ADDRESS=peer0.org1.example.com:7051 - CORE_PEER_CHAINCODELISTENADDRESS=peer0.org1.example.com:7052 - CORE_PEER_ADDRESSAUTODETECT=true - - CORE_PEER_GOSSIP_BOOTSTRAP=127.0.0.1:7051 + - CORE_PEER_GOSSIP_BOOTSTRAP=peer1.org1.example.com:7151 - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.example.com:7051 - - CORE_PEER_EVENTS_ADDRESS=0.0.0.0:7053 + - CORE_PEER_EVENTS_ADDRESS=peer0.org1.example.com:7053 - CORE_PEER_TLS_ENABLED=true - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/tls/peer/server.key - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/tls/peer/server.crt @@ -151,6 +151,67 @@ services: - builder - golangruntime + org1peer2: + image: ${FABRIC_DOCKER_REGISTRY}${FABRIC_PEER_FIXTURE_IMAGE}:${ARCH}${ARCH_SEP}${FABRIC_PEER_FIXTURE_TAG} + environment: + - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock + - CORE_PEER_ID=peer1.org1.example.com + - CORE_LOGGING_PEER=debug + # - CORE_LOGGING_GRPC=debug + # - CORE_LOGGING_GOSSIP=debug + # - CORE_CHAINCODE_STARTUPTIMEOUT=30s + - CORE_CHAINCODE_LOGGING_SHIM=debug + - CORE_CHAINCODE_LOGGING_LEVEL=debug + - CORE_CHAINCODE_BUILDER=${FABRIC_DOCKER_REGISTRY}${FABRIC_BUILDER_FIXTURE_IMAGE}:${ARCH}${ARCH_SEP}${FABRIC_BUILDER_FIXTURE_TAG} + - CORE_CHAINCODE_GOLANG_RUNTIME=${FABRIC_DOCKER_REGISTRY}${FABRIC_BASEOS_FIXTURE_IMAGE}:${BASE_ARCH}-${FABRIC_BASEOS_FIXTURE_TAG} + ## the following setting redirects chaincode container logs to the peer container logs + - CORE_VM_DOCKER_ATTACHSTDOUT=true + - CORE_PEER_LOCALMSPID=Org1MSP + - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/peer/ + - CORE_PEER_LISTENADDRESS=peer1.org1.example.com:7151 + - CORE_PEER_ADDRESS=peer1.org1.example.com:7151 + - CORE_PEER_CHAINCODELISTENADDRESS=peer1.org1.example.com:7152 + - CORE_PEER_ADDRESSAUTODETECT=true + - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org1.example.com:7051 + - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1.org1.example.com:7151 + - CORE_PEER_EVENTS_ADDRESS=peer1.org1.example.com:7153 + - CORE_PEER_TLS_ENABLED=true + - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/tls/peer/server.key + - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/tls/peer/server.crt + - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/tls/peer/ca.crt + - CORE_PEER_TLS_CLIENTAUTHREQUIRED + - CORE_PEER_TLS_CLIENTROOTCAS_FILES + # # the following setting starts chaincode containers on the same + # # bridge network as the peers + # # https://docs.docker.com/compose/networking/ + - CORE_PEER_NETWORKID=${CORE_PEER_NETWORKID} + - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=${CORE_PEER_NETWORKID}_default + #comment out logging.driver in order to render the debug logs + logging: + driver: none + working_dir: /opt/gopath/src/github.com/hyperledger/fabric + command: peer node start + ports: + - "7151:7151" + - "7153:7153" + expose: + - "7151" + - "7152" + - "7153" + volumes: + - /var/run/:/host/var/run/ + - ../fabric/${FABRIC_CRYPTOCONFIG_VERSION}/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/msp:/etc/hyperledger/msp/peer + - ../fabric/${FABRIC_CRYPTOCONFIG_VERSION}/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls:/etc/hyperledger/tls/peer + - ../fabric/${FABRIC_CRYPTOCONFIG_VERSION}/mutual_tls:/etc/hyperledger/mutual_tls/peer + networks: + default: + aliases: + - peer1.org1.example.com + depends_on: + - orderer1 + - builder + - golangruntime + org2peer1: image: ${FABRIC_DOCKER_REGISTRY}${FABRIC_PEER_FIXTURE_IMAGE}:${ARCH}${ARCH_SEP}${FABRIC_PEER_FIXTURE_TAG} environment: @@ -169,13 +230,13 @@ services: - CORE_VM_DOCKER_ATTACHSTDOUT=true - CORE_PEER_LOCALMSPID=Org2MSP - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/peer/ - - CORE_PEER_LISTENADDRESS=0.0.0.0:8051 - - CORE_PEER_ADDRESS=0.0.0.0:8051 + - CORE_PEER_LISTENADDRESS=peer0.org2.example.com:8051 + - CORE_PEER_ADDRESS=peer0.org2.example.com:8051 - CORE_PEER_CHAINCODELISTENADDRESS=peer0.org2.example.com:7052 - CORE_PEER_ADDRESSAUTODETECT=true - - CORE_PEER_GOSSIP_BOOTSTRAP=127.0.0.1:8051 + - CORE_PEER_GOSSIP_BOOTSTRAP=peer1.org2.example.com:9051 - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org2.example.com:8051 - - CORE_PEER_EVENTS_ADDRESS=0.0.0.0:8053 + - CORE_PEER_EVENTS_ADDRESS=peer0.org2.example.com:8053 - CORE_PEER_TLS_ENABLED=true - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/tls/peer/server.key - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/tls/peer/server.crt @@ -231,13 +292,13 @@ services: - CORE_VM_DOCKER_ATTACHSTDOUT=true - CORE_PEER_LOCALMSPID=Org2MSP - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/peer/ - - CORE_PEER_LISTENADDRESS=0.0.0.0:9051 - - CORE_PEER_ADDRESS=0.0.0.0:9051 + - CORE_PEER_LISTENADDRESS=peer1.org2.example.com:9051 + - CORE_PEER_ADDRESS=peer1.org2.example.com:9051 - CORE_PEER_CHAINCODELISTENADDRESS=peer1.org2.example.com:9052 - CORE_PEER_ADDRESSAUTODETECT=true - - CORE_PEER_GOSSIP_BOOTSTRAP=127.0.0.1:9051 + - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org2.example.com:8051 - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1.org2.example.com:9051 - - CORE_PEER_EVENTS_ADDRESS=0.0.0.0:9053 + - CORE_PEER_EVENTS_ADDRESS=peer1.org2.example.com:9053 - CORE_PEER_TLS_ENABLED=true - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/tls/peer/server.key - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/tls/peer/server.crt diff --git a/test/integration/orgs/multiple_orgs_minconfig_test.go b/test/integration/orgs/multiple_orgs_minconfig_test.go new file mode 100644 index 0000000000..405d05057d --- /dev/null +++ b/test/integration/orgs/multiple_orgs_minconfig_test.go @@ -0,0 +1,112 @@ +// +build devstable + +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package orgs + +import ( + "testing" + "time" + + "github.com/hyperledger/fabric-sdk-go/pkg/client/common/discovery/dynamicdiscovery" + "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab" + "github.com/hyperledger/fabric-sdk-go/pkg/core/config" + "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" + "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk/factory/defsvc" + "github.com/hyperledger/fabric-sdk-go/test/integration" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const bootStrapCC = "btspExampleCC" + +//TestOrgsEndToEndWithBootstrapConfigs does the same as TestOrgsEndToEnd with the difference of loading +// minimal configs instead of the normal config_test.yaml configs and with the help of discovery service to discover +// other peers not in the config (example org1 has 2 peers and only peer0 is defined in the bootstrap configs) +func TestOrgsEndToEndWithBootstrapConfigs(t *testing.T) { + configPath := "../../fixtures/config/config_test_multiorg_bootstrap.yaml" + sdk, err := fabsdk.New(config.FromFile(configPath), + fabsdk.WithServicePkg(&DynamicDiscoveryProviderFactory{}), + ) + if err != nil { + require.NoError(t, err, "Failed to create new SDK") + } + defer sdk.Close() + + // Delete all private keys from the crypto suite store + // and users from the user store at the end + integration.CleanupUserData(t, sdk) + defer integration.CleanupUserData(t, sdk) + + //prepare contexts + mc := multiorgContext{ + ordererClientContext: sdk.Context(fabsdk.WithUser(ordererAdminUser), fabsdk.WithOrg(ordererOrgName)), + org1AdminClientContext: sdk.Context(fabsdk.WithUser(org1AdminUser), fabsdk.WithOrg(org1)), + org2AdminClientContext: sdk.Context(fabsdk.WithUser(org2AdminUser), fabsdk.WithOrg(org2)), + ccName: bootStrapCC, + } + + // create channel and join orderer/orgs peers to it if was not done already + setupClientContextsAndChannel(t, sdk, &mc) + + // wait some time to allow the gossip to propagate the peers discovery + time.Sleep(10 * time.Second) + + testDynamicDiscovery(t, sdk) + + // now run the same test as multiple_orgs_test.go to make sure it works with bootstrap config.. + + // Load specific targets for move funds test + loadOrgPeers(t, sdk.Context(fabsdk.WithUser(org1AdminUser), fabsdk.WithOrg(org1))) + + expectedValue := testWithOrg1(t, sdk, &mc) + expectedValue = testWithOrg2(t, expectedValue, mc.ccName) + verifyWithOrg1(t, sdk, expectedValue, mc.ccName) +} + +func testDynamicDiscovery(t *testing.T, sdk *fabsdk.FabricSDK) { + // example discovering the peers from the bootstap peer + // there should be three peers returned from discovery: + // 1 org1 anchor peer (peer0.org1.example.com) + // 1 discovered peer (not in config: peer1.org1.example.com) + // 1 org2 anchor peer (peer0.org2.example.com) + peersList := discoverPeers(t, sdk) + assert.Equal(t, 3, len(peersList), "Expected exactly 3 peers as per %s's channel and %s's org configs", channelID, org2) +} + +func discoverPeers(t *testing.T, sdk *fabsdk.FabricSDK) []fab.Peer { + // any user from the network can access the discovery service, user org2User is selected for the test. + chProvider := sdk.ChannelContext(channelID, fabsdk.WithUser(org2User), fabsdk.WithOrg(org2)) + chCtx, err := chProvider() + require.NoError(t, err, "Error creating channel context") + + chCtx.ChannelService() + peers, err := chCtx.DiscoveryService().GetPeers() + require.NoErrorf(t, err, "Error getting peers for channel [%s]", channelID) + require.NotEmptyf(t, peers, "No peers were found for channel [%s]", channelID) + + t.Logf("Peers of channel [%s]:", channelID) + for i, p := range peers { + t.Logf("%d- [%s] - MSP [%s]", i, p.URL(), p.MSPID()) + } + return peers +} + +// DynamicDiscoveryProviderFactory is configured with dynamic (endorser) selection provider +type DynamicDiscoveryProviderFactory struct { + defsvc.ProviderFactory +} + +// CreateDiscoveryProvider returns a new dynamic discovery provider +func (f *DynamicDiscoveryProviderFactory) CreateDiscoveryProvider(config fab.EndpointConfig) (fab.DiscoveryProvider, error) { + return dynamicdiscovery.New(config), nil +} + +// CreateLocalDiscoveryProvider returns a new local dynamic discovery provider +func (f *DynamicDiscoveryProviderFactory) CreateLocalDiscoveryProvider(config fab.EndpointConfig) (fab.LocalDiscoveryProvider, error) { + return dynamicdiscovery.New(config), nil +} diff --git a/test/integration/orgs/multiple_orgs_test.go b/test/integration/orgs/multiple_orgs_test.go index 2e9ac00a53..2b45d59536 100644 --- a/test/integration/orgs/multiple_orgs_test.go +++ b/test/integration/orgs/multiple_orgs_test.go @@ -54,18 +54,33 @@ const ( org1User = "User1" org2User = "User1" channelID = "orgchannel" + exampleCC = "exampleCC" ) -// SDK -var sdk *fabsdk.FabricSDK +var ( + // SDK + sdk *fabsdk.FabricSDK -// Org MSP clients -var org1MspClient *mspclient.Client -var org2MspClient *mspclient.Client + // Org MSP clients + org1MspClient *mspclient.Client + org2MspClient *mspclient.Client + // Peers + orgTestPeer0 fab.Peer + orgTestPeer1 fab.Peer -// Peers -var orgTestPeer0 fab.Peer -var orgTestPeer1 fab.Peer + isJoinedChannel bool +) + +// used to create context for different tests in the orgs package +type multiorgContext struct { + // client contexts + ordererClientContext contextAPI.ClientProvider + org1AdminClientContext contextAPI.ClientProvider + org2AdminClientContext contextAPI.ClientProvider + org1ResMgmt *resmgmt.Client + org2ResMgmt *resmgmt.Client + ccName string +} func TestMain(m *testing.M) { err := setup() @@ -118,27 +133,20 @@ func TestOrgsEndToEnd(t *testing.T) { // Load specific targets for move funds test loadOrgPeers(t, sdk.Context(fabsdk.WithUser(org1AdminUser), fabsdk.WithOrg(org1))) - expectedValue := testWithOrg1(t, sdk) - expectedValue = testWithOrg2(t, expectedValue) - verifyWithOrg1(t, sdk, expectedValue) -} - -func testWithOrg1(t *testing.T, sdk *fabsdk.FabricSDK) int { - //prepare contexts - ordererClientContext := sdk.Context(fabsdk.WithUser(ordererAdminUser), fabsdk.WithOrg(ordererOrgName)) - org1AdminClientContext := sdk.Context(fabsdk.WithUser(org1AdminUser), fabsdk.WithOrg(org1)) - org2AdminClientContext := sdk.Context(fabsdk.WithUser(org2AdminUser), fabsdk.WithOrg(org2)) - org1AdminChannelContext := sdk.ChannelContext(channelID, fabsdk.WithUser(org1AdminUser), fabsdk.WithOrg(org1)) - org1ChannelClientContext := sdk.ChannelContext(channelID, fabsdk.WithUser(org1User), fabsdk.WithOrg(org1)) - org2ChannelClientContext := sdk.ChannelContext(channelID, fabsdk.WithUser(org2User), fabsdk.WithOrg(org2)) - - // Channel management client is responsible for managing channels (create/update channel) - chMgmtClient, err := resmgmt.New(ordererClientContext) - if err != nil { - t.Fatal(err) + mc := multiorgContext{ + ordererClientContext: sdk.Context(fabsdk.WithUser(ordererAdminUser), fabsdk.WithOrg(ordererOrgName)), + org1AdminClientContext: sdk.Context(fabsdk.WithUser(org1AdminUser), fabsdk.WithOrg(org1)), + org2AdminClientContext: sdk.Context(fabsdk.WithUser(org2AdminUser), fabsdk.WithOrg(org2)), + ccName: exampleCC, // basic multi orgs test uses exampleCC for testing } + expectedValue := testWithOrg1(t, sdk, &mc) + expectedValue = testWithOrg2(t, expectedValue, mc.ccName) + verifyWithOrg1(t, sdk, expectedValue, mc.ccName) +} + +func setupClientContextsAndChannel(t *testing.T, sdk *fabsdk.FabricSDK, mc *multiorgContext) { // Get signing identity that is used to sign create channel request org1AdminUser, err := org1MspClient.GetSigningIdentity(org1AdminUser) if err != nil { @@ -150,29 +158,39 @@ func testWithOrg1(t *testing.T, sdk *fabsdk.FabricSDK) int { t.Fatalf("failed to get org2AdminUser, err : %v", err) } - createChannel(org1AdminUser, org2AdminUser, chMgmtClient, t) - // Org1 resource management client (Org1 is default org) - org1ResMgmt, err := resmgmt.New(org1AdminClientContext) - if err != nil { - t.Fatalf("Failed to create new resource management client: %s", err) - } + org1RMgmt, err := resmgmt.New(mc.org1AdminClientContext) + require.NoError(t, err, "failed to create org1 resource management client") - // Org1 peers join channel - if err = org1ResMgmt.JoinChannel("orgchannel", resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")); err != nil { - t.Fatalf("Org1 peers failed to JoinChannel: %s", err) - } + mc.org1ResMgmt = org1RMgmt // Org2 resource management client - org2ResMgmt, err := resmgmt.New(org2AdminClientContext) - if err != nil { - t.Fatal(err) - } + org2RMgmt, err := resmgmt.New(mc.org2AdminClientContext) + require.NoError(t, err, "failed to create org2 resource management client") + + mc.org2ResMgmt = org2RMgmt - // Org2 peers join channel - if err = org2ResMgmt.JoinChannel("orgchannel", resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")); err != nil { - t.Fatalf("Org2 peers failed to JoinChannel: %s", err) + // create/join channel if was not already done + if !isJoinedChannel { + defer func() { isJoinedChannel = true }() + createChannel(org1AdminUser, org2AdminUser, mc, t) + // Org1 peers join channel + err = mc.org1ResMgmt.JoinChannel(channelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")) + require.NoError(t, err, "Org1 peers failed to JoinChannel") + + // Org2 peers join channel + err = mc.org2ResMgmt.JoinChannel(channelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")) + require.NoError(t, err, "Org2 peers failed to JoinChannel") } +} + +func testWithOrg1(t *testing.T, sdk *fabsdk.FabricSDK, mc *multiorgContext) int { + + org1AdminChannelContext := sdk.ChannelContext(channelID, fabsdk.WithUser(org1AdminUser), fabsdk.WithOrg(org1)) + org1ChannelClientContext := sdk.ChannelContext(channelID, fabsdk.WithUser(org1User), fabsdk.WithOrg(org1)) + org2ChannelClientContext := sdk.ChannelContext(channelID, fabsdk.WithUser(org2User), fabsdk.WithOrg(org2)) + + setupClientContextsAndChannel(t, sdk, mc) ccPkg, err := packager.NewCCPackage("github.com/example_cc", "../../fixtures/testdata") if err != nil { @@ -180,15 +198,15 @@ func testWithOrg1(t *testing.T, sdk *fabsdk.FabricSDK) int { } // Create chaincode package for example cc - createCC(t, org1ResMgmt, org2ResMgmt, ccPkg) + createCC(t, mc.org1ResMgmt, mc.org2ResMgmt, ccPkg, mc.ccName) chClientOrg1User, chClientOrg2User := connectUserToOrgChannel(org1ChannelClientContext, t, org2ChannelClientContext) // Call with a dummy function and expect a fail with multiple errors - verifyErrorFromCC(chClientOrg1User, t) + verifyErrorFromCC(chClientOrg1User, t, mc.ccName) // Org1 user queries initial value on both peers - value := queryCC(chClientOrg1User, t) + value := queryCC(chClientOrg1User, t, mc.ccName) initial, _ := strconv.Atoi(string(value)) ledgerClient, err := ledger.New(org1AdminChannelContext) @@ -200,24 +218,24 @@ func testWithOrg1(t *testing.T, sdk *fabsdk.FabricSDK) int { ledgerInfoBefore := getBlockchainInfo(ledgerClient, t) // Org2 user moves funds on org2 peer - transactionID := moveFunds(chClientOrg2User, t) + transactionID := moveFunds(chClientOrg2User, t, mc.ccName) // Assert that funds have changed value on org1 peer - verifyValue(t, chClientOrg1User, initial+1) + verifyValue(t, chClientOrg1User, initial+1, mc.ccName) // Get latest blockchain info checkLedgerInfo(ledgerClient, t, ledgerInfoBefore, transactionID) // Start chaincode upgrade process (install and instantiate new version of exampleCC) - upgradeCC(ccPkg, org1ResMgmt, t, org2ResMgmt) + upgradeCC(ccPkg, mc.org1ResMgmt, t, mc.org2ResMgmt, mc.ccName) // Org2 user moves funds on org2 peer (cc policy fails since both Org1 and Org2 peers should participate) - testCCPolicy(chClientOrg2User, t) + testCCPolicy(chClientOrg2User, t, mc.ccName) // Assert that funds have changed value on org1 peer beforeTxValue, _ := strconv.Atoi(integration.ExampleCCUpgradeB) expectedValue := beforeTxValue + 1 - verifyValue(t, chClientOrg1User, expectedValue) + verifyValue(t, chClientOrg1User, expectedValue, mc.ccName) return expectedValue } @@ -262,54 +280,76 @@ func checkLedgerInfo(ledgerClient *ledger.Client, t *testing.T, ledgerInfoBefore } } -func createChannel(org1AdminUser msp.SigningIdentity, org2AdminUser msp.SigningIdentity, chMgmtClient *resmgmt.Client, t *testing.T) { - req := resmgmt.SaveChannelRequest{ChannelID: "orgchannel", +func createChannel(org1AdminUser msp.SigningIdentity, org2AdminUser msp.SigningIdentity, mc *multiorgContext, t *testing.T) { + // Channel management client is responsible for managing channels (create/update channel) + chMgmtClient, err := resmgmt.New(mc.ordererClientContext) + require.NoError(t, err, "failed to get a new channel management client") + + // create a channel for orgchannel.tx + req := resmgmt.SaveChannelRequest{ChannelID: channelID, ChannelConfigPath: path.Join("../../../", metadata.ChannelConfigPath, "orgchannel.tx"), SigningIdentities: []msp.SigningIdentity{org1AdminUser, org2AdminUser}} txID, err := chMgmtClient.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")) - require.Nil(t, err, "error should be nil") + require.Nil(t, err, "error should be nil for SaveChannel of orgchannel") require.NotEmpty(t, txID, "transaction ID should be populated") + + //do the same get ch client and create channel for each anchor peer as well (first for Org1MSP) + chMgmtClient, err = resmgmt.New(mc.org1AdminClientContext) + require.NoError(t, err, "failed to get a new channel management client for org1Admin") + req = resmgmt.SaveChannelRequest{ChannelID: channelID, + ChannelConfigPath: path.Join("../../../", metadata.ChannelConfigPath, "orgchannelOrg1MSPanchors.tx"), + SigningIdentities: []msp.SigningIdentity{org1AdminUser}} + txID, err = chMgmtClient.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")) + require.Nil(t, err, "error should be nil for SaveChannel for anchor peer 1") + require.NotEmpty(t, txID, "transaction ID should be populated for anchor peer 1") + + // lastly create channel for Org2MSP anchor peer + chMgmtClient, err = resmgmt.New(mc.org2AdminClientContext) + require.NoError(t, err, "failed to get a new channel management client for org2Admin") + req = resmgmt.SaveChannelRequest{ChannelID: channelID, + ChannelConfigPath: path.Join("../../../", metadata.ChannelConfigPath, "orgchannelOrg2MSPanchors.tx"), + SigningIdentities: []msp.SigningIdentity{org2AdminUser}} + txID, err = chMgmtClient.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")) + require.Nil(t, err, "error should be nil for SaveChannel for anchor peer 2") + require.NotEmpty(t, txID, "transaction ID should be populated for anchor peer 2") } -func testCCPolicy(chClientOrg2User *channel.Client, t *testing.T) { - _, err := chClientOrg2User.Execute(channel.Request{ChaincodeID: "exampleCC", Fcn: "invoke", Args: integration.ExampleCCTxArgs()}, channel.WithTargets(orgTestPeer1), +func testCCPolicy(chClientOrg2User *channel.Client, t *testing.T, ccName string) { + _, err := chClientOrg2User.Execute(channel.Request{ChaincodeID: ccName, Fcn: "invoke", Args: integration.ExampleCCTxArgs()}, channel.WithTargets(orgTestPeer1), channel.WithRetry(retry.DefaultChannelOpts)) if err == nil { t.Fatalf("Should have failed to move funds due to cc policy") } // Org2 user moves funds (cc policy ok since we have provided peers for both Orgs) - _, err = chClientOrg2User.Execute(channel.Request{ChaincodeID: "exampleCC", Fcn: "invoke", Args: integration.ExampleCCTxArgs()}, channel.WithTargets(orgTestPeer0, orgTestPeer1), + _, err = chClientOrg2User.Execute(channel.Request{ChaincodeID: ccName, Fcn: "invoke", Args: integration.ExampleCCTxArgs()}, channel.WithTargets(orgTestPeer0, orgTestPeer1), channel.WithRetry(retry.DefaultChannelOpts)) if err != nil { t.Fatalf("Failed to move funds: %s", err) } } -func upgradeCC(ccPkg *resource.CCPackage, org1ResMgmt *resmgmt.Client, t *testing.T, org2ResMgmt *resmgmt.Client) { - installCCReq := resmgmt.InstallCCRequest{Name: "exampleCC", Path: "github.com/example_cc", Version: "1", Package: ccPkg} +func upgradeCC(ccPkg *resource.CCPackage, org1ResMgmt *resmgmt.Client, t *testing.T, org2ResMgmt *resmgmt.Client, ccName string) { + installCCReq := resmgmt.InstallCCRequest{Name: ccName, Path: "github.com/example_cc", Version: "1", Package: ccPkg} // Install example cc version '1' to Org1 peers _, err := org1ResMgmt.InstallCC(installCCReq, resmgmt.WithRetry(retry.DefaultResMgmtOpts)) - if err != nil { - t.Fatal(err) - } + require.Nil(t, err, "error should be nil for InstallCC version '1' or Org1 peers") + // Install example cc version '1' to Org2 peers _, err = org2ResMgmt.InstallCC(installCCReq, resmgmt.WithRetry(retry.DefaultResMgmtOpts)) - if err != nil { - t.Fatal(err) - } + require.Nil(t, err, "error should be nil for InstallCC version '1' or Org2 peers") + // New chaincode policy (both orgs have to approve) org1Andorg2Policy, err := cauthdsl.FromString("AND ('Org1MSP.member','Org2MSP.member')") - if err != nil { - t.Fatal(err) - } + require.Nil(t, err, "error should be nil for getting cc policy with both orgs to approve") + // Org1 resource manager will instantiate 'example_cc' version 1 on 'orgchannel' - upgradeResp, err := org1ResMgmt.UpgradeCC("orgchannel", resmgmt.UpgradeCCRequest{Name: "exampleCC", Path: "github.com/example_cc", Version: "1", Args: integration.ExampleCCUpgradeArgs(), Policy: org1Andorg2Policy}) - require.Nil(t, err, "error should be nil") + upgradeResp, err := org1ResMgmt.UpgradeCC(channelID, resmgmt.UpgradeCCRequest{Name: ccName, Path: "github.com/example_cc", Version: "1", Args: integration.ExampleCCUpgradeArgs(), Policy: org1Andorg2Policy}) + require.Nil(t, err, "error should be nil for UpgradeCC version '1' on 'orgchannel'") require.NotEmpty(t, upgradeResp, "transaction response should be populated") } -func moveFunds(chClientOrgUser *channel.Client, t *testing.T) fab.TransactionID { - response, err := chClientOrgUser.Execute(channel.Request{ChaincodeID: "exampleCC", Fcn: "invoke", Args: integration.ExampleCCTxArgs()}, channel.WithTargets(orgTestPeer1), +func moveFunds(chClientOrgUser *channel.Client, t *testing.T, ccName string) fab.TransactionID { + response, err := chClientOrgUser.Execute(channel.Request{ChaincodeID: ccName, Fcn: "invoke", Args: integration.ExampleCCTxArgs()}, channel.WithTargets(orgTestPeer1), channel.WithRetry(retry.DefaultChannelOpts)) if err != nil { t.Fatalf("Failed to move funds: %s", err) @@ -350,23 +390,21 @@ func getBlockchainInfo(ledgerClient *ledger.Client, t *testing.T) *fab.Blockchai return ledgerInfoBefore } -func queryCC(chClientOrg1User *channel.Client, t *testing.T) []byte { - response, err := chClientOrg1User.Query(channel.Request{ChaincodeID: "exampleCC", Fcn: "invoke", Args: integration.ExampleCCQueryArgs()}, +func queryCC(chClientOrg1User *channel.Client, t *testing.T, ccName string) []byte { + response, err := chClientOrg1User.Query(channel.Request{ChaincodeID: ccName, Fcn: "invoke", Args: integration.ExampleCCQueryArgs()}, channel.WithRetry(retry.DefaultChannelOpts)) - if err != nil { - t.Fatalf("Failed to query funds: %s", err) - } - if response.ChaincodeStatus == 0 { - t.Fatalf("Expected ChaincodeStatus") - } - if response.Responses[0].ChaincodeStatus != response.ChaincodeStatus { - t.Fatalf("Expected the chaincode status returned by successful Peer Endorsement to be same as Chaincode status for client response") - } + + require.NoError(t, err, "Failed to query funds") + + require.NotZero(t, response.ChaincodeStatus, "Expected ChaincodeStatus") + + require.Equal(t, response.ChaincodeStatus, response.Responses[0].ChaincodeStatus, "Expected the chaincode status returned by successful Peer Endorsement to be same as Chaincode status for client response") + return response.Payload } -func verifyErrorFromCC(chClientOrg1User *channel.Client, t *testing.T) { - r, err := chClientOrg1User.Query(channel.Request{ChaincodeID: "exampleCC", Fcn: "DUMMY_FUNCTION", Args: integration.ExampleCCQueryArgs()}, +func verifyErrorFromCC(chClientOrg1User *channel.Client, t *testing.T, ccName string) { + r, err := chClientOrg1User.Query(channel.Request{ChaincodeID: ccName, Fcn: "DUMMY_FUNCTION", Args: integration.ExampleCCQueryArgs()}, channel.WithRetry(retry.DefaultChannelOpts)) t.Logf("verifyErrorFromCC err: %s ***** responses: %v", err, r) @@ -385,46 +423,59 @@ func verifyErrorFromCC(chClientOrg1User *channel.Client, t *testing.T) { } } -func createCC(t *testing.T, org1ResMgmt *resmgmt.Client, org2ResMgmt *resmgmt.Client, ccPkg *resource.CCPackage) { - installCCReq := resmgmt.InstallCCRequest{Name: "exampleCC", Path: "github.com/example_cc", Version: "0", Package: ccPkg} +func createCC(t *testing.T, org1ResMgmt *resmgmt.Client, org2ResMgmt *resmgmt.Client, ccPkg *resource.CCPackage, ccName string) { + installCCReq := resmgmt.InstallCCRequest{Name: ccName, Path: "github.com/example_cc", Version: "0", Package: ccPkg} + // Install example cc to Org1 peers _, err := org1ResMgmt.InstallCC(installCCReq, resmgmt.WithRetry(retry.DefaultResMgmtOpts)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err, "InstallCC for Org1 failed") + // Install example cc to Org2 peers _, err = org2ResMgmt.InstallCC(installCCReq, resmgmt.WithRetry(retry.DefaultResMgmtOpts)) - if err != nil { - t.Fatal(err) - } - // Set up chaincode policy to 'any of two msps' - ccPolicy := cauthdsl.SignedByAnyMember([]string{"Org1MSP", "Org2MSP"}) - // Org1 resource manager will instantiate 'example_cc' on 'orgchannel' - instantiateResp, err := org1ResMgmt.InstantiateCC("orgchannel", resmgmt.InstantiateCCRequest{Name: "exampleCC", Path: "github.com/example_cc", Version: "0", Args: integration.ExampleCCInitArgs(), Policy: ccPolicy}, resmgmt.WithRetry(retry.DefaultResMgmtOpts)) - require.Nil(t, err, "error should be nil") - require.NotEmpty(t, instantiateResp, "transaction response should be populated") + require.NoError(t, err, "InstallCC for Org2 failed") + + instantiateCC(t, org1ResMgmt, []string{"peer0.org1.example.com"}, ccName) + // instantiateCC above will propagate the call to the other peers through gossip, so no need to call it on the other peers. + // Verify that example CC is instantiated on Org1 peer - chaincodeQueryResponse, err := org1ResMgmt.QueryInstantiatedChaincodes("orgchannel", resmgmt.WithRetry(retry.DefaultResMgmtOpts)) - if err != nil { - t.Fatalf("QueryInstantiatedChaincodes return error: %v", err) - } + chaincodeQueryResponse, err := org1ResMgmt.QueryInstantiatedChaincodes(channelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts)) + require.NoError(t, err, "QueryInstantiatedChaincodes return error") + found := false for _, chaincode := range chaincodeQueryResponse.Chaincodes { - if chaincode.Name == "exampleCC" { + if chaincode.Name == ccName { found = true } } - if !found { - t.Fatalf("QueryInstantiatedChaincodes failed to find instantiated exampleCC chaincode") - } + require.True(t, found, "QueryInstantiatedChaincodes failed to find instantiated '%s' chaincode", ccName) } -func testWithOrg2(t *testing.T, expectedValue int) int { +func instantiateCC(t *testing.T, resMgmt *resmgmt.Client, targets []string, ccName string) { + // Set up chaincode policy to 'any of two msps' + ccPolicy := cauthdsl.SignedByAnyMember([]string{"Org1MSP", "Org2MSP"}) + // Org1 resource manager will instantiate 'example_cc' on 'orgchannel' + instantiateResp, err := resMgmt.InstantiateCC( + channelID, + resmgmt.InstantiateCCRequest{ + Name: ccName, + Path: "github.com/example_cc", + Version: "0", + Args: integration.ExampleCCInitArgs(), + Policy: ccPolicy, + }, + resmgmt.WithRetry(retry.DefaultResMgmtOpts), + resmgmt.WithTargetEndpoints(targets...), + ) + + require.Nil(t, err, "error should be nil for instantiateCC") + require.NotEmpty(t, instantiateResp, "transaction response should be populated for instantateCC") +} +func testWithOrg2(t *testing.T, expectedValue int, ccName string) int { // Specify user that will be used by dynamic selection service (to retrieve chanincode policy information) // This user has to have privileges to query lscc for chaincode data - mychannelUser := selection.ChannelUser{ChannelID: "orgchannel", Username: "User1", OrgName: "Org1"} + mychannelUser := selection.ChannelUser{ChannelID: channelID, Username: "User1", OrgName: "Org1"} // Create SDK setup for channel client with dynamic selection sdk, err := fabsdk.New(integration.ConfigBackend, @@ -444,7 +495,7 @@ func testWithOrg2(t *testing.T, expectedValue int) int { } // Org2 user moves funds (dynamic selection will inspect chaincode policy to determine endorsers) - _, err = chClientOrg2User.Execute(channel.Request{ChaincodeID: "exampleCC", Fcn: "invoke", Args: integration.ExampleCCTxArgs()}, + _, err = chClientOrg2User.Execute(channel.Request{ChaincodeID: ccName, Fcn: "invoke", Args: integration.ExampleCCTxArgs()}, channel.WithRetry(retry.DefaultChannelOpts)) if err != nil { t.Fatalf("Failed to move funds: %s", err) @@ -454,7 +505,7 @@ func testWithOrg2(t *testing.T, expectedValue int) int { return expectedValue } -func verifyWithOrg1(t *testing.T, sdk *fabsdk.FabricSDK, expectedValue int) { +func verifyWithOrg1(t *testing.T, sdk *fabsdk.FabricSDK, expectedValue int, ccName string) { //prepare context org1ChannelClientContext := sdk.ChannelContext(channelID, fabsdk.WithUser(org1User), fabsdk.WithOrg(org1)) @@ -464,16 +515,15 @@ func verifyWithOrg1(t *testing.T, sdk *fabsdk.FabricSDK, expectedValue int) { t.Fatalf("Failed to create new channel client for Org1 user: %s", err) } - verifyValue(t, chClientOrg1User, expectedValue) + verifyValue(t, chClientOrg1User, expectedValue, ccName) } -func verifyValue(t *testing.T, chClient *channel.Client, expected int) { - +func verifyValue(t *testing.T, chClient *channel.Client, expected int, ccName string) { // Assert that funds have changed value on org1 peer var valueInt int for i := 0; i < pollRetries; i++ { // Query final value on org1 peer - response, err := chClient.Query(channel.Request{ChaincodeID: "exampleCC", Fcn: "invoke", Args: integration.ExampleCCQueryArgs()}, channel.WithTargets(orgTestPeer0), + response, err := chClient.Query(channel.Request{ChaincodeID: ccName, Fcn: "invoke", Args: integration.ExampleCCQueryArgs()}, channel.WithTargets(orgTestPeer0), channel.WithRetry(retry.DefaultChannelOpts)) if err != nil { t.Fatalf("Failed to query funds after transaction: %s", err)