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 }