package core import ( "crypto/ecdsa" "encoding/base64" "math/big" "os" "testing" "github.com/ethereum/go-ethereum/core/mps" "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/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/private" privateEngine "github.com/ethereum/go-ethereum/private/engine" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) var ( contractDeployed = &contract{ name: "contractDeployed", abi: mustParse(contractDeployedDefinition), bytecode: common.Hex2Bytes("608060405234801561001057600080fd5b506040516020806105a88339810180604052602081101561003057600080fd5b81019080805190602001909291905050508060008190555050610550806100586000396000f3fe608060405260043610610051576000357c01000000000000000000000000000000000000000000000000000000009004806360fe47b1146100565780636d4ce63c146100a5578063d7139463146100d0575b600080fd5b34801561006257600080fd5b5061008f6004803603602081101561007957600080fd5b810190808035906020019092919050505061010b565b6040518082815260200191505060405180910390f35b3480156100b157600080fd5b506100ba61011e565b6040518082815260200191505060405180910390f35b3480156100dc57600080fd5b50610109600480360360208110156100f357600080fd5b8101908080359060200190929190505050610127565b005b6000816000819055506000549050919050565b60008054905090565b600030610132610212565b808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050604051809103906000f080158015610184573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff166360fe47b1836040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b1580156101f657600080fd5b505af115801561020a573d6000803e3d6000fd5b505050505050565b604051610302806102238339019056fe608060405234801561001057600080fd5b506040516020806103028339810180604052602081101561003057600080fd5b8101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610271806100916000396000f3fe608060405260043610610046576000357c01000000000000000000000000000000000000000000000000000000009004806360fe47b11461004b5780636d4ce63c14610086575b600080fd5b34801561005757600080fd5b506100846004803603602081101561006e57600080fd5b81019080803590602001909291905050506100b1565b005b34801561009257600080fd5b5061009b610180565b6040518082815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166360fe47b1826040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050602060405180830381600087803b15801561014157600080fd5b505af1158015610155573d6000803e3d6000fd5b505050506040513d602081101561016b57600080fd5b81019080805190602001909291905050505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636d4ce63c6040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b15801561020557600080fd5b505afa158015610219573d6000803e3d6000fd5b505050506040513d602081101561022f57600080fd5b810190808051906020019092919050505090509056fea165627a7a72305820a537f4c360ce5c6f55523298e314e6456e5c3e02c170563751dfda37d3aeddb30029a165627a7a7230582060396bfff29d2dfc5a9f4216bfba5e24d031d54fd4b26ebebde1a26c59df0c1e0029"), } contractDeploymentCount = 100 contractArgumentInitValue = int64(10) contractArgumentSetValue = int64(15) contractCreateABIPayloadBytes = contractDeployed.create(big.NewInt(contractArgumentInitValue)) contractSetABIPayloadBytes = contractDeployed.set(contractArgumentSetValue) encryptedPayloadHashForContractDeployment = common.BytesToEncryptedPayloadHash(common.Hex2Bytes("41a982be5d1f3d92d57487d7d9a905c1d92d3353570730464639affc964bcc83ea24e5b449140a2216ecc3f1d11d3dfd3663c6a9a4f18a7c837a9e4d8bfc81ce")) encryptedPayloadHashForSetFunction = common.BytesToEncryptedPayloadHash(common.Hex2Bytes("93f769208aa744b6d65310ab191f1fe22f8508ad069810f06889381b89d8c03ade785c7b14230439673f76e08ec84bad611d95d1cbb66dbcf548acbf93db0296")) slot0OnAccountStorage = common.HexToHash("00") ) func TestPrefetch_PublicTransaction(t *testing.T) { var ( engine = ethash.NewFaker() ) mockTxDataArr := createMockTxData(contractDeploymentCount, Public) chain, gspec := createBlockchain(params.QuorumTestChainConfig, mockTxDataArr) minedBlock, futureBlock := createBlocks(chain, gspec, mockTxDataArr, nil) // Import the canonical chain chain.InsertChain(types.Blocks{minedBlock}) prefetcher := newStatePrefetcher(gspec.Config, chain, engine) throwaway, throwawayRepo := createThrowawayStates(minedBlock, chain) // When prefetcher.Prefetch(futureBlock, throwaway, throwawayRepo, vm.Config{}, nil) // Then for _, data := range mockTxDataArr { assert.Equal(t, uint64(2), throwaway.GetNonce(data.fromAddress)) assert.Equal(t, common.BigToHash(big.NewInt(contractArgumentSetValue)), throwaway.GetState(data.toAddress, slot0OnAccountStorage)) } } func TestPrefetch_PrivateDualStateTransaction(t *testing.T) { var ( engine = ethash.NewFaker() mockCtrl = gomock.NewController(t) ) defer mockCtrl.Finish() mockptm := createDualStatePrivateTransactionManagerMock(mockCtrl) saved := private.P defer func() { private.P = saved }() private.P = mockptm mockTxDataArr := createMockTxData(contractDeploymentCount, Private) chain, gspec := createBlockchain(params.QuorumTestChainConfig, mockTxDataArr) minedBlock, futureBlock := createBlocks(chain, gspec, mockTxDataArr, nil) // Import the canonical chain chain.InsertChain(types.Blocks{minedBlock}) prefetcher := newStatePrefetcher(gspec.Config, chain, engine) throwaway, throwawayRepo := createThrowawayStates(minedBlock, chain) // When prefetcher.Prefetch(futureBlock, throwaway, throwawayRepo, vm.Config{}, nil) // Then assertPrefetchedDualState(t, mockTxDataArr, throwaway, throwawayRepo) } func TestPrefetch_PrivateMPSTransaction(t *testing.T) { var ( engine = ethash.NewFaker() mockCtrl = gomock.NewController(t) ) defer mockCtrl.Finish() mockptm := createMPSPrivateTransactionManagerMock(mockCtrl) saved := private.P defer func() { private.P = saved }() private.P = mockptm mockTxDataArr := createMockTxData(contractDeploymentCount, Private) chain, gspec := createBlockchain(params.QuorumMPSTestChainConfig, mockTxDataArr) minedBlock, futureBlock := createBlocks(chain, gspec, mockTxDataArr, nil) // Import the canonical chain chain.InsertChain(types.Blocks{minedBlock}) prefetcher := newStatePrefetcher(gspec.Config, chain, engine) throwaway, throwawayRepo := createThrowawayStates(minedBlock, chain) // When prefetcher.Prefetch(futureBlock, throwaway, throwawayRepo, vm.Config{}, nil) // Then assertPrefetchedMPSState(t, mockTxDataArr, throwaway, throwawayRepo) } func TestPrefetch_PrivateDualState_PMTTransaction(t *testing.T) { var ( engine = ethash.NewFaker() mockCtrl = gomock.NewController(t) ) defer mockCtrl.Finish() // Activate PMT params.QuorumTestChainConfig.PrivacyPrecompileBlock = big.NewInt(0) defer func() { params.QuorumTestChainConfig.PrivacyPrecompileBlock = nil }() mockptm := createDualStatePrivateTransactionManagerMock(mockCtrl) saved := private.P defer func() { private.P = saved }() private.P = mockptm mockTxDataArr := createMockTxData(contractDeploymentCount, PMT) chain, gspec := createBlockchain(params.QuorumTestChainConfig, mockTxDataArr) minedBlock, futureBlock := createBlocks(chain, gspec, mockTxDataArr, createInnerTransactionHandlerMock(mockptm)) // Import the canonical chain chain.InsertChain(types.Blocks{minedBlock}) prefetcher := newStatePrefetcher(gspec.Config, chain, engine) throwaway, throwawayRepo := createThrowawayStates(minedBlock, chain) // When prefetcher.Prefetch(futureBlock, throwaway, throwawayRepo, vm.Config{}, nil) // Then assertPrefetchedDualState(t, mockTxDataArr, throwaway, throwawayRepo) } func TestPrefetch_PrivateMPS_PMTTransaction(t *testing.T) { var ( engine = ethash.NewFaker() mockCtrl = gomock.NewController(t) ) defer mockCtrl.Finish() // Activate PMT params.QuorumMPSTestChainConfig.PrivacyPrecompileBlock = big.NewInt(0) defer func() { params.QuorumMPSTestChainConfig.PrivacyPrecompileBlock = nil }() mockptm := createMPSPrivateTransactionManagerMock(mockCtrl) saved := private.P defer func() { private.P = saved }() private.P = mockptm mockTxDataArr := createMockTxData(contractDeploymentCount, PMT) chain, gspec := createBlockchain(params.QuorumMPSTestChainConfig, mockTxDataArr) minedBlock, futureBlock := createBlocks(chain, gspec, mockTxDataArr, createInnerTransactionHandlerMock(mockptm)) // Import the canonical chain chain.InsertChain(types.Blocks{minedBlock}) prefetcher := newStatePrefetcher(gspec.Config, chain, engine) throwaway, throwawayRepo := createThrowawayStates(minedBlock, chain) // When prefetcher.Prefetch(futureBlock, throwaway, throwawayRepo, vm.Config{}, nil) // Then assertPrefetchedMPSState(t, mockTxDataArr, throwaway, throwawayRepo) } // Assert functions func assertPrefetchedDualState(t *testing.T, mockTxDataArr []*mockTxData, throwaway *state.StateDB, throwawayRepo mps.PrivateStateRepository) { throwawayPrivateState, _ := throwawayRepo.DefaultState() for _, data := range mockTxDataArr { assert.Equal(t, uint64(2), throwaway.GetNonce(data.fromAddress)) assert.Equal(t, common.Hash{}, throwaway.GetState(data.toAddress, slot0OnAccountStorage)) assert.Equal(t, common.BigToHash(big.NewInt(contractArgumentSetValue)), throwawayPrivateState.GetState(data.toAddress, slot0OnAccountStorage)) } } func assertPrefetchedMPSState(t *testing.T, mockTxDataArr []*mockTxData, throwaway *state.StateDB, throwawayRepo mps.PrivateStateRepository) { throwawayDefaultPrivateState, _ := throwawayRepo.DefaultState() throwawayPS1PrivateState, _ := throwawayRepo.StatePSI(PSI1PSM.ID) throwawayPS2PrivateState, _ := throwawayRepo.StatePSI(PSI2PSM.ID) for _, data := range mockTxDataArr { assert.Equal(t, uint64(2), throwaway.GetNonce(data.fromAddress)) assert.Equal(t, common.Hash{}, throwaway.GetState(data.toAddress, slot0OnAccountStorage)) assert.Equal(t, common.Hash{}, throwawayDefaultPrivateState.GetState(data.toAddress, slot0OnAccountStorage)) assert.Equal(t, common.Hash{}, throwawayPS1PrivateState.GetState(data.toAddress, slot0OnAccountStorage)) assert.Equal(t, common.BigToHash(big.NewInt(contractArgumentSetValue)), throwawayPS2PrivateState.GetState(data.toAddress, slot0OnAccountStorage)) } } // Utility types type txType int const ( Public txType = iota Private PMT ) type mockTxData struct { fromAddress common.Address fromPrivateKey *ecdsa.PrivateKey toAddress common.Address funds *big.Int txType txType } // Utility functions // createThrowawayStates create the StateDBs to be used for the prefetcher to warm up the cached but are thrown away after func createThrowawayStates(minedBlock *types.Block, chain *BlockChain) (*state.StateDB, mps.PrivateStateRepository) { throwaway, _ := state.New(minedBlock.Root(), chain.stateCache, chain.snaps) privateRepo, _ := chain.PrivateStateManager().StateRepository(minedBlock.Root()) throwawayRepo := privateRepo.Copy() return throwaway, throwawayRepo } // createDualStatePrivateTransactionManagerMock create the Tessera mock for Dual State Mode func createDualStatePrivateTransactionManagerMock(mockCtrl *gomock.Controller) *private.MockPrivateTransactionManager { mockptm := private.NewMockPrivateTransactionManager(mockCtrl) mockptm.EXPECT().Receive(encryptedPayloadHashForContractDeployment).Return("", []string{}, contractCreateABIPayloadBytes, nil, nil).AnyTimes() mockptm.EXPECT().Receive(encryptedPayloadHashForSetFunction).Return("", []string{}, contractSetABIPayloadBytes, nil, nil).AnyTimes() return mockptm } // createMPSPrivateTransactionManagerMock create the Tessera mock for MPS Mode func createMPSPrivateTransactionManagerMock(mockCtrl *gomock.Controller) *private.MockPrivateTransactionManager { mockptm := private.NewMockPrivateTransactionManager(mockCtrl) mockptm.EXPECT().Receive(common.EncryptedPayloadHash{}).Return("", []string{}, nil, nil, nil).AnyTimes() mockptm.EXPECT().Receive(encryptedPayloadHashForContractDeployment).Return("", []string{"BBB"}, contractCreateABIPayloadBytes, nil, nil).AnyTimes() mockptm.EXPECT().Receive(encryptedPayloadHashForSetFunction).Return("", []string{"BBB"}, contractSetABIPayloadBytes, nil, nil).AnyTimes() mockptm.EXPECT().HasFeature(privateEngine.MultiplePrivateStates).Return(true).AnyTimes() mockptm.EXPECT().Groups().Return([]privateEngine.PrivacyGroup{ { Type: privateEngine.PrivacyGroupResident, Name: PSI1PSM.Name, PrivacyGroupId: base64.StdEncoding.EncodeToString([]byte(PSI1PSM.ID)), Description: "Resident Group 1", From: "", Members: []string{"AAA"}, }, { Type: privateEngine.PrivacyGroupResident, Name: PSI2PSM.Name, PrivacyGroupId: base64.StdEncoding.EncodeToString([]byte(PSI2PSM.ID)), Description: "Resident Group 2", From: "", Members: []string{"BBB"}, }, }, nil) return mockptm } // createInnerTransactionHandlerMock create a function that will create the necessary mock to handle the Private Transaction `Receive()` of the private transaction inside a PMT Tx func createInnerTransactionHandlerMock(mockptm *private.MockPrivateTransactionManager) func(outerTx *types.Transaction, mockTxData *mockTxData) { return func(outerTx *types.Transaction, mockTxData *mockTxData) { enclaveHash := common.BytesToEncryptedPayloadHash(outerTx.Data()) mockptm.EXPECT().Receive(enclaveHash).DoAndReturn(func(hash common.EncryptedPayloadHash) (string, []string, []byte, *privateEngine.ExtraMetadata, error) { innerTx := types.NewTransaction(1, mockTxData.toAddress, common.Big0, uint64(3000000), common.Big0, encryptedPayloadHashForSetFunction.Bytes()) innerTx.SetPrivate() signedTx, _ := types.SignTx(innerTx, types.QuorumPrivateTxSigner{}, mockTxData.fromPrivateKey) jsonSignedTx, _ := signedTx.MarshalJSON() return "", []string{}, jsonSignedTx, nil, nil }).AnyTimes() } } // createMockTxData create mocked data for transactions func createMockTxData(n int, txType txType) []*mockTxData { result := make([]*mockTxData, n) for i := 0; i < n; i++ { fromKey, _ := crypto.GenerateKey() fromAddress := crypto.PubkeyToAddress(fromKey.PublicKey) result[i] = &mockTxData{ fromPrivateKey: fromKey, fromAddress: fromAddress, funds: big.NewInt(1000000000), txType: txType} } return result } func createBlockchain(chainConfig *params.ChainConfig, mockTxDataArr []*mockTxData) (*BlockChain, *Genesis) { var ( engine = ethash.NewFaker() cacheConfig = *defaultCacheConfig ) // Disable prefetching. We are going to manually run prefetch cacheConfig.TrieCleanNoPrefetch = true allocation := GenesisAlloc{} for _, data := range mockTxDataArr { allocation[data.fromAddress] = GenesisAccount{ Balance: data.funds, Nonce: 0, } } gspec := &Genesis{ Config: chainConfig, Alloc: allocation, } diskdb := rawdb.NewMemoryDatabase() gspec.MustCommit(diskdb) vmConfig := vm.Config{ Debug: true, Tracer: vm.NewJSONLogger(nil, os.Stdout), } chain, _ := NewBlockChain(diskdb, &cacheConfig, gspec.Config, engine, vmConfig, nil, nil, nil) return chain, gspec } func createBlocks(chain *BlockChain, gspec *Genesis, mockTxDataArr []*mockTxData, decorateSetTransaction func(*types.Transaction, *mockTxData)) (*types.Block, *types.Block) { var ( engine = ethash.NewFaker() temporaryDb = rawdb.NewMemoryDatabase() ) genesisBlock := gspec.MustCommit(temporaryDb) minedBlocks, _ := GenerateChain(gspec.Config, genesisBlock, engine, temporaryDb, 1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) var signer types.Signer = types.HomesteadSigner{} for _, mockTxData := range mockTxDataArr { var data []byte switch mockTxData.txType { case Public: data = contractCreateABIPayloadBytes case Private, PMT: data = encryptedPayloadHashForContractDeployment.Bytes() } createTransaction := types.NewContractCreation(0, common.Big0, uint64(3000000), common.Big0, data) switch mockTxData.txType { case Private, PMT: createTransaction.SetPrivate() signer = types.QuorumPrivateTxSigner{} } signedTx, _ := types.SignTx(createTransaction, signer, mockTxData.fromPrivateKey) b.AddTxWithChain(chain, signedTx) // save the contract address to use when calling `set()` mockTxData.toAddress = b.receipts[0].ContractAddress } }) futureBlocks, _ := GenerateChain(gspec.Config, minedBlocks[0], engine, temporaryDb, 1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) var signer types.Signer = types.HomesteadSigner{} for _, mockTxData := range mockTxDataArr { var data []byte var setTransaction *types.Transaction switch mockTxData.txType { case Public: data = contractSetABIPayloadBytes setTransaction = types.NewTransaction(1, mockTxData.toAddress, common.Big0, uint64(3000000), common.Big0, data) case Private: data = encryptedPayloadHashForSetFunction.Bytes() setTransaction = types.NewTransaction(1, mockTxData.toAddress, common.Big0, uint64(3000000), common.Big0, data) setTransaction.SetPrivate() signer = types.QuorumPrivateTxSigner{} case PMT: data = common.LeftPadBytes(mockTxData.toAddress.Bytes(), 64) setTransaction = types.NewTransaction(1, common.QuorumPrivacyPrecompileContractAddress(), common.Big0, uint64(3000000), common.Big0, data) } if decorateSetTransaction != nil { decorateSetTransaction(setTransaction, mockTxData) } signedTx, _ := types.SignTx(setTransaction, signer, mockTxData.fromPrivateKey) b.AddTxWithChain(chain, signedTx) } }) return minedBlocks[0], futureBlocks[0] } const ( contractDeployedDefinition = ` [ { "constant": false, "inputs": [ { "name": "newValue", "type": "uint256" } ], "name": "set", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "get", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "newValue", "type": "uint256" } ], "name": "newContractC2", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "name": "initVal", "type": "uint256" } ], "payable": false, "stateMutability": "nonpayable", "type": "constructor" } ] ` )