From 3d8825e9d36aa5d90bbff9216eaa6ae18da4f2c2 Mon Sep 17 00:00:00 2001 From: Wenjian Qiao Date: Thu, 14 Nov 2019 11:28:45 -0500 Subject: [PATCH] Add rebuild test with real ledger data - Add an IT test to generate ledger data - Test rebuild command with the generated ledger data and verify statedb, blocks, pvtdata, historydb, configHistorydb Change-Id: I9f469d95f7120b402545b027cc5fed9b188afb95 Signed-off-by: Wenjian Qiao --- core/ledger/kvledger/tests/env.go | 57 ++++++++ .../v20/sample_ledgers/ledgersData.zip | Bin 0 -> 57951 bytes core/ledger/kvledger/tests/util.go | 13 +- core/ledger/kvledger/tests/v20_test.go | 131 +++++++++++++++++ integration/ledger/ledger_generate_test.go | 136 ++++++++++++++++++ .../collections_config2.json | 18 +++ 6 files changed, 354 insertions(+), 1 deletion(-) create mode 100644 core/ledger/kvledger/tests/testdata/v20/sample_ledgers/ledgersData.zip create mode 100644 core/ledger/kvledger/tests/v20_test.go create mode 100644 integration/ledger/ledger_generate_test.go create mode 100644 integration/ledger/testdata/collection_configs/collections_config2.json diff --git a/core/ledger/kvledger/tests/env.go b/core/ledger/kvledger/tests/env.go index 4484d0a722b..11280c265de 100644 --- a/core/ledger/kvledger/tests/env.go +++ b/core/ledger/kvledger/tests/env.go @@ -18,6 +18,7 @@ import ( "github.com/hyperledger/fabric/common/ledger/blkstorage/fsblkstorage" "github.com/hyperledger/fabric/common/ledger/util" "github.com/hyperledger/fabric/common/metrics/disabled" + "github.com/hyperledger/fabric/core/chaincode/lifecycle" "github.com/hyperledger/fabric/core/common/privdata" "github.com/hyperledger/fabric/core/container/externalbuilder" "github.com/hyperledger/fabric/core/ledger" @@ -272,3 +273,59 @@ func createSelfSignedData(cryptoProvider bccsp.BCCSP) protoutil.SignedData { Identity: peerIdentity, } } + +// deployedCCInfoProviderWrapper is a wrapper type that overrides ChaincodeImplicitCollections +type deployedCCInfoProviderWrapper struct { + *lifecycle.ValidatorCommitter + orgMSPIDs []string +} + +// AllCollectionsConfigPkg overrides the same method in lifecycle.AllCollectionsConfigPkg. +// It is basically a copy of lifecycle.AllCollectionsConfigPkg and invokes ImplicitCollections in the wrapper. +// This method is called when the unit test code gets private data code path. +func (dc *deployedCCInfoProviderWrapper) AllCollectionsConfigPkg(channelName, chaincodeName string, qe ledger.SimpleQueryExecutor) (*common.CollectionConfigPackage, error) { + chaincodeInfo, err := dc.ChaincodeInfo(channelName, chaincodeName, qe) + if err != nil { + return nil, err + } + explicitCollectionConfigPkg := chaincodeInfo.ExplicitCollectionConfigPkg + + implicitCollections, _ := dc.ImplicitCollections(channelName, "", nil) + + var combinedColls []*common.CollectionConfig + if explicitCollectionConfigPkg != nil { + combinedColls = append(combinedColls, explicitCollectionConfigPkg.Config...) + } + for _, implicitColl := range implicitCollections { + c := &common.CollectionConfig{} + c.Payload = &common.CollectionConfig_StaticCollectionConfig{StaticCollectionConfig: implicitColl} + combinedColls = append(combinedColls, c) + } + return &common.CollectionConfigPackage{ + Config: combinedColls, + }, nil +} + +// ImplicitCollections overrides the same method in lifecycle.ValidatorCommitter. +// It constructs static collection config using known mspids from the sample ledger. +// This method is called when the unit test code gets collection configuration. +func (dc *deployedCCInfoProviderWrapper) ImplicitCollections(channelName, chaincodeName string, qe ledger.SimpleQueryExecutor) ([]*common.StaticCollectionConfig, error) { + collConfigs := make([]*common.StaticCollectionConfig, 0, len(dc.orgMSPIDs)) + for _, mspID := range dc.orgMSPIDs { + collConfigs = append(collConfigs, lifecycle.GenerateImplicitCollectionForOrg(mspID)) + } + return collConfigs, nil +} + +func createDeployedCCInfoProvider(orgMSPIDs []string) ledger.DeployedChaincodeInfoProvider { + deployedCCInfoProvider := &lifecycle.ValidatorCommitter{ + Resources: &lifecycle.Resources{ + Serializer: &lifecycle.Serializer{}, + }, + LegacyDeployedCCInfoProvider: &lscc.DeployedCCInfoProvider{}, + } + return &deployedCCInfoProviderWrapper{ + ValidatorCommitter: deployedCCInfoProvider, + orgMSPIDs: orgMSPIDs, + } +} diff --git a/core/ledger/kvledger/tests/testdata/v20/sample_ledgers/ledgersData.zip b/core/ledger/kvledger/tests/testdata/v20/sample_ledgers/ledgersData.zip new file mode 100644 index 0000000000000000000000000000000000000000..0164236164f80a0adf9197187386402a9a41ca13 GIT binary patch literal 57951 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 literal 0 HcmV?d00001 diff --git a/core/ledger/kvledger/tests/util.go b/core/ledger/kvledger/tests/util.go index a09cc1b89e0..f9f11b4f51b 100644 --- a/core/ledger/kvledger/tests/util.go +++ b/core/ledger/kvledger/tests/util.go @@ -13,6 +13,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/hyperledger/fabric-protos-go/common" "github.com/hyperledger/fabric-protos-go/ledger/rwset" + "github.com/hyperledger/fabric-protos-go/msp" protopeer "github.com/hyperledger/fabric-protos-go/peer" "github.com/hyperledger/fabric/common/cauthdsl" configtxtest "github.com/hyperledger/fabric/common/configtx/test" @@ -88,7 +89,17 @@ func convertFromMemberOrgsPolicy(policy *common.CollectionPolicyConfig) []string ids := policy.GetSignaturePolicy().Identities var members []string for _, id := range ids { - members = append(members, string(id.Principal)) + role := &msp.MSPRole{} + err := proto.Unmarshal(id.Principal, role) + if err == nil { + // This is for sample ledger generated by fabric (e.g., integration test), + // where id.Principal was properly marshalled during sample ledger generation. + members = append(members, role.MspIdentifier) + } else { + // This is for sample ledger generated by sampleDataHelper.populateLedger, + // where id.Principal was a []byte cast from a string (not a marshalled msp.MSPRole) + members = append(members, string(id.Principal)) + } } return members } diff --git a/core/ledger/kvledger/tests/v20_test.go b/core/ledger/kvledger/tests/v20_test.go new file mode 100644 index 00000000000..c2adfeade55 --- /dev/null +++ b/core/ledger/kvledger/tests/v20_test.go @@ -0,0 +1,131 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package tests + +import ( + "fmt" + "testing" + + "github.com/hyperledger/fabric/common/ledger/testutil" + "github.com/hyperledger/fabric/core/chaincode/lifecycle" + "github.com/hyperledger/fabric/core/ledger/kvledger" + "github.com/stretchr/testify/require" +) + +// TestV20SampleLedger tests rebuild function with sample v2.0 ledger data generated by integration/ledger/ledger_generate_test.go +func TestV20SampleLedger(t *testing.T) { + env := newEnv(t) + defer env.cleanup() + + dataHelper := &v20SampleDataHelper{sampleDataVersion: "v2.0", t: t} + env.initializer.DeployedChaincodeInfoProvider = createDeployedCCInfoProvider(dataHelper.mspIDsInChannelConfig()) + ledgerFSRoot := env.initializer.Config.RootFSPath + require.NoError(t, testutil.Unzip("testdata/v20/sample_ledgers/ledgersData.zip", ledgerFSRoot, false)) + + env.initLedgerMgmt() + + h1 := env.newTestHelperOpenLgr("testchannel", t) + dataHelper.verify(h1) + + // rebuild and verify again + env.closeLedgerMgmt() + kvledger.RebuildDBs(env.getLedgerRootPath()) + env.initLedgerMgmt() + h1 = env.newTestHelperOpenLgr("testchannel", t) + dataHelper.verify(h1) +} + +// The generated ledger has the following blocks: +// block 0: genesis +// block 1 to 4: network setup +// block 5 to 8: marblesp chaincode instantiation +// block 9 to 12: marbles chancode instantiation +// block 13: marblesp chaincode invocation (new marble1) +// block 14 to 17: upgrade marblesp chaincode with a new collection config +// block 18 to 19: marbles chaincode invocation (new marble100 and transfer) +type v20SampleDataHelper struct { + sampleDataVersion string + t *testing.T +} + +func (d *v20SampleDataHelper) verify(h *testhelper) { + d.verifyState(h) + d.verifyBlockAndPvtdata(h) + d.verifyConfigHistory(h) + d.verifyHistory(h) +} + +func (d *v20SampleDataHelper) verifyState(h *testhelper) { + h.verifyPubState("marbles", "marble100", d.marbleValue("marble100", "blue", "jerry", 35)) + h.verifyPvtState("marblesp", "collectionMarbles", "marble1", d.marbleValue("marble1", "blue", "tom", 35)) + h.verifyPvtState("marblesp", "collectionMarblePrivateDetails", "marble1", d.marbleDetail("marble1", 99)) +} + +func (d *v20SampleDataHelper) verifyHistory(h *testhelper) { + expectedHistoryValue1 := []string{ + d.marbleValue("marble100", "blue", "jerry", 35), + d.marbleValue("marble100", "blue", "tom", 35), + } + h.verifyHistory("marbles", "marble100", expectedHistoryValue1) +} + +func (d *v20SampleDataHelper) verifyConfigHistory(h *testhelper) { + // below block 10 should match integration/ledger/testdata/collection_configs/collections_config1.json + h.verifyMostRecentCollectionConfigBelow(10, "marblesp", + &expectedCollConfInfo{8, d.marbleCollConf1("marbelsp")}) + + // below block 18 should match integration/ledger/testdata/collection_configs/collections_config2.json + h.verifyMostRecentCollectionConfigBelow(18, "marblesp", + &expectedCollConfInfo{17, d.marbleCollConf2("marbelsp")}) +} + +func (d *v20SampleDataHelper) verifyBlockAndPvtdata(h *testhelper) { + h.verifyBlockAndPvtData(8, nil, func(r *retrievedBlockAndPvtdata) { + r.hasNumTx(1) + r.hasNoPvtdata() + }) + + h.verifyBlockAndPvtData(13, nil, func(r *retrievedBlockAndPvtdata) { + r.hasNumTx(1) + r.pvtdataShouldContain(0, "marblesp", "collectionMarbles", "marble1", d.marbleValue("marble1", "blue", "tom", 35)) + r.pvtdataShouldContain(0, "marblesp", "collectionMarblePrivateDetails", "marble1", d.marbleDetail("marble1", 99)) + }) +} + +func (d *v20SampleDataHelper) marbleValue(name, color, owner string, size int) string { + return fmt.Sprintf(`{"docType":"marble","name":"%s","color":"%s","size":%d,"owner":"%s"}`, name, color, size, owner) +} + +func (d *v20SampleDataHelper) marbleDetail(name string, price int) string { + return fmt.Sprintf(`{"docType":"marblePrivateDetails","name":"%s","price":%d}`, name, price) +} + +func (d *v20SampleDataHelper) mspIDsInChannelConfig() []string { + return []string{"Org1MSP", "Org2MSP", "Org2MSP"} +} + +// match integration/ledger/testdata/collection_configs/collections_config1.json +func (d *v20SampleDataHelper) marbleCollConf1(ccName string) []*collConf { + collConfigs := make([]*collConf, 0) + collConfigs = append(collConfigs, &collConf{name: "collectionMarbles", btl: 1000000, members: []string{"Org1MSP", "Org2MSP"}}) + collConfigs = append(collConfigs, &collConf{name: "collectionMarblePrivateDetails", btl: 1000000, members: []string{"Org2MSP", "Org3MSP"}}) + for _, mspID := range d.mspIDsInChannelConfig() { + collConfigs = append(collConfigs, &collConf{name: lifecycle.ImplicitCollectionNameForOrg(mspID), btl: 0, members: []string{mspID}}) + } + return collConfigs +} + +// match integration/ledger/testdata/collection_configs/collections_config2.json +func (d *v20SampleDataHelper) marbleCollConf2(ccName string) []*collConf { + collConfigs := make([]*collConf, 0) + collConfigs = append(collConfigs, &collConf{name: "collectionMarbles", btl: 1000000, members: []string{"Org2MSP", "Org3MSP"}}) + collConfigs = append(collConfigs, &collConf{name: "collectionMarblePrivateDetails", btl: 1000000, members: []string{"Org1MSP", "Org2MSP", "Org3MSP"}}) + for _, mspID := range d.mspIDsInChannelConfig() { + collConfigs = append(collConfigs, &collConf{name: lifecycle.ImplicitCollectionNameForOrg(mspID), btl: 0, members: []string{mspID}}) + } + return collConfigs +} diff --git a/integration/ledger/ledger_generate_test.go b/integration/ledger/ledger_generate_test.go new file mode 100644 index 00000000000..6fd3b98854b --- /dev/null +++ b/integration/ledger/ledger_generate_test.go @@ -0,0 +1,136 @@ +/* +Copyright IBM Corp All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ledger + +import ( + "fmt" + "path/filepath" + + . "github.com/onsi/ginkgo" + + "github.com/hyperledger/fabric/integration/nwo" +) + +// This test generate sample ledger data that can be used to verify rebuild ledger function and upgrade function (in a future release). +// It is skipped in general. To generate sample ledger data, comment out the line `xxx` and run this test in isolation. +// It does not delete the network directory so that you can copy the generated data to a different directory for unit tests. +// At the end of test, it prints `setup.testDir is `. Copy the network data under to +// the unit test directory for rebuild tests as needed. +// It generates the following blocks: +// block 0: genesis +// block 1 to 4: network setup +// block 5 to 8: marblesp chaincode instantiation +// block 9 to 12: marbles chancode instantiation +// block 13: marblesp chaincode invocation +// block 14 to 17: upgrade marblesp chaincode with a new collection config +// block 18: marbles chaincode invocation +var _ = Describe("sample ledger generation", func() { + var ( + setup *setup + helper *testHelper + chaincodemp nwo.Chaincode + chaincodem nwo.Chaincode + ) + + BeforeEach(func() { + setup = initThreeOrgsSetup() + nwo.EnableCapabilities(setup.network, setup.channelID, "Application", "V2_0", setup.orderer, setup.peers...) + helper = &testHelper{ + networkHelper: &networkHelper{ + Network: setup.network, + orderer: setup.orderer, + peers: setup.peers, + testDir: setup.testDir, + channelID: setup.channelID, + }, + } + + chaincodemp = nwo.Chaincode{ + Name: "marblesp", + Version: "1.0", + Path: components.Build("github.com/hyperledger/fabric/integration/chaincode/marbles_private/cmd"), + Lang: "binary", + PackageFile: filepath.Join(setup.testDir, "marbles-pvtdata.tar.gz"), + Label: "marbles-private-20", + SignaturePolicy: `OR ('Org1MSP.member','Org2MSP.member', 'Org3MSP.member')`, + CollectionsConfig: filepath.Join("testdata", "collection_configs", "collections_config1.json"), + Sequence: "1", + } + + chaincodem = nwo.Chaincode{ + Name: "marbles", + Version: "0.0", + Path: "github.com/hyperledger/fabric/integration/chaincode/marbles/cmd", + Lang: "golang", + PackageFile: filepath.Join(setup.testDir, "marbles.tar.gz"), + Label: "marbles", + SignaturePolicy: `OR ('Org1MSP.member','Org2MSP.member', 'Org3MSP.member')`, + Sequence: "1", + } + }) + + AfterEach(func() { + setup.terminateAllProcess() + setup.network.Cleanup() + // do not delete testDir and log it so that we can copy the test data to unit tests for verification purpose + fmt.Printf("The test dir is %s. Use peers/org2.peer0/filesystem/ledgersData as the sample ledger for kvledger rebuild tests\n", setup.testDir) + }) + + It("creates marbles", func() { + Skip("Uncomment to generate sample ledger in v2.0 with new lifecycle chaincode deployment") + + org2peer0 := setup.network.Peer("org2", "peer0") + height := helper.getLedgerHeight(org2peer0) + + By(fmt.Sprintf("deploying marblesp chaincode at block height %d", height)) + helper.deployChaincode(chaincodemp) + + height = helper.getLedgerHeight(org2peer0) + By(fmt.Sprintf("deploying marbles chaincode at block height %d", height)) + helper.deployChaincode(chaincodem) + + height = helper.getLedgerHeight(org2peer0) + By(fmt.Sprintf("creating marbles1 with marblesp chaincode at block height %d", height)) + helper.addMarble("marblesp", `{"name":"marble1", "color":"blue", "size":35, "owner":"tom", "price":99}`, org2peer0) + helper.waitUntilEqualLedgerHeight(height + 1) + + By("verifying marble1 exist in collectionMarbles & collectionMarblePrivateDetails in peer0.org2") + helper.assertPresentInCollectionM("marblesp", "marble1", org2peer0) + helper.assertPresentInCollectionMPD("marblesp", "marble1", org2peer0) + + By(fmt.Sprintf("upgrading marblesp chaincode at block height %d", helper.getLedgerHeight(org2peer0))) + chaincodemp.Version = "1.1" + chaincodemp.CollectionsConfig = filepath.Join("testdata", "collection_configs", "collections_config2.json") + chaincodemp.Sequence = "2" + nwo.DeployChaincode(setup.network, setup.channelID, setup.orderer, chaincodemp) + + mhelper := &marblesTestHelper{ + networkHelper: &networkHelper{ + Network: setup.network, + orderer: setup.orderer, + peers: setup.peers, + testDir: setup.testDir, + channelID: setup.channelID, + }, + } + By(fmt.Sprintf("creating marble100 with marbles chaincode at block height %d", helper.getLedgerHeight(org2peer0))) + mhelper.invokeMarblesChaincode("marbles", org2peer0, "initMarble", "marble100", "blue", "35", "tom") + By("transferring marble100 owner") + mhelper.invokeMarblesChaincode("marbles", org2peer0, "transferMarble", "marble100", "jerry") + + By("verifying marble100 new owner after transfer by color") + expectedResult := newMarble("marble100", "blue", 35, "jerry") + mhelper.assertMarbleExists("marbles", org2peer0, expectedResult, "marble100") + + By("getting history for marble100") + expectedHistoryResult := []*marbleHistoryResult{ + {IsDelete: "false", Value: newMarble("marble100", "blue", 35, "jerry")}, + {IsDelete: "false", Value: newMarble("marble100", "blue", 35, "tom")}, + } + mhelper.assertGetHistoryForMarble("marbles", org2peer0, expectedHistoryResult, "marble100") + }) +}) diff --git a/integration/ledger/testdata/collection_configs/collections_config2.json b/integration/ledger/testdata/collection_configs/collections_config2.json new file mode 100644 index 00000000000..f65faf1d867 --- /dev/null +++ b/integration/ledger/testdata/collection_configs/collections_config2.json @@ -0,0 +1,18 @@ +[ + { + "name": "collectionMarbles", + "policy": "OR('Org2MSP.member', 'Org3MSP.member')", + "requiredPeerCount": 1, + "maxPeerCount": 2, + "blockToLive":1000000, + "memberOnlyRead": false + }, + { + "name": "collectionMarblePrivateDetails", + "policy": "OR('Org1MSP.member', 'Org2MSP.member', 'Org3MSP.member')", + "requiredPeerCount": 1, + "maxPeerCount": 2, + "blockToLive":1000000, + "memberOnlyRead": false + } +]