github.com/baptiste-b-pegasys/quorum/v22@v22.4.2/core/blockchain_cache_provider_mps_test.go (about)

     1  package core
     2  
     3  import (
     4  	"encoding/base64"
     5  	"math/big"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/ethereum/go-ethereum/accounts/abi"
    11  	"github.com/ethereum/go-ethereum/common"
    12  	"github.com/ethereum/go-ethereum/consensus/ethash"
    13  	"github.com/ethereum/go-ethereum/core/rawdb"
    14  	"github.com/ethereum/go-ethereum/core/types"
    15  	"github.com/ethereum/go-ethereum/core/vm"
    16  	"github.com/ethereum/go-ethereum/crypto"
    17  	"github.com/ethereum/go-ethereum/params"
    18  	"github.com/ethereum/go-ethereum/private"
    19  	"github.com/ethereum/go-ethereum/private/engine"
    20  	"github.com/golang/mock/gomock"
    21  	"github.com/stretchr/testify/assert"
    22  )
    23  
    24  /*
    25  pragma solidity ^0.5.0;
    26  
    27  contract Accumulator {
    28    uint public storedData;
    29  
    30    event IncEvent(uint value);
    31  
    32    constructor(uint initVal) public{
    33      storedData = initVal;
    34    }
    35  
    36    function inc(uint x) public {
    37      storedData = storedData + x;
    38      emit IncEvent(storedData);
    39    }
    40  
    41    function get() view public returns (uint retVal) {
    42      return storedData;
    43    }
    44  }
    45  */
    46  
    47  const AccumulatorABI = "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"initVal\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"IncEvent\",\"type\":\"event\"},{\"constant\":true,\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"retVal\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"x\",\"type\":\"uint256\"}],\"name\":\"inc\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"storedData\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]"
    48  
    49  var (
    50  	deployContract       = common.BytesToEncryptedPayloadHash([]byte("deployContract"))
    51  	incrementByOnePS1    = common.BytesToEncryptedPayloadHash([]byte("incContractPS1"))
    52  	incrementByOnePS1PS2 = common.BytesToEncryptedPayloadHash([]byte("incContractPS1PS2"))
    53  
    54  	AccumulatorParsedABI, _         = abi.JSON(strings.NewReader(AccumulatorABI))
    55  	AccumulatorBin                  = "0x608060405234801561001057600080fd5b5060405161018a38038061018a8339818101604052602081101561003357600080fd5b8101908080519060200190929190505050806000819055505061012f8061005b6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80632a1afcd91460415780636d4ce63c14605d578063812600df146079575b600080fd5b604760a4565b6040518082815260200191505060405180910390f35b606360aa565b6040518082815260200191505060405180910390f35b60a260048036036020811015608d57600080fd5b810190808035906020019092919050505060b3565b005b60005481565b60008054905090565b80600054016000819055507fc13aa85405f3616d514cfd2316b12181b047ed7f229bce08ce53c671f6f94f986000546040518082815260200191505060405180910390a15056fea265627a7a723158208fb1390ecdc6d669bf1855aed67a225931aee2c14ac2b8f5cd2ac5a8fb3a21af64736f6c63430005110032"
    56  	Contract1AddressAfterDeployment = crypto.CreateAddress(testAddress, 0)
    57  	Contract2AddressAfterDeployment = crypto.CreateAddress(testAddress, 1)
    58  	PS1PG                           = engine.PrivacyGroup{
    59  		Type:           "RESIDENT",
    60  		Name:           "PS1",
    61  		PrivacyGroupId: base64.StdEncoding.EncodeToString([]byte("PS1")),
    62  		Description:    "Resident Group 1",
    63  		From:           "",
    64  		Members:        []string{"AAA", "BBB"},
    65  	}
    66  
    67  	PS2PG = engine.PrivacyGroup{
    68  		Type:           "RESIDENT",
    69  		Name:           "PS2",
    70  		PrivacyGroupId: base64.StdEncoding.EncodeToString([]byte("PS2")),
    71  		Description:    "Resident Group 2",
    72  		From:           "",
    73  		Members:        []string{"CCC", "DDD"},
    74  	}
    75  )
    76  
    77  func buildCacheProviderMPSTestChain(n int, config *params.ChainConfig, quorumChainConfig *QuorumChainConfig) ([]*types.Block, map[common.Hash]*types.Block, *BlockChain) {
    78  	testdb := rawdb.NewMemoryDatabase()
    79  	genesis := GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000))
    80  
    81  	// The generated chain deploys two Accumulator contracts
    82  	// - Accumulator contract 1 is incremented every 1 and 2 blocks for PS1 and PS1&PS2 respectively
    83  	// - Accumulator contract 2 is incremented every block for both PS1 and PS2
    84  	blocks, _ := GenerateChain(config, genesis, ethash.NewFaker(), testdb, n, func(i int, block *BlockGen) {
    85  		block.SetCoinbase(common.Address{0})
    86  
    87  		signer := types.QuorumPrivateTxSigner{}
    88  		var tx *types.Transaction
    89  		var err error
    90  		if i == 0 {
    91  			tx, err = types.SignTx(types.NewContractCreation(block.TxNonce(testAddress), big.NewInt(0), testGas, nil, deployContract.Bytes()), signer, testKey)
    92  			if err != nil {
    93  				panic(err)
    94  			}
    95  			block.AddTx(tx)
    96  			tx, err = types.SignTx(types.NewContractCreation(block.TxNonce(testAddress), big.NewInt(0), testGas, nil, deployContract.Bytes()), signer, testKey)
    97  			if err != nil {
    98  				panic(err)
    99  			}
   100  			block.AddTx(tx)
   101  		} else {
   102  			if i%2 == 1 {
   103  				tx, err = types.SignTx(types.NewTransaction(block.TxNonce(testAddress), Contract1AddressAfterDeployment, big.NewInt(0), testGas, nil, incrementByOnePS1.Bytes()), signer, testKey)
   104  				if err != nil {
   105  					panic(err)
   106  				}
   107  				block.AddTx(tx)
   108  				tx, err = types.SignTx(types.NewTransaction(block.TxNonce(testAddress), Contract2AddressAfterDeployment, big.NewInt(0), testGas, nil, incrementByOnePS1PS2.Bytes()), signer, testKey)
   109  				if err != nil {
   110  					panic(err)
   111  				}
   112  				block.AddTx(tx)
   113  			} else {
   114  				tx, err = types.SignTx(types.NewTransaction(block.TxNonce(testAddress), Contract1AddressAfterDeployment, big.NewInt(0), testGas, nil, incrementByOnePS1PS2.Bytes()), signer, testKey)
   115  				if err != nil {
   116  					panic(err)
   117  				}
   118  				block.AddTx(tx)
   119  				tx, err = types.SignTx(types.NewTransaction(block.TxNonce(testAddress), Contract2AddressAfterDeployment, big.NewInt(0), testGas, nil, incrementByOnePS1PS2.Bytes()), signer, testKey)
   120  				if err != nil {
   121  					panic(err)
   122  				}
   123  				block.AddTx(tx)
   124  			}
   125  		}
   126  
   127  	})
   128  
   129  	hashes := make([]common.Hash, n+1)
   130  	hashes[len(hashes)-1] = genesis.Hash()
   131  	blockm := make(map[common.Hash]*types.Block, n+1)
   132  	blockm[genesis.Hash()] = genesis
   133  	for i, b := range blocks {
   134  		hashes[len(hashes)-i-2] = b.Hash()
   135  		blockm[b.Hash()] = b
   136  	}
   137  
   138  	// recreate the DB so that we don't have the public state written already by the block generation logic
   139  	testdb = rawdb.NewMemoryDatabase()
   140  	genesis = GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000))
   141  
   142  	// disable snapshots
   143  	testingCacheConfig := &CacheConfig{
   144  		TrieCleanLimit: 256,
   145  		TrieDirtyLimit: 256,
   146  		TrieTimeLimit:  5 * time.Minute,
   147  		// TODO - figure out why is the snapshot causing panics when enabled during the test
   148  		SnapshotLimit: 0,
   149  		SnapshotWait:  true,
   150  	}
   151  
   152  	blockchain, err := NewBlockChain(testdb, testingCacheConfig, config, ethash.NewFaker(), vm.Config{}, nil, nil, quorumChainConfig)
   153  	if err != nil {
   154  		return nil, nil, nil
   155  	}
   156  	return blocks, blockm, blockchain
   157  }
   158  
   159  func buildMockMPSPTM(mockCtrl *gomock.Controller) private.PrivateTransactionManager {
   160  	mockptm := private.NewMockPrivateTransactionManager(mockCtrl)
   161  	deployAccumulatorContractConstructor, _ := AccumulatorParsedABI.Pack("", big.NewInt(1))
   162  	deployAccumulatorContract := append(common.FromHex(AccumulatorBin), deployAccumulatorContractConstructor...)
   163  	incrementAccumulatorContract, _ := AccumulatorParsedABI.Pack("inc", big.NewInt(1))
   164  
   165  	mockptm.EXPECT().Receive(deployContract).Return("", []string{"AAA", "CCC"}, deployAccumulatorContract, nil, nil).AnyTimes()
   166  	mockptm.EXPECT().Receive(incrementByOnePS1).Return("", []string{"AAA"}, incrementAccumulatorContract, nil, nil).AnyTimes()
   167  	mockptm.EXPECT().Receive(incrementByOnePS1PS2).Return("", []string{"AAA", "CCC"}, incrementAccumulatorContract, nil, nil).AnyTimes()
   168  	mockptm.EXPECT().Receive(common.EncryptedPayloadHash{}).Return("", []string{}, common.EncryptedPayloadHash{}.Bytes(), nil, nil).AnyTimes()
   169  	mockptm.EXPECT().HasFeature(engine.MultiplePrivateStates).Return(true)
   170  	mockptm.EXPECT().Groups().Return([]engine.PrivacyGroup{PS1PG, PS2PG}, nil).AnyTimes()
   171  
   172  	return mockptm
   173  }
   174  
   175  func TestSegregatedCacheProviderMPS(t *testing.T) {
   176  	mockCtrl := gomock.NewController(t)
   177  	defer mockCtrl.Finish()
   178  
   179  	mockptm := buildMockMPSPTM(mockCtrl)
   180  
   181  	saved := private.P
   182  	defer func() {
   183  		private.P = saved
   184  	}()
   185  	private.P = mockptm
   186  
   187  	blocks, _, blockchain := buildCacheProviderMPSTestChain(11, params.QuorumMPSTestChainConfig, nil)
   188  
   189  	count, err := blockchain.InsertChain(blocks)
   190  
   191  	assert.Nil(t, err)
   192  	assert.Equal(t, len(blocks), count)
   193  
   194  	lastBlock := blocks[len(blocks)-1]
   195  
   196  	statedbLast, privateStateRepoLast, _ := blockchain.StateAt(lastBlock.Root())
   197  
   198  	assert.Equal(t, uint64(2*len(blocks)), statedbLast.GetNonce(testAddress))
   199  	PS1, _ := privateStateRepoLast.StatePSI(types.PrivateStateIdentifier("PS1"))
   200  	accPS1StateLast := PS1.GetState(Contract1AddressAfterDeployment, common.Hash{})
   201  	assert.Equal(t, common.BytesToHash(big.NewInt(int64(len(blocks))).Bytes()), accPS1StateLast)
   202  
   203  	PS2, _ := privateStateRepoLast.StatePSI(types.PrivateStateIdentifier("PS2"))
   204  	accPS2StateLast := PS2.GetState(Contract1AddressAfterDeployment, common.Hash{})
   205  	// PS2 is incremented every other block - thus the (len(blocks)+1)/2 formula
   206  	assert.Equal(t, common.BytesToHash(big.NewInt(int64((len(blocks)+1)/2)).Bytes()), accPS2StateLast)
   207  
   208  	// retrieve the state at block height 1
   209  	block1 := blocks[1]
   210  
   211  	statedbB1, privateStateRepoB1, _ := blockchain.StateAt(block1.Root())
   212  
   213  	assert.Equal(t, uint64(4), statedbB1.GetNonce(testAddress))
   214  	PS1, _ = privateStateRepoB1.StatePSI(types.PrivateStateIdentifier("PS1"))
   215  	PS1Root := PS1.IntermediateRoot(false)
   216  	accPS1StateB1 := PS1.GetState(Contract1AddressAfterDeployment, common.Hash{})
   217  	assert.Equal(t, common.BytesToHash([]byte{2}), accPS1StateB1)
   218  
   219  	PS2, _ = privateStateRepoB1.StatePSI(types.PrivateStateIdentifier("PS2"))
   220  	PS2Root := PS2.IntermediateRoot(false)
   221  	accPS2StateB1 := PS2.GetState(Contract1AddressAfterDeployment, common.Hash{})
   222  	// PS2 is incremented every other block - thus the (len(blocks)+1)/2 formula
   223  	assert.Equal(t, common.BytesToHash([]byte{1}), accPS2StateB1)
   224  
   225  	// check that both roots have already been written to the underlying DB
   226  
   227  	contains, err := blockchain.db.Has(PS1Root.Bytes())
   228  	assert.Nil(t, err)
   229  	assert.True(t, contains)
   230  	contains, err = blockchain.db.Has(PS2Root.Bytes())
   231  	assert.Nil(t, err)
   232  	assert.True(t, contains)
   233  }
   234  
   235  func TestUnifiedCacheProviderMPS(t *testing.T) {
   236  	mockCtrl := gomock.NewController(t)
   237  	defer mockCtrl.Finish()
   238  
   239  	mockptm := buildMockMPSPTM(mockCtrl)
   240  
   241  	saved := private.P
   242  	defer func() {
   243  		private.P = saved
   244  	}()
   245  	private.P = mockptm
   246  
   247  	blocks, _, blockchain := buildCacheProviderMPSTestChain(130, params.QuorumMPSTestChainConfig, &QuorumChainConfig{multiTenantEnabled: true, privateTrieCacheEnabled: true})
   248  
   249  	count, err := blockchain.InsertChain(blocks[:129])
   250  
   251  	assert.Nil(t, err)
   252  	assert.Equal(t, 129, count)
   253  
   254  	lastBlock := blocks[128]
   255  
   256  	statedb, privateStateRepo, _ := blockchain.StateAt(lastBlock.Root())
   257  
   258  	assert.Equal(t, uint64(258), statedb.GetNonce(testAddress))
   259  	PS1, _ := privateStateRepo.StatePSI(types.PrivateStateIdentifier("PS1"))
   260  	accPS1State := PS1.GetState(Contract1AddressAfterDeployment, common.Hash{})
   261  	assert.Equal(t, common.BytesToHash(big.NewInt(129).Bytes()), accPS1State)
   262  
   263  	PS2, _ := privateStateRepo.StatePSI(types.PrivateStateIdentifier("PS2"))
   264  	accPS2State := PS2.GetState(Contract1AddressAfterDeployment, common.Hash{})
   265  	// PS2 is incremented every other block - thus the (len(blocks)+1)/2 formula
   266  	assert.Equal(t, common.BytesToHash(big.NewInt(65).Bytes()), accPS2State)
   267  
   268  	// The following is an attempt to explain the process by which the trie nodes corresponding to PS1 and PS2 are being
   269  	// garbage collected due to the TriesInMemory limit implemented in the blockchain
   270  
   271  	// Expected state structure (block 2 - index 1 in the blocks array)
   272  	// Public state just contains the testAddress(testKey) with nonce 4 and has PUB(BL2) root hash
   273  	// PS1 has C1(2) and C2(2) with PS1(BL2) as root hash
   274  	// PS2 has C1(1) and C2(2) with PS2(BL2) as root hash
   275  	// the Trie of private states contains PS1(BL2) and PS2(BL2) and has TPS(BL2) root hash
   276  	// the public state root references the trie of private states PUB(BL2) -> TPS(BL2)
   277  	// the trie of private states leaves reference PS1(BL2) and PS2(BL2)
   278  
   279  	// Considering the above we can establish that
   280  	// PS1(BL2) is referenced once by the PS1 leaf in the trie of private states at block height 2
   281  	// PS2(BL2) is referenced once by the PS2 leaf in the trie of private states at block height 2
   282  
   283  	// retrieve the state at block height 2
   284  	block1 := blocks[1]
   285  	statedbB1, privateStateRepoB1, _ := blockchain.StateAt(block1.Root())
   286  
   287  	assert.Equal(t, uint64(4), statedbB1.GetNonce(testAddress))
   288  	PS1, _ = privateStateRepoB1.StatePSI(types.PrivateStateIdentifier("PS1"))
   289  	PS1Root := PS1.IntermediateRoot(false)
   290  	accPS1StateB1 := PS1.GetState(Contract1AddressAfterDeployment, common.Hash{})
   291  	assert.Equal(t, common.BytesToHash([]byte{2}), accPS1StateB1)
   292  
   293  	PS2, _ = privateStateRepoB1.StatePSI(types.PrivateStateIdentifier("PS2"))
   294  	PS2Root := PS2.IntermediateRoot(false)
   295  	accPS2StateB1 := PS2.GetState(Contract1AddressAfterDeployment, common.Hash{})
   296  	// PS2 is incremented every other block - thus the (len(blocks)+1)/2 formula
   297  	assert.Equal(t, common.BytesToHash([]byte{1}), accPS2StateB1)
   298  
   299  	// check that the roots have NOT been written to the underlying DB
   300  	contains, err := blockchain.db.Has(block1.Root().Bytes())
   301  	assert.Nil(t, err)
   302  	assert.False(t, contains)
   303  	contains, err = blockchain.db.Has(PS1Root.Bytes())
   304  	assert.Nil(t, err)
   305  	assert.False(t, contains)
   306  	contains, err = blockchain.db.Has(PS2Root.Bytes())
   307  	assert.Nil(t, err)
   308  	assert.False(t, contains)
   309  
   310  	// check that the roots are available in the cache
   311  	data, err := blockchain.stateCache.TrieDB().Node(block1.Root())
   312  	assert.Nil(t, err)
   313  	assert.True(t, len(data) > 0)
   314  	data, err = blockchain.stateCache.TrieDB().Node(PS1Root)
   315  	assert.Nil(t, err)
   316  	assert.True(t, len(data) > 0)
   317  	data, err = blockchain.stateCache.TrieDB().Node(PS2Root)
   318  	assert.Nil(t, err)
   319  	assert.True(t, len(data) > 0)
   320  
   321  	// Process block 130 and reassess the underlying DB and the cache
   322  	// When block 130 is processed the "chosen" in blockchain becomes 2 (130 - TriesInMemory) and the public root hash
   323  	// PUB(BL2) is being de-referenced (reference count is reduced by 1) and the reference count (parents) becomes 0
   324  	// As a result the trie of private states TPS(BL2) is also being de-referenced and becomes 0
   325  	// Each leaf in the trie of private states is being de-referenced which then causes the private state roots PS1(BL2)
   326  	// and PS2(BL2) to at block height 2 be de-referenced as well
   327  
   328  	// All nodes with reference counts (parents) equal to 0 are being garbage collected (removed from the cache)
   329  	count, err = blockchain.InsertChain(blocks[129:])
   330  
   331  	assert.Nil(t, err)
   332  	assert.Equal(t, 1, count)
   333  
   334  	// check that the roots have NOT been written to the underlying DB
   335  	contains, err = blockchain.db.Has(block1.Root().Bytes())
   336  	assert.Nil(t, err)
   337  	assert.False(t, contains)
   338  	contains, err = blockchain.db.Has(PS1Root.Bytes())
   339  	assert.Nil(t, err)
   340  	assert.False(t, contains)
   341  	contains, err = blockchain.db.Has(PS2Root.Bytes())
   342  	assert.Nil(t, err)
   343  	assert.False(t, contains)
   344  
   345  	// check that the roots have been garbage collected (removed) from the cache (other intermediate trie nodes may have
   346  	// been eliminated from the cache as well)
   347  	data, err = blockchain.stateCache.TrieDB().Node(block1.Root())
   348  	assert.Error(t, err, "not found")
   349  	assert.Nil(t, data)
   350  	data, err = blockchain.stateCache.TrieDB().Node(PS1Root)
   351  	assert.Error(t, err, "not found")
   352  	assert.Nil(t, data)
   353  	data, err = blockchain.stateCache.TrieDB().Node(PS2Root)
   354  	assert.Error(t, err, "not found")
   355  	assert.Nil(t, data)
   356  }