diff --git a/core/ledger/kvledger/tests/client.go b/core/ledger/kvledger/tests/client.go index a8651d75035..acbf47ba450 100644 --- a/core/ledger/kvledger/tests/client.go +++ b/core/ledger/kvledger/tests/client.go @@ -73,7 +73,7 @@ func (c *client) addPostOrderTx(txid string, customTxType common.HeaderType) *tx } // simulateDeployTx mimics a transction that deploys a chaincode. This in turn calls the function 'simulateDataTx' -// with supplying the simulation logic that mimics the inoke funciton of 'lscc' for the ledger tests +// with supplying the simulation logic that mimics the invoke function of 'lscc' for the ledger tests func (c *client) simulateDeployTx(ccName string, collConfs []*collConf) *txAndPvtdata { ccData := &ccprovider.ChaincodeData{Name: ccName} ccDataBytes, err := proto.Marshal(ccData) @@ -105,6 +105,22 @@ func (c *client) causeMissingPvtData(txIndex uint64) { c.simulatedTrans[txIndex].Pvtws = nil } +func (c *client) retrieveCommittedBlocksAndPvtdata(startNum, endNum uint64) []*ledger.BlockAndPvtData { + data := []*ledger.BlockAndPvtData{} + for i := startNum; i <= endNum; i++ { + d, err := c.lgr.GetPvtDataAndBlockByNum(i, nil) + c.assert.NoError(err) + data = append(data, d) + } + return data +} + +func (c *client) currentHeight() uint64 { + bcInfo, err := c.lgr.GetBlockchainInfo() + c.assert.NoError(err) + return bcInfo.Height +} + /////////////////////// simulator wrapper functions /////////////////////// type simulator struct { ledger.TxSimulator diff --git a/core/ledger/kvledger/tests/sample_data_helper.go b/core/ledger/kvledger/tests/sample_data_helper.go index 202029a7dd8..8aaedf3418e 100644 --- a/core/ledger/kvledger/tests/sample_data_helper.go +++ b/core/ledger/kvledger/tests/sample_data_helper.go @@ -205,9 +205,11 @@ func (d *sampleDataHelper) verifyBlockAndPvtdataUsingSubmittedData(h *testhelper h.verifyBlockAndPvtData(uint64(8), nil, func(r *retrievedBlockAndPvtdata) { r.sameBlockHeaderAndData(submittedBlk.Block) r.containsValidationCode(0, protopeer.TxValidationCode_MVCC_READ_CONFLICT) + r.containsCommitHash() }) } } + h.verifyCommitHashExists() } func (d *sampleDataHelper) verifyGetTransactionByIDUsingSubmittedData(h *testhelper) { diff --git a/core/ledger/kvledger/tests/test_helper.go b/core/ledger/kvledger/tests/test_helper.go index 73449b98d76..57d41c87428 100644 --- a/core/ledger/kvledger/tests/test_helper.go +++ b/core/ledger/kvledger/tests/test_helper.go @@ -14,7 +14,7 @@ import ( ) // testhelper embeds (1) a client, (2) a committer and (3) a verifier, all three operate on -// a ledger instance and add helping/resuable functionality on top of ledger apis that helps +// a ledger instance and add helping/reusable functionality on top of ledger apis that helps // in avoiding the repeation in the actual tests code. // the 'client' adds value to the simulation relation apis, the 'committer' helps in cutting the // next block and committing the block, and finally, the verifier helps in veryfying that the diff --git a/core/ledger/kvledger/tests/testdata/v11/sample_ledgers_with_commit_hashes/ledgersData.zip b/core/ledger/kvledger/tests/testdata/v11/sample_ledgers_with_commit_hashes/ledgersData.zip new file mode 100644 index 00000000000..b718163c3ea Binary files /dev/null and b/core/ledger/kvledger/tests/testdata/v11/sample_ledgers_with_commit_hashes/ledgersData.zip differ diff --git a/core/ledger/kvledger/tests/v1x_test.go b/core/ledger/kvledger/tests/v1x_test.go index 32e0f649486..8c32057a041 100644 --- a/core/ledger/kvledger/tests/v1x_test.go +++ b/core/ledger/kvledger/tests/v1x_test.go @@ -14,15 +14,21 @@ import ( "testing" "time" + "github.com/hyperledger/fabric-protos-go/common" protopeer "github.com/hyperledger/fabric-protos-go/peer" "github.com/hyperledger/fabric/common/ledger/testutil" "github.com/hyperledger/fabric/core/ledger" "github.com/hyperledger/fabric/core/ledger/kvledger" "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb/statecouchdb" "github.com/hyperledger/fabric/core/ledger/mock" + "github.com/hyperledger/fabric/protoutil" "github.com/stretchr/testify/require" ) +// Test data used in the tests in this file was generated by v1.1 code https://gerrit.hyperledger.org/r/#/c/22749/6/core/ledger/kvledger/tests/v11_generate_test.go@22 +// Folder, "testdata/v11/sample_ledgers" contains the data that was generated before commit hash feature was added. +// Folder, "testdata/v11/sample_ledgers_with_commit_hashes" contains the data that was generated after commit hash feature was added. + // TestV11 tests that a ledgersData folder created by v1.1 can be used with future releases after upgrading dbs. // The test data was generated by v1.1 code https://github.com/hyperledger/fabric/blob/release-1.1/core/ledger/kvledger/tests/v11_generate_test.go#L22 func TestV11(t *testing.T) { @@ -49,6 +55,166 @@ func TestV11(t *testing.T) { h1, h2 = env.newTestHelperOpenLgr("ledger1", t), env.newTestHelperOpenLgr("ledger2", t) dataHelper.verify(h1) dataHelper.verify(h2) + + h1.verifyCommitHashNotExists() + h2.verifyCommitHashNotExists() + h1.simulateDataTx("txid1_with_new_binary", func(s *simulator) { + s.setState("cc1", "new_key", "new_value") + }) + + // add a new block and the new block should not contain a commit hash + // because the previously committed block from 1.1 code did not contain commit hash + h1.cutBlockAndCommitLegacy() + h1.verifyCommitHashNotExists() +} + +func TestV11CommitHashes(t *testing.T) { + testCases := []struct { + description string + v11SampleDataPath string + preResetCommitHashExists bool + resetFunc func(h *testhelper, ledgerFSRoot string) + postResetCommitHashExists bool + }{ + { + "Reset (no existing CommitHash)", + "testdata/v11/sample_ledgers/ledgersData.zip", + false, + func(h *testhelper, ledgerFSRoot string) { + require.NoError(t, kvledger.ResetAllKVLedgers(ledgerFSRoot)) + }, + true, + }, + + { + "Rollback to genesis block (no existing CommitHash)", + "testdata/v11/sample_ledgers/ledgersData.zip", + false, + func(h *testhelper, ledgerFSRoot string) { + require.NoError(t, kvledger.RollbackKVLedger(ledgerFSRoot, h.lgrid, 0)) + }, + true, + }, + + { + "Rollback to block other than genesis block (no existing CommitHash)", + "testdata/v11/sample_ledgers/ledgersData.zip", + false, + func(h *testhelper, ledgerFSRoot string) { + require.NoError(t, kvledger.RollbackKVLedger(ledgerFSRoot, h.lgrid, h.currentHeight()/2+1)) + }, + false, + }, + + { + "Reset (existing CommitHash)", + "testdata/v11/sample_ledgers_with_commit_hashes/ledgersData.zip", + true, + func(h *testhelper, ledgerFSRoot string) { + require.NoError(t, kvledger.ResetAllKVLedgers(ledgerFSRoot)) + }, + true, + }, + + { + "Rollback to genesis block (existing CommitHash)", + "testdata/v11/sample_ledgers_with_commit_hashes/ledgersData.zip", + true, + func(h *testhelper, ledgerFSRoot string) { + require.NoError(t, kvledger.RollbackKVLedger(ledgerFSRoot, h.lgrid, 0)) + }, + true, + }, + + { + "Rollback to block other than genesis block (existing CommitHash)", + "testdata/v11/sample_ledgers_with_commit_hashes/ledgersData.zip", + true, + func(h *testhelper, ledgerFSRoot string) { + require.NoError(t, kvledger.RollbackKVLedger(ledgerFSRoot, h.lgrid, h.currentHeight()/2+1)) + }, + true, + }, + } + + for _, testCase := range testCases { + t.Run( + testCase.description, + func(t *testing.T) { + testV11CommitHashes( + t, + testCase.v11SampleDataPath, + testCase.preResetCommitHashExists, + testCase.resetFunc, + testCase.postResetCommitHashExists, + ) + }) + } +} + +func testV11CommitHashes(t *testing.T, + v11DataPath string, + preResetCommitHashExists bool, + resetFunc func(*testhelper, string), + postResetCommitHashExists bool, +) { + env := newEnv(t) + defer env.cleanup() + + ledgerFSRoot := env.initializer.Config.RootFSPath + // pass false so that 'ledgersData' directory will not be created when unzipped to ledgerFSRoot + require.NoError(t, testutil.Unzip(v11DataPath, ledgerFSRoot, false)) + + require.NoError(t, kvledger.UpgradeDBs(env.initializer.Config)) + rebuildable := rebuildableStatedb | rebuildableBookkeeper | rebuildableConfigHistory | rebuildableHistoryDB | rebuildableBlockIndex + env.verifyRebuilableDoesNotExist(rebuildable) + + env.initLedgerMgmt() + h := env.newTestHelperOpenLgr("ledger1", t) + blocksAndPvtData := h.retrieveCommittedBlocksAndPvtdata(0, h.currentHeight()-1) + if preResetCommitHashExists { + h.verifyCommitHashExists() + } else { + h.verifyCommitHashNotExists() + } + + env.closeLedgerMgmt() + resetFunc(h, ledgerFSRoot) + env.initLedgerMgmt() + + h = env.newTestHelperOpenLgr("ledger1", t) + for i := int(h.currentHeight()); i < len(blocksAndPvtData); i++ { + d := blocksAndPvtData[i] + // add metadata slot for commit hash, as this would have be missing in the blocks from 1.1 prior to this feature + for len(d.Block.Metadata.Metadata) < int(common.BlockMetadataIndex_COMMIT_HASH)+1 { + d.Block.Metadata.Metadata = append(d.Block.Metadata.Metadata, []byte{}) + } + // set previous block hash, as this is not present in the test blocks from 1.1 + d.Block.Header.PreviousHash = protoutil.BlockHeaderHash(blocksAndPvtData[i-1].Block.Header) + require.NoError(t, h.lgr.CommitLegacy(d, &ledger.CommitOptions{FetchPvtDataFromLedger: true})) + } + + if postResetCommitHashExists { + h.verifyCommitHashExists() + } else { + h.verifyCommitHashNotExists() + } + + bcInfo, err := h.lgr.GetBlockchainInfo() + require.NoError(t, err) + h.committer.blkgen.lastNum = bcInfo.Height - 1 + h.committer.blkgen.lastHash = bcInfo.CurrentBlockHash + + h.simulateDataTx("txid1_with_new_binary", func(s *simulator) { + s.setState("cc1", "new_key", "new_value") + }) + h.cutBlockAndCommitLegacy() + + if postResetCommitHashExists { + h.verifyCommitHashExists() + } else { + h.verifyCommitHashNotExists() + } } // TestV13WithStateCouchdb tests that a ledgersData folder and couchdb data created by v1.3 can be read by latest fabric version after upgrading dbs. diff --git a/core/ledger/kvledger/tests/verifier.go b/core/ledger/kvledger/tests/verifier.go index 4739d1d3979..cd1adffb5e0 100644 --- a/core/ledger/kvledger/tests/verifier.go +++ b/core/ledger/kvledger/tests/verifier.go @@ -135,6 +135,24 @@ func (v *verifier) verifyHistory(ns, key string, expectedVals []string) { v.assert.Equal(expectedVals, historyValues) } +func (v *verifier) verifyCommitHashExists() { + bcInfo, err := v.lgr.GetBlockchainInfo() + v.assert.NoError(err) + b, err := v.lgr.GetPvtDataAndBlockByNum(bcInfo.Height-1, nil) + v.assert.NoError(err) + r := &retrievedBlockAndPvtdata{BlockAndPvtData: b, assert: v.assert} + r.containsCommitHash() +} + +func (v *verifier) verifyCommitHashNotExists() { + bcInfo, err := v.lgr.GetBlockchainInfo() + v.assert.NoError(err) + b, err := v.lgr.GetPvtDataAndBlockByNum(bcInfo.Height-1, nil) + v.assert.NoError(err) + r := &retrievedBlockAndPvtdata{BlockAndPvtData: b, assert: v.assert} + r.notContainCommitHash() +} + //////////// structs used by verifier ////////////////////////////////////////////////////////////// type expectedCollConfInfo struct { committingBlockNum uint64 @@ -200,18 +218,13 @@ func (r *retrievedBlockAndPvtdata) sameMetadata(expectedBlock *common.Block) { expectedMetadata := expectedBlock.Metadata.Metadata r.assert.Equal(len(expectedMetadata), len(retrievedMetadata)) for i := 0; i < len(expectedMetadata); i++ { + if i == int(common.BlockMetadataIndex_COMMIT_HASH) { + // in order to compare the exact hash value, we need to duplicate the + // production code in this test too, so skipping this match + continue + } if len(expectedMetadata[i])+len(retrievedMetadata[i]) != 0 { - if i != int(common.BlockMetadataIndex_COMMIT_HASH) { - r.assert.Equal(expectedMetadata[i], retrievedMetadata[i]) - } else { - // in order to compare the exact hash value, we need to duplicate the - // production code in this test too (which is not recommended). - commitHash := &common.Metadata{} - err := proto.Unmarshal(retrievedMetadata[common.BlockMetadataIndex_COMMIT_HASH], - commitHash) - r.assert.NoError(err) - r.assert.Equal(len(commitHash.Value), 32) - } + r.assert.Equal(expectedMetadata[i], retrievedMetadata[i]) } } } @@ -230,3 +243,20 @@ func (r *retrievedBlockAndPvtdata) samePvtdata(expectedPvtdata map[uint64]*ledge r.assert.True(proto.Equal(pvtData.WriteSet, actualPvtData.WriteSet)) } } + +func (r *retrievedBlockAndPvtdata) containsCommitHash() { + commitHash := &common.Metadata{} + spew.Dump(r.Block.Metadata) + err := proto.Unmarshal( + r.Block.Metadata.Metadata[common.BlockMetadataIndex_COMMIT_HASH], + commitHash, + ) + r.assert.NoError(err) + r.assert.Equal(len(commitHash.Value), 32) +} + +func (r *retrievedBlockAndPvtdata) notContainCommitHash() { + exists := len(r.Block.Metadata.Metadata) >= int(common.BlockMetadataIndex_COMMIT_HASH)+1 && + len(r.Block.Metadata.Metadata[common.BlockMetadataIndex_COMMIT_HASH]) > 0 + r.assert.False(exists) +}