package core import ( "math/big" "testing" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/private" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) var ( incrementByOne = common.BytesToEncryptedPayloadHash([]byte("incContract")) ) func buildCacheProviderTestChain(n int, config *params.ChainConfig, quorumChainConfig *QuorumChainConfig) ([]*types.Block, map[common.Hash]*types.Block, *BlockChain) { testdb := rawdb.NewMemoryDatabase() genesis := GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000)) // The generated chain deploys one Accumulator contracts which is incremented every block blocks, _ := GenerateChain(config, genesis, ethash.NewFaker(), testdb, n, func(i int, block *BlockGen) { block.SetCoinbase(common.Address{0}) signer := types.QuorumPrivateTxSigner{} var tx *types.Transaction var err error if i == 0 { tx, err = types.SignTx(types.NewContractCreation(block.TxNonce(testAddress), big.NewInt(0), testGas, nil, deployContract.Bytes()), signer, testKey) } else { tx, err = types.SignTx(types.NewTransaction(block.TxNonce(testAddress), Contract1AddressAfterDeployment, big.NewInt(0), testGas, nil, incrementByOne.Bytes()), signer, testKey) } if err != nil { panic(err) } block.AddTx(tx) }) hashes := make([]common.Hash, n+1) hashes[len(hashes)-1] = genesis.Hash() blockm := make(map[common.Hash]*types.Block, n+1) blockm[genesis.Hash()] = genesis for i, b := range blocks { hashes[len(hashes)-i-2] = b.Hash() blockm[b.Hash()] = b } // recreate the DB so that we don't have the public state written already by the block generation logic testdb = rawdb.NewMemoryDatabase() genesis = GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000)) // disable snapshots testingCacheConfig := &CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, // TODO - figure out why is the snapshot causing panics when enabled during the test SnapshotLimit: 0, SnapshotWait: true, } blockchain, err := NewBlockChain(testdb, testingCacheConfig, config, ethash.NewFaker(), vm.Config{}, nil, nil, quorumChainConfig) if err != nil { return nil, nil, nil } return blocks, blockm, blockchain } func buildMockPTM(mockCtrl *gomock.Controller) private.PrivateTransactionManager { mockptm := private.NewMockPrivateTransactionManager(mockCtrl) deployAccumulatorContractConstructor, _ := AccumulatorParsedABI.Pack("", big.NewInt(1)) deployAccumulatorContract := append(common.FromHex(AccumulatorBin), deployAccumulatorContractConstructor...) incrementAccumulatorContract, _ := AccumulatorParsedABI.Pack("inc", big.NewInt(1)) mockptm.EXPECT().Receive(deployContract).Return("", []string{"AAA"}, deployAccumulatorContract, nil, nil).AnyTimes() mockptm.EXPECT().Receive(incrementByOne).Return("", []string{"AAA"}, incrementAccumulatorContract, nil, nil).AnyTimes() mockptm.EXPECT().Receive(common.EncryptedPayloadHash{}).Return("", []string{}, common.EncryptedPayloadHash{}.Bytes(), nil, nil).AnyTimes() return mockptm } func TestSegregatedCacheProvider(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockptm := buildMockPTM(mockCtrl) saved := private.P defer func() { private.P = saved }() private.P = mockptm blocks, _, blockchain := buildCacheProviderTestChain(11, params.QuorumTestChainConfig, nil) count, err := blockchain.InsertChain(blocks) assert.Nil(t, err) assert.Equal(t, len(blocks), count) lastBlock := blocks[len(blocks)-1] statedbLast, privateStateRepoLast, _ := blockchain.StateAt(lastBlock.Root()) assert.Equal(t, uint64(len(blocks)), statedbLast.GetNonce(testAddress)) privateState, _ := privateStateRepoLast.DefaultState() accPrivateStateStateLast := privateState.GetState(Contract1AddressAfterDeployment, common.Hash{}) assert.Equal(t, common.BytesToHash(big.NewInt(int64(len(blocks))).Bytes()), accPrivateStateStateLast) // retrieve the state at block height 1 block1 := blocks[1] statedbB1, privateStateRepoB1, _ := blockchain.StateAt(block1.Root()) assert.Equal(t, uint64(2), statedbB1.GetNonce(testAddress)) privateState, _ = privateStateRepoB1.DefaultState() privateStateRoot := privateState.IntermediateRoot(false) accPrivateStateStateB1 := privateState.GetState(Contract1AddressAfterDeployment, common.Hash{}) assert.Equal(t, common.BytesToHash([]byte{2}), accPrivateStateStateB1) // check that the private state root has already been written to the DB contains, err := blockchain.db.Has(privateStateRoot.Bytes()) assert.Nil(t, err) assert.True(t, contains) } func TestUnifiedCacheProvider(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockptm := buildMockPTM(mockCtrl) saved := private.P defer func() { private.P = saved }() private.P = mockptm blocks, _, blockchain := buildCacheProviderTestChain(130, params.QuorumTestChainConfig, &QuorumChainConfig{privateTrieCacheEnabled: true}) count, err := blockchain.InsertChain(blocks[:129]) assert.Nil(t, err) assert.Equal(t, 129, count) lastBlock := blocks[128] statedb, privateStateRepo, _ := blockchain.StateAt(lastBlock.Root()) assert.Equal(t, uint64(129), statedb.GetNonce(testAddress)) privateState, _ := privateStateRepo.DefaultState() accPrivateState := privateState.GetState(Contract1AddressAfterDeployment, common.Hash{}) assert.Equal(t, common.BytesToHash(big.NewInt(129).Bytes()), accPrivateState) // The following is an attempt to explain the process by which the trie nodes corresponding to privateState are being // garbage collected due to the TriesInMemory limit implemented in the blockchain // Expected state structure (block 2 - index 1 in the blocks array) // Public state just contains the testAddress(testKey) with nonce 2 and has PUB(BL2) root hash // privateState has C1(2) privateState(BL2) as root hash // the public state root PUB(BL2) references the private state root privateState(BL2) // Considering the above we can establish that // privateState(BL2) is referenced once by the public state root PUB(BL2) at block height 2 // retrieve the state at block height 2 block1 := blocks[1] statedbB1, privateStateRepoB1, _ := blockchain.StateAt(block1.Root()) assert.Equal(t, uint64(2), statedbB1.GetNonce(testAddress)) privateState, _ = privateStateRepoB1.DefaultState() privateStateRoot := privateState.IntermediateRoot(false) accPrivateStateB1 := privateState.GetState(Contract1AddressAfterDeployment, common.Hash{}) assert.Equal(t, common.BytesToHash([]byte{2}), accPrivateStateB1) // check that the roots have NOT been written to the underlying DB contains, err := blockchain.db.Has(block1.Root().Bytes()) assert.Nil(t, err) assert.False(t, contains) contains, err = blockchain.db.Has(privateStateRoot.Bytes()) assert.Nil(t, err) assert.False(t, contains) // check that the roots are available in the cache data, err := blockchain.stateCache.TrieDB().Node(block1.Root()) assert.Nil(t, err) assert.True(t, len(data) > 0) data, err = blockchain.stateCache.TrieDB().Node(privateStateRoot) assert.Nil(t, err) assert.True(t, len(data) > 0) // Process block 130 and reassess the underlying DB and the cache // When block 130 is processed the "chosen" in blockchain becomes 2 (130 - TriesInMemory) and the public root hash // PUB(BL2) is being de-referenced (reference count is reduced by 1) and the reference count (parents) becomes 0 // As a result private state root privateState(BL2) is de-referenced and the reference count becomes 0 // All nodes with reference counts (parents) equal to 0 are being garbage collected (removed from the cache) count, err = blockchain.InsertChain(blocks[129:]) assert.Nil(t, err) assert.Equal(t, 1, count) // check that the roots have NOT been written to the underlying DB contains, err = blockchain.db.Has(block1.Root().Bytes()) assert.Nil(t, err) assert.False(t, contains) contains, err = blockchain.db.Has(privateStateRoot.Bytes()) assert.Nil(t, err) assert.False(t, contains) // check that the roots have been garbage collected (removed) from the cache (other intermediate trie nodes may have // been eliminated from the cache as well) data, err = blockchain.stateCache.TrieDB().Node(block1.Root()) assert.Error(t, err, "not found") assert.Nil(t, data) data, err = blockchain.stateCache.TrieDB().Node(privateStateRoot) assert.Error(t, err, "not found") assert.Nil(t, data) }