From 1bcd34a4069707cf74c29f107c0eb89f5dc6caf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= <93934272+Stefan-Ethernal@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:03:39 +0100 Subject: [PATCH] [FEATURE] Berlin hard fork (#106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * EIP-2565 Implementation (#1704) * implemented EIP-2565 * fixed linting error * fixed linting error * fixed TestModExpWithEIP2565 * resolved comments * resolved comments * resolved merge conflicts * Eip 2929 (#1748) * implemented changes for EIP-2929 (access list) * added missing files * fixed unit test Test_opCall * populate access_list with precompiles only if eip-2929 is activated * added address to access list for Balance opcode * added test cases and for EIP-2929 and also call stack output * changed structure of access list and removed some comments * fixed linting errors * fixed AddSlot method * added test cases for access list * removed call stack output for Test_Transition_EIP2929 * corrected indentation * fixed linting errors * fixed linting errors * fixed Test_opReturnDataCopy * removed ActivePrecompile Address slice * changed the const name * changed the const name in comments * change the AddAddress function to a variadic function * refactored * added test cases for SLOAD opcode * fixed linting errors * refactored code * resolved merge conflicts * fixed EIP-2929 test * Rebase fix * `Berlin` fork implementation (#81) * Berlin implementation * Fix tests * Lint fix * Lint fix part 2 * Bug fixes * Lint fix * Legacy e2e tests fix * Unmarshal fix * Tx signer fix * Use requires instead of Fatalf * Pass the TestLondonSignerSender * Cleanup precompile addresses * TxSigner fix * Fix precompiles addresses access in unit test * Order tx types * Address comments --------- Co-authored-by: Stefan Negovanović * Rebase fix * Berlin fixes (#86) * Add journal to contract access list changes * small fix on journal * remove access list from state * Fix build * Remove commented code * Add access list to state_test_util * Include BerlinToLondon tests as well * Minor change --------- Co-authored-by: Stefan Negovanović * Use `btcec v2` lib for `ECDSA` keys manipulation (#105) * Use btcec v2 lib for ECDSA keys manipulation * Fix test * Remove key from stake manager * Warning fix (loop variable capture) * Bump Ethereum tests version (#119) * Bump Ethereum tests version * Linter fixes * Skip loopMul and minor refactoring * Refactor Ethereum tests files resolution * Provide EIP 3607 to each fork config * Audit duration of each test * Add detailed logging and skip CREATEBlake2f_MaxRounds test * Remove -v flag * small fixes * fix * Lint fix --------- Co-authored-by: Goran Rojovic * London test fixes (#111) * Tests fixes * SelfDestruct fix * Rebase fix * Journaling fix * Tests fix * e2e fix * subGasLimit fix * Fix * Skip CheckEIP1559 e2e test * Remove auxState field from Transition and use NewTransition function * increase timeout for UT * Suppress linting warnings * underflow test fix * Revert make file * Remove panic * Use %d in the error formatting --------- Co-authored-by: Stefan Negovanović --------- Co-authored-by: Rachit Sonthalia <54906134+rachit77@users.noreply.github.com> Co-authored-by: Goran Rojovic Co-authored-by: Goran Rojovic <100121253+goran-ethernal@users.noreply.github.com> --- blockchain/blockchain.go | 13 +- blockchain/blockchain_test.go | 120 +++--- blockchain/storage/leveldb/leveldb_test.go | 8 +- blockchain/storage/testing.go | 18 +- blockchain/testing.go | 2 +- chain/params.go | 4 + consensus/dev/dev.go | 2 +- consensus/polybft/block_builder.go | 8 +- consensus/polybft/block_builder_test.go | 55 +-- consensus/polybft/blockchain_wrapper.go | 10 +- consensus/polybft/consensus_runtime.go | 1 - consensus/polybft/fsm.go | 28 +- consensus/polybft/fsm_test.go | 8 +- consensus/polybft/polybft.go | 10 +- consensus/polybft/stake_manager.go | 2 - consensus/polybft/stake_manager_fuzz_test.go | 3 - consensus/polybft/stake_manager_test.go | 6 - consensus/polybft/state_sync_commitment.go | 8 +- consensus/polybft/wallet/account.go | 3 +- consensus/polybft/wallet/key.go | 2 +- crypto/crypto.go | 111 +++-- crypto/crypto_test.go | 6 +- crypto/txsigner.go | 166 ++++++-- crypto/txsigner_eip155.go | 28 +- crypto/txsigner_eip155_test.go | 8 +- crypto/txsigner_frontier.go | 28 +- crypto/txsigner_frontier_test.go | 4 +- crypto/txsigner_london.go | 79 ---- crypto/txsigner_london_berlin.go | 72 ++++ ...test.go => txsigner_london_berlin_test.go} | 84 ++-- e2e-polybft/e2e/consensus_test.go | 2 + e2e-polybft/e2e/jsonrpc_test.go | 4 +- e2e/framework/testserver.go | 4 +- e2e/txpool_test.go | 31 +- gasprice/feehistory.go | 2 +- gasprice/feehistory_test.go | 4 +- gasprice/gasprice.go | 2 +- gasprice/gasprice_test.go | 8 +- go.mod | 3 +- go.sum | 2 + helper/enode/enode.go | 4 +- helper/predeployment/predeployment.go | 3 +- helper/tests/testing.go | 4 +- jsonrpc/debug_endpoint.go | 6 +- jsonrpc/debug_endpoint_test.go | 4 +- jsonrpc/eth_blockchain_test.go | 19 +- jsonrpc/eth_endpoint.go | 35 +- jsonrpc/eth_endpoint_test.go | 24 +- jsonrpc/eth_state_test.go | 2 +- jsonrpc/eth_txpool_test.go | 16 +- jsonrpc/filter_manager.go | 4 +- jsonrpc/filter_manager_fuzz_test.go | 12 +- jsonrpc/filter_manager_test.go | 12 +- jsonrpc/helper.go | 6 +- jsonrpc/helper_test.go | 40 +- jsonrpc/txpool_endpoint.go | 6 +- jsonrpc/txpool_endpoint_test.go | 82 ++-- jsonrpc/types.go | 113 +++--- jsonrpc/types_test.go | 26 +- server/server.go | 4 +- state/executor.go | 317 ++++++++++----- state/executor_test.go | 131 +++++- state/runtime/access_list.go | 115 ++++++ state/runtime/access_list_test.go | 99 +++++ state/runtime/evm/dispatch_table.go | 2 +- state/runtime/evm/evm_fuzz_test.go | 13 +- state/runtime/evm/evm_test.go | 27 +- state/runtime/evm/instructions.go | 127 ++++-- state/runtime/evm/instructions_test.go | 381 ++++++++++++++---- state/runtime/journal.go | 57 +++ state/runtime/precompiled/base.go | 6 +- state/runtime/precompiled/base_test.go | 11 +- state/runtime/precompiled/modexp.go | 40 +- state/runtime/precompiled/modexp_test.go | 103 ++++- .../precompiled/native_transfer_test.go | 28 ++ state/runtime/precompiled/precompiled.go | 7 +- state/runtime/runtime.go | 11 +- state/transition_test.go | 30 +- state/txn.go | 29 +- tests/state_test.go | 26 +- tests/state_test_util.go | 205 +++++++--- tests/tests | 2 +- txpool/account.go | 25 +- txpool/lookup_map.go | 6 +- txpool/mock_test.go | 2 +- txpool/operator.go | 6 +- txpool/queue_account.go | 8 +- txpool/queue_priced.go | 2 +- txpool/queue_priced_test.go | 140 ++++--- txpool/txpool.go | 120 +++--- txpool/txpool_test.go | 247 ++++++------ types/access_list_tx.go | 135 +++++++ types/mixed_tx.go | 105 +++++ types/rlp_encoding_test.go | 54 +-- types/rlp_marshal.go | 133 ++++-- types/rlp_marshal_storage.go | 6 +- types/rlp_unmarshal.go | 363 +++++++++++++---- types/rlp_unmarshal_storage.go | 36 +- types/transaction.go | 353 ++++++++++++---- types/types_test.go | 4 +- 100 files changed, 3455 insertions(+), 1438 deletions(-) delete mode 100644 crypto/txsigner_london.go create mode 100644 crypto/txsigner_london_berlin.go rename crypto/{txsigner_london_test.go => txsigner_london_berlin_test.go} (57%) create mode 100644 state/runtime/access_list.go create mode 100644 state/runtime/access_list_test.go create mode 100644 state/runtime/journal.go create mode 100644 types/access_list_tx.go create mode 100644 types/mixed_tx.go diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index fe4d17fb0d..e9749db955 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -1003,7 +1003,7 @@ func (b *Blockchain) writeBody(batchWriter *storage.BatchWriter, block *types.Bl // Write txn lookups (txHash -> block) for _, txn := range block.Transactions { - batchWriter.PutTxLookup(txn.Hash, block.Hash()) + batchWriter.PutTxLookup(txn.Hash(), block.Hash()) } return nil @@ -1020,7 +1020,7 @@ func (b *Blockchain) ReadTxLookup(hash types.Hash) (types.Hash, bool) { // return error if the invalid signature found func (b *Blockchain) recoverFromFieldsInBlock(block *types.Block) error { for _, tx := range block.Transactions { - if tx.From != types.ZeroAddress || tx.Type == types.StateTx { + if tx.From() != types.ZeroAddress || tx.Type() == types.StateTx { continue } @@ -1029,7 +1029,7 @@ func (b *Blockchain) recoverFromFieldsInBlock(block *types.Block) error { return err } - tx.From = sender + tx.SetFrom(sender) } return nil @@ -1041,18 +1041,19 @@ func (b *Blockchain) recoverFromFieldsInTransactions(transactions []*types.Trans updated := false for _, tx := range transactions { - if tx.From != types.ZeroAddress || tx.Type == types.StateTx { + if tx.From() != types.ZeroAddress || tx.Type() == types.StateTx { continue } sender, err := b.txSigner.Sender(tx) if err != nil { - b.logger.Warn("failed to recover from address in Tx", "hash", tx.Hash, "err", err) + b.logger.Warn("failed to recover from address in Tx", "hash", tx.Hash(), "err", err) continue } - tx.From = sender + tx.SetFrom(sender) + updated = true } diff --git a/blockchain/blockchain_test.go b/blockchain/blockchain_test.go index 9ab759513c..567e605944 100644 --- a/blockchain/blockchain_test.go +++ b/blockchain/blockchain_test.go @@ -590,11 +590,11 @@ func TestBlockchainWriteBody(t *testing.T) { t.Run("should succeed if tx has from field", func(t *testing.T) { t.Parallel() - tx := &types.Transaction{ + tx := types.NewTx(&types.MixedTxn{ Value: big.NewInt(10), V: big.NewInt(1), From: addr, - } + }) block := &types.Block{ Header: &types.Header{}, @@ -622,10 +622,10 @@ func TestBlockchainWriteBody(t *testing.T) { t.Run("should return error if tx doesn't have from and recovering address fails", func(t *testing.T) { t.Parallel() - tx := &types.Transaction{ + tx := types.NewTx(&types.MixedTxn{ Value: big.NewInt(10), V: big.NewInt(1), - } + }) block := &types.Block{ Header: &types.Header{}, @@ -654,10 +654,10 @@ func TestBlockchainWriteBody(t *testing.T) { t.Run("should recover from address and store to storage", func(t *testing.T) { t.Parallel() - tx := &types.Transaction{ + tx := types.NewTx(&types.MixedTxn{ Value: big.NewInt(10), V: big.NewInt(1), - } + }) block := &types.Block{ Header: &types.Header{}, @@ -670,7 +670,7 @@ func TestBlockchainWriteBody(t *testing.T) { block.Header.ComputeHash() txFromByTxHash := map[types.Hash]types.Address{ - tx.Hash: addr, + tx.Hash(): addr, } chain := newChain(t, txFromByTxHash, "t3") @@ -686,7 +686,7 @@ func TestBlockchainWriteBody(t *testing.T) { readBody, ok := chain.readBody(block.Hash()) assert.True(t, ok) - assert.Equal(t, addr, readBody.Transactions[0].From) + assert.Equal(t, addr, readBody.Transactions[0].From()) }) } @@ -715,12 +715,12 @@ func Test_recoverFromFieldsInBlock(t *testing.T) { }, } - tx1 := &types.Transaction{Nonce: 0, From: addr1} - tx2 := &types.Transaction{Nonce: 1, From: types.ZeroAddress} + tx1 := types.NewTx(&types.MixedTxn{Nonce: 0, From: addr1}) + tx2 := types.NewTx(&types.MixedTxn{Nonce: 1, From: types.ZeroAddress}) computeTxHashes(tx1, tx2) - txFromByTxHash[tx2.Hash] = addr2 + txFromByTxHash[tx2.Hash()] = addr2 block := &types.Block{ Transactions: []*types.Transaction{ @@ -745,15 +745,15 @@ func Test_recoverFromFieldsInBlock(t *testing.T) { }, } - tx1 := &types.Transaction{Nonce: 0, From: types.ZeroAddress} - tx2 := &types.Transaction{Nonce: 1, From: types.ZeroAddress} - tx3 := &types.Transaction{Nonce: 2, From: types.ZeroAddress} + tx1 := types.NewTx(&types.MixedTxn{Nonce: 0, From: types.ZeroAddress}) + tx2 := types.NewTx(&types.MixedTxn{Nonce: 1, From: types.ZeroAddress}) + tx3 := types.NewTx(&types.MixedTxn{Nonce: 2, From: types.ZeroAddress}) computeTxHashes(tx1, tx2, tx3) // returns only addresses for tx1 and tx3 - txFromByTxHash[tx1.Hash] = addr1 - txFromByTxHash[tx3.Hash] = addr3 + txFromByTxHash[tx1.Hash()] = addr1 + txFromByTxHash[tx3.Hash()] = addr3 block := &types.Block{ Transactions: []*types.Transaction{ @@ -769,9 +769,9 @@ func Test_recoverFromFieldsInBlock(t *testing.T) { errRecoveryAddressFailed, ) - assert.Equal(t, addr1, tx1.From) - assert.Equal(t, types.ZeroAddress, tx2.From) - assert.Equal(t, types.ZeroAddress, tx3.From) + assert.Equal(t, addr1, tx1.From()) + assert.Equal(t, types.ZeroAddress, tx2.From()) + assert.Equal(t, types.ZeroAddress, tx3.From()) }) } @@ -801,12 +801,12 @@ func Test_recoverFromFieldsInTransactions(t *testing.T) { }, } - tx1 := &types.Transaction{Nonce: 0, From: addr1} - tx2 := &types.Transaction{Nonce: 1, From: types.ZeroAddress} + tx1 := types.NewTx(&types.MixedTxn{Nonce: 0, From: addr1}) + tx2 := types.NewTx(&types.MixedTxn{Nonce: 1, From: types.ZeroAddress}) computeTxHashes(tx1, tx2) - txFromByTxHash[tx2.Hash] = addr2 + txFromByTxHash[tx2.Hash()] = addr2 transactions := []*types.Transaction{ tx1, @@ -830,15 +830,15 @@ func Test_recoverFromFieldsInTransactions(t *testing.T) { }, } - tx1 := &types.Transaction{Nonce: 0, From: types.ZeroAddress} - tx2 := &types.Transaction{Nonce: 1, From: types.ZeroAddress} - tx3 := &types.Transaction{Nonce: 2, From: types.ZeroAddress} + tx1 := types.NewTx(&types.MixedTxn{Nonce: 0, From: types.ZeroAddress}) + tx2 := types.NewTx(&types.MixedTxn{Nonce: 1, From: types.ZeroAddress}) + tx3 := types.NewTx(&types.MixedTxn{Nonce: 2, From: types.ZeroAddress}) computeTxHashes(tx1, tx2, tx3) // returns only addresses for tx1 and tx3 - txFromByTxHash[tx1.Hash] = addr1 - txFromByTxHash[tx3.Hash] = addr3 + txFromByTxHash[tx1.Hash()] = addr1 + txFromByTxHash[tx3.Hash()] = addr3 transactions := []*types.Transaction{ tx1, @@ -848,9 +848,9 @@ func Test_recoverFromFieldsInTransactions(t *testing.T) { assert.True(t, chain.recoverFromFieldsInTransactions(transactions)) - assert.Equal(t, addr1, tx1.From) - assert.Equal(t, types.ZeroAddress, tx2.From) - assert.Equal(t, addr3, tx3.From) + assert.Equal(t, addr1, tx1.From()) + assert.Equal(t, types.ZeroAddress, tx2.From()) + assert.Equal(t, addr3, tx3.From()) }) t.Run("should return false if all transactions has from field", func(t *testing.T) { @@ -864,12 +864,12 @@ func Test_recoverFromFieldsInTransactions(t *testing.T) { }, } - tx1 := &types.Transaction{Nonce: 0, From: addr1} - tx2 := &types.Transaction{Nonce: 1, From: addr2} + tx1 := types.NewTx(&types.MixedTxn{Nonce: 0, From: addr1}) + tx2 := types.NewTx(&types.MixedTxn{Nonce: 1, From: addr2}) computeTxHashes(tx1, tx2) - txFromByTxHash[tx2.Hash] = addr2 + txFromByTxHash[tx2.Hash()] = addr2 transactions := []*types.Transaction{ tx1, @@ -900,10 +900,10 @@ func TestBlockchainReadBody(t *testing.T) { batchWriter := storage.NewBatchWriter(b.db) - tx := &types.Transaction{ + tx := types.NewTx(&types.MixedTxn{ Value: big.NewInt(10), V: big.NewInt(1), - } + }) tx.ComputeHash() @@ -916,7 +916,7 @@ func TestBlockchainReadBody(t *testing.T) { block.Header.ComputeHash() - txFromByTxHash[tx.Hash] = types.ZeroAddress + txFromByTxHash[tx.Hash()] = types.ZeroAddress batchWriter.PutCanonicalHeader(block.Header, big.NewInt(0)) @@ -924,12 +924,12 @@ func TestBlockchainReadBody(t *testing.T) { assert.NoError(t, batchWriter.WriteBatch()) - txFromByTxHash[tx.Hash] = addr + txFromByTxHash[tx.Hash()] = addr readBody, found := b.readBody(block.Hash()) assert.True(t, found) - assert.Equal(t, addr, readBody.Transactions[0].From) + assert.Equal(t, addr, readBody.Transactions[0].From()) } func TestCalculateGasLimit(t *testing.T) { @@ -1602,9 +1602,9 @@ func TestBlockchain_WriteFullBlock(t *testing.T) { {GasUsed: 100}, {GasUsed: 200}, } - tx := &types.Transaction{ + tx := types.NewTx(&types.MixedTxn{ Value: big.NewInt(1), - } + }) tx.ComputeHash() header.ComputeHash() @@ -1615,7 +1615,7 @@ func TestBlockchain_WriteFullBlock(t *testing.T) { header.ParentHash = existingHeader.Hash bc.txSigner = &mockSigner{ txFromByTxHash: map[types.Hash]types.Address{ - tx.Hash: {1, 2}, + tx.Hash(): {1, 2}, }, } @@ -1645,7 +1645,7 @@ func TestBlockchain_WriteFullBlock(t *testing.T) { require.Equal(t, 8, len(db)) require.Equal(t, uint64(2), bc.currentHeader.Load().Number) require.NotNil(t, db[hex.EncodeToHex(getKey(storage.BODY, header.Hash.Bytes()))]) - require.NotNil(t, db[hex.EncodeToHex(getKey(storage.TX_LOOKUP_PREFIX, tx.Hash.Bytes()))]) + require.NotNil(t, db[hex.EncodeToHex(getKey(storage.TX_LOOKUP_PREFIX, tx.Hash().Bytes()))]) require.NotNil(t, db[hex.EncodeToHex(getKey(storage.HEADER, header.Hash.Bytes()))]) require.NotNil(t, db[hex.EncodeToHex(getKey(storage.HEAD, storage.HASH))]) require.NotNil(t, db[hex.EncodeToHex(getKey(storage.CANONICAL, common.EncodeUint64ToBytes(header.Number)))]) @@ -1765,9 +1765,9 @@ func blockWriter(tb testing.TB, numberOfBlocks uint64, blockTime, checkInterval block.Block.Header.Hash = types.StringToHash(fmt.Sprintf("%d", counter.GetValue())) for i, transaction := range block.Block.Transactions { - transaction.Nonce = counter.x * uint64(i) + transaction.SetNonce(counter.x * uint64(i)) addr := types.StringToAddress(fmt.Sprintf("%d", counter.GetValue()*uint64(i))) - transaction.To = &addr + transaction.SetTo(&addr) } batchWriter.PutHeader(block.Block.Header) @@ -1868,8 +1868,8 @@ func customJSONBlockUnmarshall(tb testing.TB, jsonData []byte) (*types.FullBlock for _, transactionJSON := range transactionsJSON { tr := transactionJSON.(map[string]interface{}) - transaction := &types.Transaction{} - transaction.Hash = types.StringToHash(tr["hash"].(string)) + transaction := types.NewTx(&types.MixedTxn{}) + transaction.SetHash(types.StringToHash(tr["hash"].(string))) nonce := tr["nonce"].(string) nonceNumber, err := common.ParseUint64orHex(&nonce) @@ -1877,11 +1877,11 @@ func customJSONBlockUnmarshall(tb testing.TB, jsonData []byte) (*types.FullBlock return nil, err } - transaction.Nonce = nonceNumber + transaction.SetNonce(nonceNumber) - transaction.From = types.StringToAddress(tr["from"].(string)) + transaction.SetFrom(types.StringToAddress(tr["from"].(string))) addr := types.StringToAddress(tr["to"].(string)) - transaction.To = &addr + transaction.SetTo(&addr) value := tr["value"].(string) @@ -1890,7 +1890,7 @@ func customJSONBlockUnmarshall(tb testing.TB, jsonData []byte) (*types.FullBlock return nil, err } - transaction.Value = valueNumber + transaction.SetValue(valueNumber) gasPrice := tr["gasPrice"].(string) @@ -1899,9 +1899,9 @@ func customJSONBlockUnmarshall(tb testing.TB, jsonData []byte) (*types.FullBlock return nil, err } - transaction.GasPrice = gasPriceNumber + transaction.SetGasPrice(gasPriceNumber) - transaction.Input = []byte(tr["input"].(string)) + transaction.SetInput([]byte(tr["input"].(string))) v := tr["v"].(string) @@ -1910,8 +1910,6 @@ func customJSONBlockUnmarshall(tb testing.TB, jsonData []byte) (*types.FullBlock return nil, err } - transaction.V = vNumber - r := tr["r"].(string) rNumber, err := common.ParseUint256orHex(&r) @@ -1919,8 +1917,6 @@ func customJSONBlockUnmarshall(tb testing.TB, jsonData []byte) (*types.FullBlock return nil, err } - transaction.R = rNumber - s := tr["s"].(string) sNumber, err := common.ParseUint256orHex(&s) @@ -1928,7 +1924,7 @@ func customJSONBlockUnmarshall(tb testing.TB, jsonData []byte) (*types.FullBlock return nil, err } - transaction.S = sNumber + transaction.SetSignatureValues(vNumber, rNumber, sNumber) chainID := tr["chainId"].(string) @@ -1937,7 +1933,7 @@ func customJSONBlockUnmarshall(tb testing.TB, jsonData []byte) (*types.FullBlock return nil, err } - transaction.ChainID = chainIDNumber + transaction.SetChainID(chainIDNumber) txType := tr["type"].(string) @@ -1946,7 +1942,7 @@ func customJSONBlockUnmarshall(tb testing.TB, jsonData []byte) (*types.FullBlock return nil, err } - transaction.Type = types.TxType(txTypeNumber) + transaction.SetTransactionType(types.TxType(txTypeNumber)) gasFeeCapGeneric, ok := tr["maxFeePerGas"] if ok { @@ -1955,7 +1951,7 @@ func customJSONBlockUnmarshall(tb testing.TB, jsonData []byte) (*types.FullBlock gasFeeCapNumber, err := common.ParseUint256orHex(&gasFeeCap) require.NoError(tb, err) - transaction.GasFeeCap = gasFeeCapNumber + transaction.SetGasFeeCap(gasFeeCapNumber) } gasTipCapGeneric, ok := tr["maxPriorityFeePerGas"] @@ -1965,7 +1961,7 @@ func customJSONBlockUnmarshall(tb testing.TB, jsonData []byte) (*types.FullBlock gasTipCapNumber, err := common.ParseUint256orHex(&gasTipCap) require.NoError(tb, err) - transaction.GasTipCap = gasTipCapNumber + transaction.SetGasTipCap(gasTipCapNumber) } transactions = append(transactions, transaction) @@ -2050,6 +2046,8 @@ func customJSONReceiptsUnmarshall(tb testing.TB, jsonData []byte) ([]*types.Rece logs = append(logs, log) } + receipt.Logs = logs + receipts = append(receipts, receipt) } diff --git a/blockchain/storage/leveldb/leveldb_test.go b/blockchain/storage/leveldb/leveldb_test.go index 744edc2701..21136e69a7 100644 --- a/blockchain/storage/leveldb/leveldb_test.go +++ b/blockchain/storage/leveldb/leveldb_test.go @@ -54,7 +54,7 @@ func generateTxs(t *testing.T, startNonce, count int, from types.Address, to *ty txs := make([]*types.Transaction, count) for i := range txs { - tx := &types.Transaction{ + tx := types.NewTx(&types.MixedTxn{ Gas: types.StateTransactionGasLimit, Nonce: uint64(startNonce + i), From: from, @@ -63,7 +63,7 @@ func generateTxs(t *testing.T, startNonce, count int, from types.Address, to *ty Type: types.DynamicFeeTx, GasFeeCap: big.NewInt(100), GasTipCap: big.NewInt(10), - } + }) input := make([]byte, 1000) _, err := rand.Read(input) @@ -110,7 +110,7 @@ func generateBlock(t *testing.T, num uint64) *types.FullBlock { for i := 0; i < len(b.Block.Transactions); i++ { b.Receipts[i] = &types.Receipt{ - TxHash: b.Block.Transactions[i].Hash, + TxHash: b.Block.Transactions[i].Hash(), Root: types.StringToHash("mockhashstring"), TransactionType: types.LegacyTx, GasUsed: uint64(100000), @@ -234,7 +234,7 @@ insertloop: batchWriter.PutBody(b.Block.Hash(), b.Block.Body()) for _, tx := range b.Block.Transactions { - batchWriter.PutTxLookup(tx.Hash, b.Block.Hash()) + batchWriter.PutTxLookup(tx.Hash(), b.Block.Hash()) } batchWriter.PutHeader(b.Block.Header) diff --git a/blockchain/storage/testing.go b/blockchain/storage/testing.go index 45615363ad..c947251a55 100644 --- a/blockchain/storage/testing.go +++ b/blockchain/storage/testing.go @@ -270,7 +270,7 @@ func testBody(t *testing.T, m PlaceholderStorage) { require.NoError(t, batch.WriteBatch()) addr1 := types.StringToAddress("11") - t0 := &types.Transaction{ + t0 := types.NewTx(&types.MixedTxn{ Nonce: 0, To: &addr1, Value: big.NewInt(1), @@ -278,11 +278,11 @@ func testBody(t *testing.T, m PlaceholderStorage) { GasPrice: big.NewInt(11), Input: []byte{1, 2}, V: big.NewInt(1), - } + }) t0.ComputeHash() addr2 := types.StringToAddress("22") - t1 := &types.Transaction{ + t1 := types.NewTx(&types.MixedTxn{ Nonce: 0, To: &addr2, Value: big.NewInt(1), @@ -290,7 +290,7 @@ func testBody(t *testing.T, m PlaceholderStorage) { GasPrice: big.NewInt(11), Input: []byte{4, 5}, V: big.NewInt(2), - } + }) t1.ComputeHash() block := types.Block{ @@ -315,7 +315,7 @@ func testBody(t *testing.T, m PlaceholderStorage) { } for indx, i := range tx0 { - if i.Hash != tx1[indx].Hash { + if i.Hash() != tx1[indx].Hash() { t.Fatal("tx not correct") } } @@ -338,19 +338,19 @@ func testReceipts(t *testing.T, m PlaceholderStorage) { body := &types.Body{ Transactions: []*types.Transaction{ - { + types.NewTx(&types.MixedTxn{ Nonce: 1000, Gas: 50, GasPrice: new(big.Int).SetUint64(100), V: big.NewInt(11), - }, + }), }, } receipts := []*types.Receipt{ { Root: types.StringToHash("1"), CumulativeGasUsed: 10, - TxHash: body.Transactions[0].Hash, + TxHash: body.Transactions[0].Hash(), LogsBloom: types.Bloom{0x1}, Logs: []*types.Log{ { @@ -367,7 +367,7 @@ func testReceipts(t *testing.T, m PlaceholderStorage) { { Root: types.StringToHash("1"), CumulativeGasUsed: 10, - TxHash: body.Transactions[0].Hash, + TxHash: body.Transactions[0].Hash(), LogsBloom: types.Bloom{0x1}, GasUsed: 10, ContractAddress: &types.Address{0x1}, diff --git a/blockchain/testing.go b/blockchain/testing.go index bac1212829..94b5f4eb43 100644 --- a/blockchain/testing.go +++ b/blockchain/testing.go @@ -326,7 +326,7 @@ type mockSigner struct { } func (m *mockSigner) Sender(tx *types.Transaction) (types.Address, error) { - if from, ok := m.txFromByTxHash[tx.Hash]; ok { + if from, ok := m.txFromByTxHash[tx.Hash()]; ok { return from, nil } diff --git a/chain/params.go b/chain/params.go index 646c18a56c..1847a0d1b2 100644 --- a/chain/params.go +++ b/chain/params.go @@ -93,6 +93,7 @@ const ( EIP155 = "EIP155" Governance = "governance" EIP3855 = "EIP3855" + Berlin = "Berlin" EIP3607 = "EIP3607" ) @@ -131,6 +132,7 @@ func (f *Forks) At(block uint64) ForksInTime { EIP155: f.IsActive(EIP155, block), Governance: f.IsActive(Governance, block), EIP3855: f.IsActive(EIP3855, block), + Berlin: f.IsActive(Berlin, block), EIP3607: f.IsActive(EIP3607, block), } } @@ -184,6 +186,7 @@ type ForksInTime struct { EIP155, Governance, EIP3855, + Berlin, EIP3607 bool } @@ -200,5 +203,6 @@ var AllForksEnabled = &Forks{ London: NewFork(0), Governance: NewFork(0), EIP3855: NewFork(0), + Berlin: NewFork(0), EIP3607: NewFork(0), } diff --git a/consensus/dev/dev.go b/consensus/dev/dev.go index c10108b950..24fda3c40e 100644 --- a/consensus/dev/dev.go +++ b/consensus/dev/dev.go @@ -121,7 +121,7 @@ func (d *Dev) writeTransactions(gasLimit uint64, transition transitionInterface) break } - if tx.Gas > gasLimit { + if tx.Gas() > gasLimit { d.txpool.Drop(tx) continue diff --git a/consensus/polybft/block_builder.go b/consensus/polybft/block_builder.go index 67e2e7d54d..1849faa1db 100644 --- a/consensus/polybft/block_builder.go +++ b/consensus/polybft/block_builder.go @@ -139,9 +139,9 @@ func (b *BlockBuilder) Build(handler func(h *types.Header)) (*types.FullBlock, e // WriteTx applies given transaction to the state. If transaction apply fails, it reverts the saved snapshot. func (b *BlockBuilder) WriteTx(tx *types.Transaction) error { - if tx.Gas > b.params.GasLimit { - b.params.Logger.Info("Transaction gas limit exceedes block gas limit", "hash", tx.Hash, - "tx gas limit", tx.Gas, "block gas limt", b.params.GasLimit) + if tx.Gas() > b.params.GasLimit { + b.params.Logger.Info("Transaction gas limit exceedes block gas limit", "hash", tx.Hash(), + "tx gas limit", tx.Gas(), "block gas limt", b.params.GasLimit) return txpool.ErrBlockLimitExceeded } @@ -171,7 +171,7 @@ write: // execute transactions one by one finished, err := b.writeTxPoolTransaction(tx) if err != nil { - b.params.Logger.Debug("Fill transaction error", "hash", tx.Hash, "err", err) + b.params.Logger.Debug("Fill transaction error", "hash", tx.Hash(), "err", err) } if finished { diff --git a/consensus/polybft/block_builder_test.go b/consensus/polybft/block_builder_test.go index 9c0bdef87b..bce6cbb733 100644 --- a/consensus/polybft/block_builder_test.go +++ b/consensus/polybft/block_builder_test.go @@ -1,12 +1,12 @@ package polybft import ( + "crypto/ecdsa" "math/big" "testing" "time" "github.com/0xPolygon/polygon-edge/chain" - "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" "github.com/0xPolygon/polygon-edge/crypto" "github.com/0xPolygon/polygon-edge/helper/common" "github.com/0xPolygon/polygon-edge/state" @@ -29,10 +29,21 @@ func TestBlockBuilder_BuildBlockTxOneFailedTxAndOneTakesTooMuchGas(t *testing.T) chainID = 100 ) - accounts := [6]*wallet.Account{} + type account struct { + privKey *ecdsa.PrivateKey + address types.Address + } + + accounts := [6]*account{} for i := range accounts { - accounts[i] = generateTestAccount(t) + ecdsaKey, err := crypto.GenerateECDSAKey() + require.NoError(t, err) + + accounts[i] = &account{ + privKey: ecdsaKey, + address: crypto.PubKeyToAddress(&ecdsaKey.PublicKey), + } } forks := &chain.Forks{} @@ -60,9 +71,7 @@ func TestBlockBuilder_BuildBlockTxOneFailedTxAndOneTakesTooMuchGas(t *testing.T) for i, acc := range accounts { // the third tx will fail because of insufficient balance if i != 2 { - balanceMap[types.Address(acc.Ecdsa.Address())] = &chain.GenesisAccount{ - Balance: ethgo.Ether(1), - } + balanceMap[acc.address] = &chain.GenesisAccount{Balance: ethgo.Ether(1)} } } @@ -72,31 +81,27 @@ func TestBlockBuilder_BuildBlockTxOneFailedTxAndOneTakesTooMuchGas(t *testing.T) require.NotEqual(t, types.ZeroHash, hash) // Gas Limit is important to be high for tx pool - parentHeader := &types.Header{StateRoot: hash, GasLimit: 1_000_000_000_000_000} + parentHeader := &types.Header{StateRoot: hash, GasLimit: 1e15} txPool := &txPoolMock{} txPool.On("Prepare").Once() for i, acc := range accounts { - receiver := types.Address(acc.Ecdsa.Address()) - privateKey, err := acc.GetEcdsaPrivateKey() - - require.NoError(t, err) + gas := uint64(gasLimit) + // fifth tx will cause filling to stop + if i == 4 { + gas = blockGasLimit - 1 + } - tx := &types.Transaction{ + tx := types.NewTx(&types.MixedTxn{ Value: big.NewInt(amount), GasPrice: big.NewInt(gasPrice), - Gas: gasLimit, + Gas: gas, Nonce: 0, - To: &receiver, - } - - // fifth tx will cause filling to stop - if i == 4 { - tx.Gas = blockGasLimit - 1 - } + To: &acc.address, + }) - tx, err = signer.SignTx(tx, privateKey) + tx, err = signer.SignTx(tx, acc.privKey) require.NoError(t, err) // all tx until the fifth will be retrieved from the pool @@ -146,15 +151,17 @@ func TestBlockBuilder_BuildBlockTxOneFailedTxAndOneTakesTooMuchGas(t *testing.T) require.Len(t, bb.txns, 3, "Should have 3 transactions but has %d", len(bb.txns)) require.Len(t, bb.Receipts(), 3) + logsBloom := fb.Block.Header.LogsBloom + // assert logs bloom for _, r := range bb.Receipts() { for _, l := range r.Logs { - assert.True(t, fb.Block.Header.LogsBloom.IsLogInBloom(l)) + assert.True(t, logsBloom.IsLogInBloom(l)) } } - assert.False(t, fb.Block.Header.LogsBloom.IsLogInBloom( + assert.False(t, logsBloom.IsLogInBloom( &types.Log{Address: types.StringToAddress("999911117777")})) - assert.False(t, fb.Block.Header.LogsBloom.IsLogInBloom( + assert.False(t, logsBloom.IsLogInBloom( &types.Log{Address: types.StringToAddress("111177779999")})) } diff --git a/consensus/polybft/blockchain_wrapper.go b/consensus/polybft/blockchain_wrapper.go index 98b3ebbe9f..902f2bca19 100644 --- a/consensus/polybft/blockchain_wrapper.go +++ b/consensus/polybft/blockchain_wrapper.go @@ -98,7 +98,7 @@ func (p *blockchainWrapper) ProcessBlock(parent *types.Header, block *types.Bloc // apply transactions from block for _, tx := range block.Transactions { if err = transition.Write(tx); err != nil { - return nil, fmt.Errorf("process block tx error, tx = %v, err = %w", tx.Hash, err) + return nil, fmt.Errorf("process block tx error, tx = %v, err = %w", tx.Hash(), err) } } @@ -212,7 +212,13 @@ func NewStateProvider(transition *state.Transition) contract.Provider { // Call implements the contract.Provider interface to make contract calls directly to the state func (s *stateProvider) Call(addr ethgo.Address, input []byte, opts *contract.CallOpts) ([]byte, error) { - result := s.transition.Call2(contracts.SystemCaller, types.Address(addr), input, big.NewInt(0), 10000000) + result := s.transition.Call2( + contracts.SystemCaller, + types.Address(addr), + input, + big.NewInt(0), + 10000000, + ) if result.Failed() { return nil, result.Err } diff --git a/consensus/polybft/consensus_runtime.go b/consensus/polybft/consensus_runtime.go index 307845b912..5b480f51a4 100644 --- a/consensus/polybft/consensus_runtime.go +++ b/consensus/polybft/consensus_runtime.go @@ -197,7 +197,6 @@ func (c *consensusRuntime) initStakeManager(logger hcf.Logger, dbTx *bolt.Tx) er c.stakeManager, err = newStakeManager( logger.Named("stake-manager"), c.state, - wallet.NewEcdsaSigner(c.config.Key), contracts.StakeManagerContract, c.config.blockchain, c.config.polybftBackend, diff --git a/consensus/polybft/fsm.go b/consensus/polybft/fsm.go index bc2db22709..ca1e81691f 100644 --- a/consensus/polybft/fsm.go +++ b/consensus/polybft/fsm.go @@ -433,28 +433,28 @@ func (f *fsm) VerifyStateTransactions(transactions []*types.Transaction) error { ) for _, tx := range transactions { - if tx.Type != types.StateTx { + if tx.Type() != types.StateTx { continue } - decodedStateTx, err := decodeStateTransaction(tx.Input) + decodedStateTx, err := decodeStateTransaction(tx.Input()) if err != nil { - return fmt.Errorf("unknown state transaction: tx = %v, err = %w", tx.Hash, err) + return fmt.Errorf("unknown state transaction: tx = %v, err = %w", tx.Hash(), err) } switch stateTxData := decodedStateTx.(type) { case *CommitmentMessageSigned: if !f.isEndOfSprint { - return fmt.Errorf("found commitment tx in block which should not contain it (tx hash=%s)", tx.Hash) + return fmt.Errorf("found commitment tx in block which should not contain it (tx hash=%s)", tx.Hash()) } if commitmentTxExists { - return fmt.Errorf("only one commitment tx is allowed per block (tx hash=%s)", tx.Hash) + return fmt.Errorf("only one commitment tx is allowed per block (tx hash=%s)", tx.Hash()) } commitmentTxExists = true - if err = verifyBridgeCommitmentTx(f.Height(), tx.Hash, stateTxData, f.validators); err != nil { + if err = verifyBridgeCommitmentTx(f.Height(), tx.Hash(), stateTxData, f.validators); err != nil { return err } case *contractsapi.CommitEpochEpochManagerFn: @@ -598,11 +598,11 @@ func (f *fsm) verifyCommitEpochTx(commitEpochTx *types.Transaction) error { return err } - if commitEpochTx.Hash != localCommitEpochTx.Hash { + if commitEpochTx.Hash() != localCommitEpochTx.Hash() { return fmt.Errorf( "invalid commit epoch transaction. Expected '%s', but got '%s' commit epoch transaction hash", - localCommitEpochTx.Hash, - commitEpochTx.Hash, + localCommitEpochTx.Hash(), + commitEpochTx.Hash(), ) } @@ -622,11 +622,11 @@ func (f *fsm) verifyDistributeRewardsTx(distributeRewardsTx *types.Transaction) return err } - if distributeRewardsTx.Hash != localDistributeRewardsTx.Hash { + if distributeRewardsTx.Hash() != localDistributeRewardsTx.Hash() { return fmt.Errorf( "invalid distribute rewards transaction. Expected '%s', but got '%s' distribute rewards hash", - localDistributeRewardsTx.Hash, - distributeRewardsTx.Hash, + localDistributeRewardsTx.Hash(), + distributeRewardsTx.Hash(), ) } @@ -716,14 +716,14 @@ func validateHeaderFields(parent *types.Header, header *types.Header, blockTimeD // createStateTransactionWithData creates a state transaction // with provided target address and inputData parameter which is ABI encoded byte array. func createStateTransactionWithData(target types.Address, inputData []byte) *types.Transaction { - tx := &types.Transaction{ + tx := types.NewTx(&types.MixedTxn{ From: contracts.SystemCaller, To: &target, Type: types.StateTx, Input: inputData, Gas: types.StateTransactionGasLimit, GasPrice: big.NewInt(0), - } + }) return tx.ComputeHash() } diff --git a/consensus/polybft/fsm_test.go b/consensus/polybft/fsm_test.go index ae5a3dfc2b..e65b53f4b5 100644 --- a/consensus/polybft/fsm_test.go +++ b/consensus/polybft/fsm_test.go @@ -100,12 +100,12 @@ func TestFSM_verifyCommitEpochTx(t *testing.T) { assert.NoError(t, fsm.verifyCommitEpochTx(commitEpochTx)) // submit tampered commit epoch transaction to the epoch ending block - alteredCommitEpochTx := &types.Transaction{ + alteredCommitEpochTx := types.NewTx(&types.MixedTxn{ To: &contracts.EpochManagerContract, Input: []byte{}, Gas: 0, Type: types.StateTx, - } + }) assert.ErrorContains(t, fsm.verifyCommitEpochTx(alteredCommitEpochTx), "invalid commit epoch transaction") // submit validators commit epoch transaction to the non-epoch ending block @@ -1498,7 +1498,7 @@ func TestFSM_DecodeCommitmentStateTxs(t *testing.T) { bridgeCommitmentTx, err := f.createBridgeCommitmentTx() require.NoError(t, err) - decodedData, err := decodeStateTransaction(bridgeCommitmentTx.Input) + decodedData, err := decodeStateTransaction(bridgeCommitmentTx.Input()) require.NoError(t, err) decodedCommitmentMsg, ok := decodedData.(*CommitmentMessageSigned) @@ -1515,7 +1515,7 @@ func TestFSM_DecodeCommitEpochStateTx(t *testing.T) { require.NotNil(t, input) tx := createStateTransactionWithData(contracts.EpochManagerContract, input) - decodedInputData, err := decodeStateTransaction(tx.Input) + decodedInputData, err := decodeStateTransaction(tx.Input()) require.NoError(t, err) decodedCommitEpoch, ok := decodedInputData.(*contractsapi.CommitEpochEpochManagerFn) diff --git a/consensus/polybft/polybft.go b/consensus/polybft/polybft.go index 12191d0316..9c61613ecb 100644 --- a/consensus/polybft/polybft.go +++ b/consensus/polybft/polybft.go @@ -781,25 +781,25 @@ func (p *Polybft) PreCommitState(block *types.Block, _ *state.Transition) error // validate commitment state transactions for _, tx := range block.Transactions { - if tx.Type != types.StateTx { + if tx.Type() != types.StateTx { continue } - decodedStateTx, err := decodeStateTransaction(tx.Input) + decodedStateTx, err := decodeStateTransaction(tx.Input()) if err != nil { - return fmt.Errorf("unknown state transaction: tx=%v, error: %w", tx.Hash, err) + return fmt.Errorf("unknown state transaction: tx=%v, error: %w", tx.Hash(), err) } if signedCommitment, ok := decodedStateTx.(*CommitmentMessageSigned); ok { if commitmentTxExists { - return fmt.Errorf("only one commitment state tx is allowed per block: %v", tx.Hash) + return fmt.Errorf("only one commitment state tx is allowed per block: %v", tx.Hash()) } commitmentTxExists = true if err := verifyBridgeCommitmentTx( block.Number(), - tx.Hash, + tx.Hash(), signedCommitment, validator.NewValidatorSet(validators, p.logger)); err != nil { return err diff --git a/consensus/polybft/stake_manager.go b/consensus/polybft/stake_manager.go index e14b620e15..20009c174e 100644 --- a/consensus/polybft/stake_manager.go +++ b/consensus/polybft/stake_manager.go @@ -79,7 +79,6 @@ type stakeManager struct { func newStakeManager( logger hclog.Logger, state *State, - key ethgo.Key, stakeManagerAddr types.Address, blockchain blockchainBackend, polybftBackend polybftBackend, @@ -88,7 +87,6 @@ func newStakeManager( sm := &stakeManager{ logger: logger, state: state, - key: key, stakeManagerContractAddr: stakeManagerAddr, polybftBackend: polybftBackend, blockchain: blockchain, diff --git a/consensus/polybft/stake_manager_fuzz_test.go b/consensus/polybft/stake_manager_fuzz_test.go index e7d9158e84..eccbf132be 100644 --- a/consensus/polybft/stake_manager_fuzz_test.go +++ b/consensus/polybft/stake_manager_fuzz_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/0xPolygon/polygon-edge/consensus/polybft/validator" - "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" "github.com/0xPolygon/polygon-edge/types" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/mock" @@ -111,7 +110,6 @@ func FuzzTestStakeManagerPostBlock(f *testing.F) { stakeManager, err := newStakeManager( hclog.NewNullLogger(), state, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), types.StringToAddress("0x0002"), bcMock, nil, @@ -154,7 +152,6 @@ func FuzzTestStakeManagerUpdateValidatorSet(f *testing.F) { stakeManager, err := newStakeManager( hclog.NewNullLogger(), state, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), types.StringToAddress("0x0001"), bcMock, nil, diff --git a/consensus/polybft/stake_manager_test.go b/consensus/polybft/stake_manager_test.go index 87aa1bc1db..283ead1fc1 100644 --- a/consensus/polybft/stake_manager_test.go +++ b/consensus/polybft/stake_manager_test.go @@ -6,7 +6,6 @@ import ( "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" "github.com/0xPolygon/polygon-edge/consensus/polybft/validator" - "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" "github.com/0xPolygon/polygon-edge/helper/hex" "github.com/0xPolygon/polygon-edge/txrelayer" "github.com/0xPolygon/polygon-edge/types" @@ -52,7 +51,6 @@ func TestStakeManager_PostBlock(t *testing.T) { stakeManager, err := newStakeManager( hclog.NewNullLogger(), state, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), stakeManagerAddr, bcMock, nil, @@ -110,7 +108,6 @@ func TestStakeManager_PostBlock(t *testing.T) { stakeManager, err := newStakeManager( hclog.NewNullLogger(), state, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), types.StringToAddress("0x0001"), bcMock, nil, @@ -175,7 +172,6 @@ func TestStakeManager_PostBlock(t *testing.T) { stakeManager, err := newStakeManager( hclog.NewNullLogger(), state, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), types.StringToAddress("0x0001"), bcMock, nil, @@ -233,7 +229,6 @@ func TestStakeManager_UpdateValidatorSet(t *testing.T) { stakeManager, err := newStakeManager( hclog.NewNullLogger(), state, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), types.StringToAddress("0x0001"), bcMock, nil, @@ -430,7 +425,6 @@ func TestStakeManager_UpdateOnInit(t *testing.T) { _, err := newStakeManager( hclog.NewNullLogger(), state, - wallet.NewEcdsaSigner(validators.GetValidator("A").Key()), stakeManagerAddr, nil, polyBackendMock, diff --git a/consensus/polybft/state_sync_commitment.go b/consensus/polybft/state_sync_commitment.go index 1d9e50cbba..f352789c0b 100644 --- a/consensus/polybft/state_sync_commitment.go +++ b/consensus/polybft/state_sync_commitment.go @@ -165,15 +165,15 @@ func getCommitmentMessageSignedTx(txs []*types.Transaction) (*CommitmentMessageS var commitFn contractsapi.CommitStateReceiverFn for _, tx := range txs { // skip non state CommitmentMessageSigned transactions - if tx.Type != types.StateTx || - len(tx.Input) < abiMethodIDLength || - !bytes.Equal(tx.Input[:abiMethodIDLength], commitFn.Sig()) { + if tx.Type() != types.StateTx || + len(tx.Input()) < abiMethodIDLength || + !bytes.Equal(tx.Input()[:abiMethodIDLength], commitFn.Sig()) { continue } obj := &CommitmentMessageSigned{} - if err := obj.DecodeAbi(tx.Input); err != nil { + if err := obj.DecodeAbi(tx.Input()); err != nil { return nil, fmt.Errorf("get commitment message signed tx error: %w", err) } diff --git a/consensus/polybft/wallet/account.go b/consensus/polybft/wallet/account.go index 992da93e62..7fb7477005 100644 --- a/consensus/polybft/wallet/account.go +++ b/consensus/polybft/wallet/account.go @@ -5,10 +5,11 @@ import ( "encoding/hex" "fmt" + "github.com/umbracle/ethgo/wallet" + "github.com/0xPolygon/polygon-edge/bls" "github.com/0xPolygon/polygon-edge/secrets" "github.com/0xPolygon/polygon-edge/types" - "github.com/umbracle/ethgo/wallet" ) // Account is an account for key signatures diff --git a/consensus/polybft/wallet/key.go b/consensus/polybft/wallet/key.go index fcf7ad9353..1919de1845 100644 --- a/consensus/polybft/wallet/key.go +++ b/consensus/polybft/wallet/key.go @@ -65,7 +65,7 @@ func (k *Key) SignIBFTMessage(msg *proto.Message) (*proto.Message, error) { // RecoverAddressFromSignature calculates keccak256 hash of provided rawContent // and recovers signer address from given signature and hash func RecoverAddressFromSignature(sig, rawContent []byte) (types.Address, error) { - pub, err := crypto.RecoverPubkey(sig, crypto.Keccak256(rawContent)) + pub, err := crypto.RecoverPubKey(sig, crypto.Keccak256(rawContent)) if err != nil { return types.Address{}, fmt.Errorf("cannot recover address from signature: %w", err) } diff --git a/crypto/crypto.go b/crypto/crypto.go index 2c44599ce7..4f8d2a1629 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -10,18 +10,17 @@ import ( "hash" "math/big" + "github.com/btcsuite/btcd/btcec/v2" + btc_ecdsa "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/umbracle/fastrlp" + "golang.org/x/crypto/sha3" + "github.com/0xPolygon/polygon-edge/helper/hex" "github.com/0xPolygon/polygon-edge/helper/keystore" "github.com/0xPolygon/polygon-edge/secrets" "github.com/0xPolygon/polygon-edge/types" - "github.com/btcsuite/btcd/btcec" - "github.com/umbracle/fastrlp" - "golang.org/x/crypto/sha3" ) -// S256 is the secp256k1 elliptic curve -var S256 = btcec.S256() - var ( secp256k1N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16) secp256k1NHalf = new(big.Int).Div(secp256k1N, big.NewInt(2)) @@ -29,7 +28,6 @@ var ( one = big.NewInt(1) ErrInvalidBLSSignature = errors.New("invalid BLS Signature") - errZeroHash = errors.New("can not recover public key from zero or empty message hash") errHashOfInvalidLength = errors.New("message hash of invalid length") errInvalidSignature = errors.New("invalid signature") ) @@ -41,6 +39,18 @@ const ( KeyBLS KeyType = "bls" ) +const ( + // ECDSASignatureLength indicates the byte length required to carry a signature with recovery id. + // (64 bytes ECDSA signature + 1 byte recovery id) + ECDSASignatureLength = 64 + 1 + + // recoveryID is ECDSA signature recovery id + recoveryID = byte(27) + + // recoveryIDOffset points to the byte offset within the signature that contains the recovery id. + recoveryIDOffset = 64 +) + // KeccakState wraps sha3.state. In addition to the usual hash methods, it also supports // Read to get a variable amount of data from the hash state. Read is faster than Sum // because it doesn't copy the internal state, but also modifies the internal state. @@ -100,38 +110,45 @@ func CreateAddress2(addr types.Address, salt [32]byte, inithash []byte) types.Ad } func ParseECDSAPrivateKey(buf []byte) (*ecdsa.PrivateKey, error) { - prv, _ := btcec.PrivKeyFromBytes(S256, buf) + prv, _ := btcec.PrivKeyFromBytes(buf) return prv.ToECDSA(), nil } // MarshalECDSAPrivateKey serializes the private key's D value to a []byte func MarshalECDSAPrivateKey(priv *ecdsa.PrivateKey) ([]byte, error) { - return (*btcec.PrivateKey)(priv).Serialize(), nil + btcPriv, err := convertToBtcPrivKey(priv) + if err != nil { + return nil, err + } + + defer btcPriv.Zero() + + return btcPriv.Serialize(), nil } // GenerateECDSAKey generates a new key based on the secp256k1 elliptic curve. func GenerateECDSAKey() (*ecdsa.PrivateKey, error) { - return ecdsa.GenerateKey(S256, rand.Reader) + return ecdsa.GenerateKey(btcec.S256(), rand.Reader) } // ParsePublicKey parses bytes into a public key on the secp256k1 elliptic curve. func ParsePublicKey(buf []byte) (*ecdsa.PublicKey, error) { - x, y := elliptic.Unmarshal(S256, buf) + x, y := elliptic.Unmarshal(btcec.S256(), buf) if x == nil || y == nil { return nil, fmt.Errorf("cannot unmarshal") } - return &ecdsa.PublicKey{Curve: S256, X: x, Y: y}, nil + return &ecdsa.PublicKey{Curve: btcec.S256(), X: x, Y: y}, nil } // MarshalPublicKey marshals a public key on the secp256k1 elliptic curve. func MarshalPublicKey(pub *ecdsa.PublicKey) []byte { - return elliptic.Marshal(S256, pub.X, pub.Y) + return elliptic.Marshal(btcec.S256(), pub.X, pub.Y) } func Ecrecover(hash, sig []byte) ([]byte, error) { - pub, err := RecoverPubkey(sig, hash) + pub, err := RecoverPubKey(sig, hash) if err != nil { return nil, err } @@ -139,28 +156,23 @@ func Ecrecover(hash, sig []byte) ([]byte, error) { return MarshalPublicKey(pub), nil } -// RecoverPubkey verifies the compact signature "signature" of "hash" for the -// secp256k1 curve. -func RecoverPubkey(signature, hash []byte) (*ecdsa.PublicKey, error) { +// RecoverPubKey verifies the compact signature "signature" of "hash" for the secp256k1 curve. +func RecoverPubKey(signature, hash []byte) (*ecdsa.PublicKey, error) { if len(hash) != types.HashLength { return nil, errHashOfInvalidLength } - size := len(signature) - term := byte(27) - - // Make sure the signature is present - if signature == nil || size < 1 { + signatureSize := len(signature) + if signatureSize != ECDSASignatureLength { return nil, errInvalidSignature } - if signature[size-1] == 1 { - term = 28 - } - - sig := append([]byte{term}, signature[:size-1]...) - pub, _, err := btcec.RecoverCompact(S256, sig, hash) + // Convert to btcec input format with 'recovery id' v at the beginning. + btcsig := make([]byte, signatureSize) + btcsig[0] = signature[signatureSize-1] + recoveryID + copy(btcsig[1:], signature) + pub, _, err := btc_ecdsa.RecoverCompact(btcsig, hash) if err != nil { return nil, err } @@ -168,20 +180,38 @@ func RecoverPubkey(signature, hash []byte) (*ecdsa.PublicKey, error) { return pub.ToECDSA(), nil } -// Sign produces a compact signature of the data in hash with the given +// Sign produces an ECDSA signature of the data in hash with the given // private key on the secp256k1 curve. + +// The produced signature is in the [R || S || V] format where V is 0 or 1. func Sign(priv *ecdsa.PrivateKey, hash []byte) ([]byte, error) { - sig, err := btcec.SignCompact(S256, (*btcec.PrivateKey)(priv), hash, false) + if len(hash) != types.HashLength { + return nil, fmt.Errorf("hash is required to be exactly %d bytes (%d)", types.HashLength, len(hash)) + } + + if priv.Curve != btcec.S256() { + return nil, errors.New("private key curve is not secp256k1") + } + + // convert from ecdsa.PrivateKey to btcec.PrivateKey + btcPrivKey, err := convertToBtcPrivKey(priv) if err != nil { return nil, err } - term := byte(0) - if sig[0] == 28 { - term = 1 + defer btcPrivKey.Zero() + + sig, err := btc_ecdsa.SignCompact(btcPrivKey, hash, false) + if err != nil { + return nil, err } - return append(sig, term)[1:], nil + // Convert to Ethereum signature format with 'recovery id' v at the end. + v := sig[0] - recoveryID + copy(sig, sig[1:]) + sig[recoveryIDOffset] = v + + return sig, nil } // Keccak256 calculates the Keccak256 @@ -309,3 +339,16 @@ func ReadConsensusKey(manager secrets.SecretsManager) (*ecdsa.PrivateKey, error) return BytesToECDSAPrivateKey(validatorKey) } + +// convertToBtcPrivKey converts provided ECDSA private key to btc private key format +// used by btcec library +func convertToBtcPrivKey(priv *ecdsa.PrivateKey) (*btcec.PrivateKey, error) { + var btcPriv btcec.PrivateKey + + overflow := btcPriv.Key.SetByteSlice(priv.D.Bytes()) + if overflow || btcPriv.Key.IsZero() { + return nil, errors.New("invalid private key") + } + + return &btcPriv, nil +} diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 6cd8419b60..613101338f 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -445,14 +445,14 @@ func TestRecoverPublicKey(t *testing.T) { t.Run("Empty hash", func(t *testing.T) { t.Parallel() - _, err := RecoverPubkey(testSignature, []byte{}) + _, err := RecoverPubKey(testSignature, []byte{}) require.ErrorIs(t, err, errHashOfInvalidLength) }) t.Run("Hash of non appropriate length", func(t *testing.T) { t.Parallel() - _, err := RecoverPubkey(testSignature, []byte{0, 1}) + _, err := RecoverPubKey(testSignature, []byte{0, 1}) require.ErrorIs(t, err, errHashOfInvalidLength) }) @@ -467,7 +467,7 @@ func TestRecoverPublicKey(t *testing.T) { signature, err := Sign(privateKey, hash.Bytes()) require.NoError(t, err) - publicKey, err := RecoverPubkey(signature, hash.Bytes()) + publicKey, err := RecoverPubKey(signature, hash.Bytes()) require.NoError(t, err) require.True(t, privateKey.PublicKey.Equal(publicKey)) diff --git a/crypto/txsigner.go b/crypto/txsigner.go index ff995be483..530bca94ad 100644 --- a/crypto/txsigner.go +++ b/crypto/txsigner.go @@ -2,6 +2,7 @@ package crypto import ( "crypto/ecdsa" + "errors" "fmt" "math/big" @@ -40,8 +41,8 @@ func NewSigner(forks chain.ForksInTime, chainID uint64) TxSigner { // London signer requires a fallback signer that is defined above. // This is the reason why the london signer check is separated. - if forks.London { - return NewLondonSigner(chainID, forks.Homestead, signer) + if forks.London || forks.Berlin { + return NewLondonOrBerlinSigner(chainID, forks.Homestead, signer) } return signer @@ -61,6 +62,30 @@ func encodeSignature(R, S, V *big.Int, isHomestead bool) ([]byte, error) { return sig, nil } +// recoverAddress recovers the sender address from a transaction hash and signature parameters. +// It takes the transaction hash, r, s, v values of the signature, +// and a flag indicating if the transaction is in the Homestead format. +// It returns the recovered address and an error if any. +func recoverAddress(txHash types.Hash, r, s, v *big.Int, isHomestead bool) (types.Address, error) { + sig, err := encodeSignature(r, s, v, isHomestead) + if err != nil { + return types.ZeroAddress, err + } + + pub, err := Ecrecover(txHash.Bytes(), sig) + if err != nil { + return types.ZeroAddress, err + } + + if len(pub) == 0 || pub[0] != 4 { + return types.ZeroAddress, errors.New("invalid public key") + } + + buf := Keccak256(pub[1:])[12:] + + return types.BytesToAddress(buf), nil +} + // calcTxHash calculates the transaction hash (keccak256 hash of the RLP value) // LegacyTx: // keccak256(RLP(nonce, gasPrice, gas, to, value, input, chainId, 0, 0)) @@ -69,53 +94,120 @@ func encodeSignature(R, S, V *big.Int, isHomestead bool) ([]byte, error) { // DynamicFeeTx: // keccak256(RLP(type, chainId, nonce, gasTipCap, gasFeeCap, gas, to, value, input, accessList)) func calcTxHash(tx *types.Transaction, chainID uint64) types.Hash { - a := signerPool.Get() - defer signerPool.Put(a) + var hash []byte - v := a.NewArray() + switch tx.Type() { + case types.AccessListTx: + a := signerPool.Get() + v := a.NewArray() - if tx.Type != types.LegacyTx { v.Set(a.NewUint(chainID)) - } + v.Set(a.NewUint(tx.Nonce())) + v.Set(a.NewBigInt(tx.GasPrice())) + v.Set(a.NewUint(tx.Gas())) + + if tx.To() == nil { + v.Set(a.NewNull()) + } else { + v.Set(a.NewCopyBytes((*(tx.To())).Bytes())) + } - v.Set(a.NewUint(tx.Nonce)) + v.Set(a.NewBigInt(tx.Value())) + v.Set(a.NewCopyBytes(tx.Input())) - if tx.Type == types.DynamicFeeTx { - v.Set(a.NewBigInt(tx.GasTipCap)) - v.Set(a.NewBigInt(tx.GasFeeCap)) - } else { - v.Set(a.NewBigInt(tx.GasPrice)) - } + // add accessList + accessListVV := a.NewArray() - v.Set(a.NewUint(tx.Gas)) + if tx.AccessList() != nil { + for _, accessTuple := range tx.AccessList() { + accessTupleVV := a.NewArray() + accessTupleVV.Set(a.NewCopyBytes(accessTuple.Address.Bytes())) - if tx.To == nil { - v.Set(a.NewNull()) - } else { - v.Set(a.NewCopyBytes((*tx.To).Bytes())) - } + storageKeysVV := a.NewArray() + for _, storageKey := range accessTuple.StorageKeys { + storageKeysVV.Set(a.NewCopyBytes(storageKey.Bytes())) + } + + accessTupleVV.Set(storageKeysVV) + accessListVV.Set(accessTupleVV) + } + } + + v.Set(accessListVV) - v.Set(a.NewBigInt(tx.Value)) - v.Set(a.NewCopyBytes(tx.Input)) + hash = keccak.PrefixedKeccak256Rlp([]byte{byte(tx.Type())}, nil, v) - if tx.Type == types.LegacyTx { - // EIP155 - if chainID != 0 { + signerPool.Put(a) + + return types.BytesToHash(hash) + + case types.DynamicFeeTx, types.LegacyTx, types.StateTx: + a := signerPool.Get() + isDynamicFeeTx := tx.Type() == types.DynamicFeeTx + + v := a.NewArray() + + if isDynamicFeeTx { v.Set(a.NewUint(chainID)) - v.Set(a.NewUint(0)) - v.Set(a.NewUint(0)) } - } else { - //nolint:godox - // TODO: Introduce AccessList - v.Set(a.NewArray()) - } - var hash []byte - if tx.Type == types.LegacyTx { - hash = keccak.Keccak256Rlp(nil, v) - } else { - hash = keccak.PrefixedKeccak256Rlp([]byte{byte(tx.Type)}, nil, v) + v.Set(a.NewUint(tx.Nonce())) + + if isDynamicFeeTx { + v.Set(a.NewBigInt(tx.GasTipCap())) + v.Set(a.NewBigInt(tx.GasFeeCap())) + } else { + v.Set(a.NewBigInt(tx.GasPrice())) + } + + v.Set(a.NewUint(tx.Gas())) + + if tx.To() == nil { + v.Set(a.NewNull()) + } else { + v.Set(a.NewCopyBytes((*(tx.To())).Bytes())) + } + + v.Set(a.NewBigInt(tx.Value())) + + v.Set(a.NewCopyBytes(tx.Input())) + + if isDynamicFeeTx { + // Convert TxAccessList to RLP format and add it to the vv array. + accessListVV := a.NewArray() + + if tx.AccessList() != nil { + for _, accessTuple := range tx.AccessList() { + accessTupleVV := a.NewArray() + accessTupleVV.Set(a.NewCopyBytes(accessTuple.Address.Bytes())) + + storageKeysVV := a.NewArray() + for _, storageKey := range accessTuple.StorageKeys { + storageKeysVV.Set(a.NewCopyBytes(storageKey.Bytes())) + } + + accessTupleVV.Set(storageKeysVV) + accessListVV.Set(accessTupleVV) + } + } + + v.Set(accessListVV) + } else { + // EIP155 + if chainID != 0 { + v.Set(a.NewUint(chainID)) + v.Set(a.NewUint(0)) + v.Set(a.NewUint(0)) + } + } + + if isDynamicFeeTx { + hash = keccak.PrefixedKeccak256Rlp([]byte{byte(tx.Type())}, nil, v) + } else { + hash = keccak.Keccak256Rlp(nil, v) + } + + signerPool.Put(a) } return types.BytesToHash(hash) diff --git a/crypto/txsigner_eip155.go b/crypto/txsigner_eip155.go index faa394347b..5e330cb977 100644 --- a/crypto/txsigner_eip155.go +++ b/crypto/txsigner_eip155.go @@ -32,8 +32,10 @@ func (e *EIP155Signer) Sender(tx *types.Transaction) (types.Address, error) { // Check if v value conforms to an earlier standard (before EIP155) bigV := big.NewInt(0) - if tx.V != nil { - bigV.SetBytes(tx.V.Bytes()) + + v, r, s := tx.RawSignatureValues() + if v != nil { + bigV.SetBytes(v.Bytes()) } if vv := bigV.Uint64(); bits.Len(uint(vv)) <= 8 { @@ -50,19 +52,7 @@ func (e *EIP155Signer) Sender(tx *types.Transaction) (types.Address, error) { bigV.Sub(bigV, mulOperand) bigV.Sub(bigV, big35) - sig, err := encodeSignature(tx.R, tx.S, bigV, e.isHomestead) - if err != nil { - return types.Address{}, err - } - - pub, err := Ecrecover(e.Hash(tx).Bytes(), sig) - if err != nil { - return types.Address{}, err - } - - buf := Keccak256(pub[1:])[12:] - - return types.BytesToAddress(buf), nil + return recoverAddress(e.Hash(tx), r, s, bigV, e.isHomestead) } // SignTx signs the transaction using the passed in private key @@ -79,9 +69,11 @@ func (e *EIP155Signer) SignTx( return nil, err } - tx.R = new(big.Int).SetBytes(sig[:32]) - tx.S = new(big.Int).SetBytes(sig[32:64]) - tx.V = new(big.Int).SetBytes(e.calculateV(sig[64])) + r := new(big.Int).SetBytes(sig[:32]) + s := new(big.Int).SetBytes(sig[32:64]) + v := new(big.Int).SetBytes(e.calculateV(sig[64])) + + tx.SetSignatureValues(v, r, s) return tx, nil } diff --git a/crypto/txsigner_eip155_test.go b/crypto/txsigner_eip155_test.go index a312b4023f..86769d6c7d 100644 --- a/crypto/txsigner_eip155_test.go +++ b/crypto/txsigner_eip155_test.go @@ -70,11 +70,11 @@ func TestEIP155Signer_Sender(t *testing.T) { t.Fatalf("Unable to generate key") } - txn := &types.Transaction{ + txn := types.NewTx(&types.MixedTxn{ To: &toAddress, Value: big.NewInt(1), GasPrice: big.NewInt(0), - } + }) signer := NewEIP155Signer( testCase.chainID.Uint64(), @@ -106,11 +106,11 @@ func TestEIP155Signer_ChainIDMismatch(t *testing.T) { t.Fatalf("Unable to generate key") } - txn := &types.Transaction{ + txn := types.NewTx(&types.MixedTxn{ To: &toAddress, Value: big.NewInt(1), GasPrice: big.NewInt(0), - } + }) signer := NewEIP155Signer(chainIDTop, true) diff --git a/crypto/txsigner_frontier.go b/crypto/txsigner_frontier.go index f2d2297d75..5365e54562 100644 --- a/crypto/txsigner_frontier.go +++ b/crypto/txsigner_frontier.go @@ -31,25 +31,15 @@ func (f *FrontierSigner) Hash(tx *types.Transaction) types.Hash { // Sender decodes the signature and returns the sender of the transaction func (f *FrontierSigner) Sender(tx *types.Transaction) (types.Address, error) { refV := big.NewInt(0) - if tx.V != nil { - refV.SetBytes(tx.V.Bytes()) - } - - refV.Sub(refV, big27) - sig, err := encodeSignature(tx.R, tx.S, refV, f.isHomestead) - if err != nil { - return types.Address{}, err + v, r, s := tx.RawSignatureValues() + if v != nil { + refV.SetBytes(v.Bytes()) } - pub, err := Ecrecover(f.Hash(tx).Bytes(), sig) - if err != nil { - return types.Address{}, err - } - - buf := Keccak256(pub[1:])[12:] + refV.Sub(refV, big27) - return types.BytesToAddress(buf), nil + return recoverAddress(f.Hash(tx), r, s, refV, f.isHomestead) } // SignTx signs the transaction using the passed in private key @@ -66,9 +56,11 @@ func (f *FrontierSigner) SignTx( return nil, err } - tx.R = new(big.Int).SetBytes(sig[:32]) - tx.S = new(big.Int).SetBytes(sig[32:64]) - tx.V = new(big.Int).SetBytes(f.calculateV(sig[64])) + r := new(big.Int).SetBytes(sig[:32]) + s := new(big.Int).SetBytes(sig[32:64]) + v := new(big.Int).SetBytes(f.calculateV(sig[64])) + + tx.SetSignatureValues(v, r, s) return tx, nil } diff --git a/crypto/txsigner_frontier_test.go b/crypto/txsigner_frontier_test.go index e0d9c3ddfb..aab7ffe3f6 100644 --- a/crypto/txsigner_frontier_test.go +++ b/crypto/txsigner_frontier_test.go @@ -15,11 +15,11 @@ func TestFrontierSigner(t *testing.T) { key, err := GenerateECDSAKey() assert.NoError(t, err) - txn := &types.Transaction{ + txn := types.NewTx(&types.MixedTxn{ To: &toAddress, Value: big.NewInt(10), GasPrice: big.NewInt(0), - } + }) signedTx, err := signer.SignTx(txn, key) assert.NoError(t, err) diff --git a/crypto/txsigner_london.go b/crypto/txsigner_london.go deleted file mode 100644 index 727368b28b..0000000000 --- a/crypto/txsigner_london.go +++ /dev/null @@ -1,79 +0,0 @@ -package crypto - -import ( - "crypto/ecdsa" - "math/big" - - "github.com/0xPolygon/polygon-edge/types" -) - -// LondonSigner implements signer for EIP-1559 -type LondonSigner struct { - chainID uint64 - isHomestead bool - fallbackSigner TxSigner -} - -// NewLondonSigner returns a new LondonSigner object -func NewLondonSigner(chainID uint64, isHomestead bool, fallbackSigner TxSigner) *LondonSigner { - return &LondonSigner{ - chainID: chainID, - isHomestead: isHomestead, - fallbackSigner: fallbackSigner, - } -} - -// Hash is a wrapper function that calls calcTxHash with the LondonSigner's fields -func (e *LondonSigner) Hash(tx *types.Transaction) types.Hash { - return calcTxHash(tx, e.chainID) -} - -// Sender returns the transaction sender -func (e *LondonSigner) Sender(tx *types.Transaction) (types.Address, error) { - // Apply fallback signer for non-dynamic-fee-txs - if tx.Type != types.DynamicFeeTx { - return e.fallbackSigner.Sender(tx) - } - - sig, err := encodeSignature(tx.R, tx.S, tx.V, e.isHomestead) - if err != nil { - return types.Address{}, err - } - - pub, err := Ecrecover(e.Hash(tx).Bytes(), sig) - if err != nil { - return types.Address{}, err - } - - buf := Keccak256(pub[1:])[12:] - - return types.BytesToAddress(buf), nil -} - -// SignTx signs the transaction using the passed in private key -func (e *LondonSigner) SignTx(tx *types.Transaction, pk *ecdsa.PrivateKey) (*types.Transaction, error) { - // Apply fallback signer for non-dynamic-fee-txs - if tx.Type != types.DynamicFeeTx { - return e.fallbackSigner.SignTx(tx, pk) - } - - tx = tx.Copy() - - h := e.Hash(tx) - - sig, err := Sign(pk, h[:]) - if err != nil { - return nil, err - } - - tx.R = new(big.Int).SetBytes(sig[:32]) - tx.S = new(big.Int).SetBytes(sig[32:64]) - tx.V = new(big.Int).SetBytes(e.calculateV(sig[64])) - - return tx, nil -} - -// calculateV returns the V value for transaction signatures. Based on EIP155 -func (e *LondonSigner) calculateV(parity byte) []byte { - return big.NewInt(int64(parity)).Bytes() -} diff --git a/crypto/txsigner_london_berlin.go b/crypto/txsigner_london_berlin.go new file mode 100644 index 0000000000..29f9d21e70 --- /dev/null +++ b/crypto/txsigner_london_berlin.go @@ -0,0 +1,72 @@ +package crypto + +import ( + "crypto/ecdsa" + "math/big" + + "github.com/0xPolygon/polygon-edge/types" +) + +// LondonOrBerlinSigner implements signer for london and berlin hard forks +type LondonOrBerlinSigner struct { + chainID uint64 + isHomestead bool + fallbackSigner TxSigner +} + +// NewLondonOrBerlinSigner returns new LondonOrBerlinSigner object that accepts +// - EIP-1559 dynamic fee transactions +// - EIP-2930 access list transactions, +// - EIP-155 replay protected transactions, and +// - legacy Homestead transactions. +func NewLondonOrBerlinSigner(chainID uint64, isHomestead bool, fallbackSigner TxSigner) *LondonOrBerlinSigner { + return &LondonOrBerlinSigner{ + chainID: chainID, + isHomestead: isHomestead, + fallbackSigner: fallbackSigner, + } +} + +// Hash is a wrapper function that calls calcTxHash with the LondonSigner's fields +func (e *LondonOrBerlinSigner) Hash(tx *types.Transaction) types.Hash { + return calcTxHash(tx, e.chainID) +} + +// Sender returns the transaction sender +func (e *LondonOrBerlinSigner) Sender(tx *types.Transaction) (types.Address, error) { + if tx.Type() != types.DynamicFeeTx && tx.Type() != types.AccessListTx { + return e.fallbackSigner.Sender(tx) + } + + v, r, s := tx.RawSignatureValues() + + return recoverAddress(e.Hash(tx), r, s, v, e.isHomestead) +} + +// SignTx signs the transaction using the passed in private key +func (e *LondonOrBerlinSigner) SignTx(tx *types.Transaction, pk *ecdsa.PrivateKey) (*types.Transaction, error) { + if tx.Type() != types.DynamicFeeTx && tx.Type() != types.AccessListTx { + return e.fallbackSigner.SignTx(tx, pk) + } + + tx = tx.Copy() + + h := e.Hash(tx) + + sig, err := Sign(pk, h[:]) + if err != nil { + return nil, err + } + + r := new(big.Int).SetBytes(sig[:32]) + s := new(big.Int).SetBytes(sig[32:64]) + v := new(big.Int).SetBytes(e.calculateV(sig[64])) + tx.SetSignatureValues(v, r, s) + + return tx, nil +} + +// calculateV returns the V value for transaction signatures. Based on EIP155 +func (e *LondonOrBerlinSigner) calculateV(parity byte) []byte { + return big.NewInt(int64(parity)).Bytes() +} diff --git a/crypto/txsigner_london_test.go b/crypto/txsigner_london_berlin_test.go similarity index 57% rename from crypto/txsigner_london_test.go rename to crypto/txsigner_london_berlin_test.go index 71ed4541cc..a1c989ce80 100644 --- a/crypto/txsigner_london_test.go +++ b/crypto/txsigner_london_berlin_test.go @@ -4,7 +4,6 @@ import ( "math/big" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/umbracle/ethgo" @@ -14,85 +13,90 @@ import ( func TestLondonSignerSender(t *testing.T) { t.Parallel() - toAddress := types.StringToAddress("1") + recipient := types.StringToAddress("1") - testTable := []struct { - name string - chainID *big.Int - isGomestead bool + tcs := []struct { + name string + chainID *big.Int + txType types.TxType }{ { "mainnet", big.NewInt(1), - true, + types.LegacyTx, }, { "expanse mainnet", big.NewInt(2), - true, + types.DynamicFeeTx, }, { "ropsten", big.NewInt(3), - true, + types.DynamicFeeTx, }, { "rinkeby", big.NewInt(4), - true, + types.AccessListTx, }, { "goerli", big.NewInt(5), - true, + types.AccessListTx, }, { "kovan", big.NewInt(42), - true, + types.StateTx, }, { "geth private", big.NewInt(1337), - true, + types.StateTx, }, { "mega large", big.NewInt(0).Exp(big.NewInt(2), big.NewInt(20), nil), // 2**20 - true, + types.AccessListTx, }, } - for _, testCase := range testTable { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { t.Parallel() - key, keyGenError := GenerateECDSAKey() - if keyGenError != nil { - t.Fatalf("Unable to generate key") + key, err := GenerateECDSAKey() + require.NoError(t, err, "unable to generate private key") + + var txn *types.Transaction + + switch tc.txType { + case types.AccessListTx: + txn = types.NewTx(&types.AccessListTxn{ + To: &recipient, + Value: big.NewInt(1), + GasPrice: big.NewInt(5), + }) + case types.DynamicFeeTx, types.LegacyTx, types.StateTx: + txn = types.NewTx(&types.MixedTxn{ + To: &recipient, + Value: big.NewInt(1), + GasPrice: big.NewInt(5), + }) } - txn := &types.Transaction{ - To: &toAddress, - Value: big.NewInt(1), - GasPrice: big.NewInt(0), - } + chainID := tc.chainID.Uint64() + signer := NewLondonOrBerlinSigner(chainID, true, NewEIP155Signer(chainID, true)) - chainID := testCase.chainID.Uint64() - signer := NewLondonSigner(chainID, true, NewEIP155Signer(chainID, true)) + signedTx, err := signer.SignTx(txn, key) + require.NoError(t, err, "unable to sign transaction") - signedTx, signErr := signer.SignTx(txn, key) - if signErr != nil { - t.Fatalf("Unable to sign transaction") - } - - recoveredSender, recoverErr := signer.Sender(signedTx) - if recoverErr != nil { - t.Fatalf("Unable to recover sender") - } + sender, err := signer.Sender(signedTx) + require.NoError(t, err, "failed to recover sender") - assert.Equal(t, recoveredSender.String(), PubKeyToAddress(&key.PublicKey).String()) + require.Equal(t, sender, PubKeyToAddress(&key.PublicKey)) }) } } @@ -100,7 +104,7 @@ func TestLondonSignerSender(t *testing.T) { func Test_LondonSigner_Sender(t *testing.T) { t.Parallel() - signer := NewLondonSigner(100, true, NewEIP155Signer(100, true)) + signer := NewLondonOrBerlinSigner(100, true, NewEIP155Signer(100, true)) to := types.StringToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") r, ok := big.NewInt(0).SetString("102623819621514684481463796449525884981685455700611671612296611353030973716382", 10) @@ -116,7 +120,7 @@ func Test_LondonSigner_Sender(t *testing.T) { }{ { name: "sender is 0x85dA99c8a7C2C95964c8EfD687E95E632Fc533D6", - tx: &types.Transaction{ + tx: types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasPrice: big.NewInt(1000000402), GasTipCap: ethgo.Gwei(1), @@ -127,7 +131,7 @@ func Test_LondonSigner_Sender(t *testing.T) { V: big.NewInt(0), R: r, S: s, - }, + }), sender: types.StringToAddress("0x85dA99c8a7C2C95964c8EfD687E95E632Fc533D6"), }, } diff --git a/e2e-polybft/e2e/consensus_test.go b/e2e-polybft/e2e/consensus_test.go index 5830b62436..0115d6bcb1 100644 --- a/e2e-polybft/e2e/consensus_test.go +++ b/e2e-polybft/e2e/consensus_test.go @@ -513,6 +513,8 @@ func TestE2E_Consensus_CustomRewardToken(t *testing.T) { // and check if balance of sender, receiver, burn contract and miner is updates correctly // in accordance with EIP-1559 specifications func TestE2E_Consensus_EIP1559Check(t *testing.T) { + t.Skip("TODO - since we removed burn from evm, this should be fixed after the burn solution") + sender, err := wallet.GenerateKey() require.NoError(t, err) diff --git a/e2e-polybft/e2e/jsonrpc_test.go b/e2e-polybft/e2e/jsonrpc_test.go index e6b5064c86..9198f4d697 100644 --- a/e2e-polybft/e2e/jsonrpc_test.go +++ b/e2e-polybft/e2e/jsonrpc_test.go @@ -101,7 +101,7 @@ func TestE2E_JsonRPC(t *testing.T) { Data: input, }) require.NoError(t, err) - require.Equal(t, uint64(0x56a3), estimatedGas) + require.Equal(t, uint64(0x5bb7), estimatedGas) }) t.Run("eth_estimateGas by zero-balance account", func(t *testing.T) { @@ -122,7 +122,7 @@ func TestE2E_JsonRPC(t *testing.T) { Data: input, }) require.NoError(t, err) - require.Equal(t, uint64(0x56a3), resp) + require.Equal(t, uint64(0x5bb7), resp) }) t.Run("eth_estimateGas by zero-balance account - simple value transfer", func(t *testing.T) { diff --git a/e2e/framework/testserver.go b/e2e/framework/testserver.go index 5e536e4eb2..3419115ce6 100644 --- a/e2e/framework/testserver.go +++ b/e2e/framework/testserver.go @@ -520,7 +520,7 @@ func (t *TestServer) SendRawTx( return nil, err } - signedTx, err := t.SignTx(&types.Transaction{ + signedTx, err := t.SignTx(types.NewTx(&types.MixedTxn{ From: tx.From, GasPrice: tx.GasPrice, Gas: tx.Gas, @@ -528,7 +528,7 @@ func (t *TestServer) SendRawTx( Value: tx.Value, Input: tx.Input, Nonce: nextNonce, - }, signerKey) + }), signerKey) if err != nil { return nil, err } diff --git a/e2e/txpool_test.go b/e2e/txpool_test.go index cf566e84be..240e13645f 100644 --- a/e2e/txpool_test.go +++ b/e2e/txpool_test.go @@ -42,22 +42,22 @@ type generateTxReqParams struct { } func generateTx(params generateTxReqParams) *types.Transaction { - unsignedTx := &types.Transaction{ + unsignedTx := types.NewTx(&types.MixedTxn{ Nonce: params.nonce, From: params.referenceAddr, To: ¶ms.toAddress, Gas: 1000000, Value: params.value, V: big.NewInt(27), // it is necessary to encode in rlp - } + }) if params.gasPrice != nil { - unsignedTx.Type = types.LegacyTx - unsignedTx.GasPrice = params.gasPrice + unsignedTx.SetTransactionType(types.LegacyTx) + unsignedTx.SetGasPrice(params.gasPrice) } else { - unsignedTx.Type = types.DynamicFeeTx - unsignedTx.GasFeeCap = params.gasFeeCap - unsignedTx.GasTipCap = params.gasTipCap + unsignedTx.SetTransactionType(types.DynamicFeeTx) + unsignedTx.SetGasFeeCap(params.gasFeeCap) + unsignedTx.SetGasTipCap(params.gasTipCap) } signedTx, err := signer.SignTx(unsignedTx, params.referenceKey) @@ -236,7 +236,7 @@ func TestTxPool_RecoverableError(t *testing.T) { _, receiverAddress := tests.GenerateKeyAndAddr(t) transactions := []*types.Transaction{ - { + types.NewTx(&types.MixedTxn{ Nonce: 0, GasPrice: big.NewInt(framework.DefaultGasPrice), Gas: 22000, @@ -244,8 +244,8 @@ func TestTxPool_RecoverableError(t *testing.T) { Value: oneEth, V: big.NewInt(27), From: senderAddress, - }, - { + }), + types.NewTx(&types.MixedTxn{ Nonce: 1, GasPrice: big.NewInt(framework.DefaultGasPrice), Gas: 22000, @@ -253,8 +253,8 @@ func TestTxPool_RecoverableError(t *testing.T) { Value: oneEth, V: big.NewInt(27), From: senderAddress, - }, - { + }), + types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, Nonce: 2, GasFeeCap: big.NewInt(framework.DefaultGasPrice), @@ -264,7 +264,7 @@ func TestTxPool_RecoverableError(t *testing.T) { Value: oneEth, V: big.NewInt(27), From: senderAddress, - }, + }), } server := framework.NewTestServers(t, 1, func(config *framework.TestServerConfig) { @@ -346,8 +346,7 @@ func TestTxPool_GetPendingTx(t *testing.T) { operator := server.TxnPoolOperator() client := server.JSONRPC() - // Construct the transaction - signedTx, err := signer.SignTx(&types.Transaction{ + signedTx, err := signer.SignTx(types.NewTx(&types.MixedTxn{ Nonce: 0, GasPrice: big.NewInt(1000000000), Gas: framework.DefaultGasLimit - 1, @@ -355,7 +354,7 @@ func TestTxPool_GetPendingTx(t *testing.T) { Value: oneEth, V: big.NewInt(1), From: types.ZeroAddress, - }, senderKey) + }), senderKey) assert.NoError(t, err, "failed to sign transaction") // Add the transaction diff --git a/gasprice/feehistory.go b/gasprice/feehistory.go index 5d28b2177c..f3a27fb8af 100644 --- a/gasprice/feehistory.go +++ b/gasprice/feehistory.go @@ -135,7 +135,7 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc for j, tx := range block.Transactions { cost := tx.Cost() sorter[j] = &txGasAndReward{ - gasUsed: cost.Sub(cost, tx.Value), + gasUsed: cost.Sub(cost, tx.Value()), reward: tx.EffectiveGasTip(baseFee), } } diff --git a/gasprice/feehistory_test.go b/gasprice/feehistory_test.go index 9c54fadd73..debd24a4e7 100644 --- a/gasprice/feehistory_test.go +++ b/gasprice/feehistory_test.go @@ -194,14 +194,14 @@ func TestGasHelper_FeeHistory(t *testing.T) { b.Header.Miner = sender.Bytes() for i := 0; i < 3; i++ { - tx := &types.Transaction{ + tx := types.NewTx(&types.MixedTxn{ From: sender, Value: ethgo.Ether(1), To: &types.ZeroAddress, Type: types.DynamicFeeTx, GasTipCap: ethgo.Gwei(uint64(200)), GasFeeCap: ethgo.Gwei(uint64(200 + 200)), - } + }) tx, err := signer.SignTx(tx, senderKey) require.NoError(t, err) diff --git a/gasprice/gasprice.go b/gasprice/gasprice.go index 7c04dfd299..06c4631c3b 100644 --- a/gasprice/gasprice.go +++ b/gasprice/gasprice.go @@ -159,7 +159,7 @@ func (g *GasHelper) MaxPriorityFeePerGas() (*big.Int, error) { sender, err := signer.Sender(tx) if err != nil { - return fmt.Errorf("could not get sender of transaction: %s. Error: %w", tx.Hash, err) + return fmt.Errorf("could not get sender of transaction: %s. Error: %w", tx.Hash(), err) } if sender != blockMiner { diff --git a/gasprice/gasprice_test.go b/gasprice/gasprice_test.go index 734f6e082a..c5df080fd0 100644 --- a/gasprice/gasprice_test.go +++ b/gasprice/gasprice_test.go @@ -84,14 +84,14 @@ func TestGasHelper_MaxPriorityFeePerGas(t *testing.T) { b.Header.Miner = sender.Bytes() for i := 0; i < 3; i++ { - tx := &types.Transaction{ + tx := types.NewTx(&types.MixedTxn{ From: sender, Value: ethgo.Ether(1), To: &types.ZeroAddress, Type: types.DynamicFeeTx, GasTipCap: ethgo.Gwei(uint64(rand.Intn(200))), GasFeeCap: ethgo.Gwei(uint64(rand.Intn(200) + 200)), - } + }) tx, err := signer.SignTx(tx, senderKey) require.NoError(t, err) @@ -219,14 +219,14 @@ func createTestTxs(t *testing.T, backend *backendMock, numOfTxsPerBlock, txCap i for i := 0; i < numOfTxsPerBlock; i++ { senderKey, sender := tests.GenerateKeyAndAddr(t) - tx := &types.Transaction{ + tx := types.NewTx(&types.MixedTxn{ From: sender, Value: ethgo.Ether(1), To: &types.ZeroAddress, Type: types.DynamicFeeTx, GasTipCap: ethgo.Gwei(uint64(rand.Intn(txCap))), GasFeeCap: ethgo.Gwei(uint64(rand.Intn(txCap) + txCap)), - } + }) tx, err := signer.SignTx(tx, senderKey) require.NoError(t, err) diff --git a/go.mod b/go.mod index 76c04f9224..14b6bb6fef 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/Ethernal-Tech/merkle-tree v0.0.0-20231213143318-4db9da419e04 github.com/armon/go-metrics v0.4.1 github.com/aws/aws-sdk-go v1.50.8 - github.com/btcsuite/btcd v0.22.1 + github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/docker/docker v25.0.2+incompatible github.com/docker/go-connections v0.5.0 github.com/envoyproxy/protoc-gen-validate v1.0.4 @@ -82,6 +82,7 @@ require ( github.com/andybalholm/brotli v1.0.6 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/btcsuite/btcd v0.22.1 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect diff --git a/go.sum b/go.sum index cb7a1e2447..043422033a 100644 --- a/go.sum +++ b/go.sum @@ -73,6 +73,8 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= diff --git a/helper/enode/enode.go b/helper/enode/enode.go index 80017e6f7c..67986381e7 100644 --- a/helper/enode/enode.go +++ b/helper/enode/enode.go @@ -9,6 +9,8 @@ import ( "net/url" "strconv" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/0xPolygon/polygon-edge/crypto" "github.com/0xPolygon/polygon-edge/helper/hex" ) @@ -113,7 +115,7 @@ func NodeIDToPubKey(buf []byte) (*ecdsa.PublicKey, error) { return nil, fmt.Errorf("not enough length: expected %d but found %d", nodeIDBytes, len(buf)) } - p := &ecdsa.PublicKey{Curve: crypto.S256, X: new(big.Int), Y: new(big.Int)} + p := &ecdsa.PublicKey{Curve: btcec.S256(), X: new(big.Int), Y: new(big.Int)} half := len(buf) / 2 p.X.SetBytes(buf[:half]) p.Y.SetBytes(buf[half:]) diff --git a/helper/predeployment/predeployment.go b/helper/predeployment/predeployment.go index 56a56553ca..ed152d7de5 100644 --- a/helper/predeployment/predeployment.go +++ b/helper/predeployment/predeployment.go @@ -5,6 +5,7 @@ import ( "math" "math/big" + "github.com/hashicorp/go-hclog" "github.com/umbracle/ethgo/abi" "github.com/0xPolygon/polygon-edge/chain" @@ -67,7 +68,7 @@ func getPredeployAccount(address types.Address, input []byte, config := chain.AllForksEnabled.At(0) // Create a transition - transition := state.NewTransition(config, snapshot, radix) + transition := state.NewTransition(hclog.NewNullLogger(), config, snapshot, radix) transition.ContextPtr().ChainID = chainID // Run the transition through the EVM diff --git a/helper/tests/testing.go b/helper/tests/testing.go index ce91641857..e7ceb0c8a5 100644 --- a/helper/tests/testing.go +++ b/helper/tests/testing.go @@ -238,7 +238,7 @@ type GenerateTxReqParams struct { func generateTx(params GenerateTxReqParams) (*types.Transaction, error) { signer := crypto.NewEIP155Signer(100, true) - signedTx, signErr := signer.SignTx(&types.Transaction{ + signedTx, signErr := signer.SignTx(types.NewTx(&types.MixedTxn{ Nonce: params.Nonce, From: params.ReferenceAddr, To: ¶ms.ToAddress, @@ -247,7 +247,7 @@ func generateTx(params GenerateTxReqParams) (*types.Transaction, error) { Value: params.Value, Input: params.Input, V: big.NewInt(27), // it is necessary to encode in rlp - }, params.ReferenceKey) + }), params.ReferenceKey) if signErr != nil { return nil, fmt.Errorf("unable to sign transaction, %w", signErr) diff --git a/jsonrpc/debug_endpoint.go b/jsonrpc/debug_endpoint.go index 59f44fd142..1c853b7c1f 100644 --- a/jsonrpc/debug_endpoint.go +++ b/jsonrpc/debug_endpoint.go @@ -173,7 +173,7 @@ func (d *Debug) TraceTransaction( defer cancel() - return d.store.TraceTxn(block, tx.Hash, tracer) + return d.store.TraceTxn(block, tx.Hash(), tracer) }, ) } @@ -197,8 +197,8 @@ func (d *Debug) TraceCall( } // If the caller didn't supply the gas limit in the message, then we set it to maximum possible => block gas limit - if tx.Gas == 0 { - tx.Gas = header.GasLimit + if tx.Gas() == 0 { + tx.SetGas(header.GasLimit) } tracer, cancel, err := newTracer(config) diff --git a/jsonrpc/debug_endpoint_test.go b/jsonrpc/debug_endpoint_test.go index 0b90d06181..f6e9d1603d 100644 --- a/jsonrpc/debug_endpoint_test.go +++ b/jsonrpc/debug_endpoint_test.go @@ -604,7 +604,7 @@ func TestTraceCall(t *testing.T) { Input: &input, Nonce: &nonce, } - decodedTx = &types.Transaction{ + decodedTx = types.NewTx(&types.MixedTxn{ Nonce: uint64(nonce), GasPrice: new(big.Int).SetBytes([]byte(gasPrice)), GasTipCap: new(big.Int).SetBytes([]byte(gasTipCap)), @@ -614,7 +614,7 @@ func TestTraceCall(t *testing.T) { Value: new(big.Int).SetBytes([]byte(value)), Input: data, From: from, - } + }) ) decodedTx.ComputeHash() diff --git a/jsonrpc/eth_blockchain_test.go b/jsonrpc/eth_blockchain_test.go index ac730101e3..807a0eaf6b 100644 --- a/jsonrpc/eth_blockchain_test.go +++ b/jsonrpc/eth_blockchain_test.go @@ -88,7 +88,8 @@ func TestEth_Block_GetBlockTransactionCountByNumber(t *testing.T) { block := newTestBlock(1, hash1) for i := 0; i < 10; i++ { - block.Transactions = append(block.Transactions, []*types.Transaction{{Nonce: 0, From: addr0}}...) + block.Transactions = append(block.Transactions, []*types.Transaction{ + types.NewTx(&types.MixedTxn{Nonce: 0, From: addr0})}...) } store.add(block) @@ -120,12 +121,12 @@ func TestEth_GetTransactionByHash(t *testing.T) { testTxnIndex := 5 testTxn := block.Transactions[testTxnIndex] - res, err := eth.GetTransactionByHash(testTxn.Hash) + res, err := eth.GetTransactionByHash(testTxn.Hash()) assert.NoError(t, err) assert.NotNil(t, res) foundTxn := res.(*transaction) - assert.Equal(t, argUint64(testTxn.Nonce), foundTxn.Nonce) + assert.Equal(t, argUint64(testTxn.Nonce()), foundTxn.Nonce) assert.Equal(t, argUint64(block.Number()), *foundTxn.BlockNumber) assert.Equal(t, block.Hash(), *foundTxn.BlockHash) assert.Equal(t, argUint64(testTxnIndex), *foundTxn.TxIndex) @@ -144,12 +145,12 @@ func TestEth_GetTransactionByHash(t *testing.T) { testTxn := store.pendingTxns[5] - res, err := eth.GetTransactionByHash(testTxn.Hash) + res, err := eth.GetTransactionByHash(testTxn.Hash()) assert.NoError(t, err) assert.NotNil(t, res) foundTxn := res.(*transaction) - assert.Equal(t, argUint64(testTxn.Nonce), foundTxn.Nonce) + assert.Equal(t, argUint64(testTxn.Nonce()), foundTxn.Nonce) assert.Nil(t, foundTxn.BlockNumber) assert.Nil(t, foundTxn.BlockHash) assert.Nil(t, foundTxn.TxIndex) @@ -228,13 +229,13 @@ func TestEth_GetTransactionReceipt(t *testing.T) { receipt2.SetStatus(types.ReceiptSuccess) store.receipts[hash4] = []*types.Receipt{receipt1, receipt2} - res, err := eth.GetTransactionReceipt(txn1.Hash) + res, err := eth.GetTransactionReceipt(txn1.Hash()) assert.NoError(t, err) assert.NotNil(t, res) response := res.(*receipt) - assert.Equal(t, txn1.Hash, response.TxHash) + assert.Equal(t, txn1.Hash(), response.TxHash) assert.Equal(t, block.Hash(), response.BlockHash) assert.NotNil(t, response.Logs) assert.Len(t, response.Logs, 1) @@ -588,7 +589,7 @@ func (m *mockBlockStore) Header() *types.Header { func (m *mockBlockStore) ReadTxLookup(txnHash types.Hash) (types.Hash, bool) { for _, block := range m.blocks { for _, txn := range block.Transactions { - if txn.Hash == txnHash { + if txn.Hash() == txnHash { return block.Hash(), true } } @@ -599,7 +600,7 @@ func (m *mockBlockStore) ReadTxLookup(txnHash types.Hash) (types.Hash, bool) { func (m *mockBlockStore) GetPendingTx(txHash types.Hash) (*types.Transaction, bool) { for _, txn := range m.pendingTxns { - if txn.Hash == txHash { + if txn.Hash() == txHash { return txn, true } } diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index e564950822..5ca84c79d2 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -213,7 +213,7 @@ func (e *Eth) SendRawTransaction(buf argBytes) (interface{}, error) { return nil, err } - return tx.Hash.String(), nil + return tx.Hash().String(), nil } // SendTransaction rejects eth_sendTransaction json-rpc call as we don't support wallet management @@ -244,7 +244,7 @@ func (e *Eth) GetTransactionByHash(hash types.Hash) (interface{}, error) { // Find the transaction within the block if txn, idx := types.FindTxByHash(block.Transactions, hash); txn != nil { - txn.GasPrice = txn.GetGasPrice(block.Header.BaseFee) + txn.SetGasPrice(txn.GetGasPrice(block.Header.BaseFee)) return toTransaction( txn, @@ -408,10 +408,10 @@ func (e *Eth) fillTransactionGasPrice(tx *types.Transaction) error { return err } - if tx.Type == types.DynamicFeeTx { - tx.GasFeeCap = new(big.Int).SetUint64(estimatedGasPrice) + if tx.Type() == types.DynamicFeeTx { + tx.SetGasFeeCap(new(big.Int).SetUint64(estimatedGasPrice)) } else { - tx.GasPrice = new(big.Int).SetUint64(estimatedGasPrice) + tx.SetGasPrice(new(big.Int).SetUint64(estimatedGasPrice)) } return nil @@ -467,8 +467,8 @@ func (e *Eth) Call(arg *txnArgs, filter BlockNumberOrHash, apiOverride *stateOve } // If the caller didn't supply the gas limit in the message, then we set it to maximum possible => block gas limit - if transaction.Gas == 0 { - transaction.Gas = header.GasLimit + if transaction.Gas() == 0 { + transaction.SetGas(header.GasLimit) } // Force transaction gas price if empty @@ -552,25 +552,26 @@ func (e *Eth) EstimateGas(arg *txnArgs, rawNum *BlockNumber) (interface{}, error ) // If the gas limit was passed in, use it as a ceiling - if transaction.Gas != 0 && transaction.Gas >= standardGas { - highEnd = transaction.Gas + if transaction.Gas() != 0 && transaction.Gas() >= standardGas { + highEnd = transaction.Gas() } else { // If not, use the referenced block number highEnd = header.GasLimit } - gasPriceInt := new(big.Int).Set(transaction.GasPrice) + gasPriceInt := new(big.Int).Set(transaction.GasPrice()) + valueInt := new(big.Int).Set(transaction.Value()) var availableBalance *big.Int // If the sender address is present, figure out how much available funds // are we working with - if transaction.From != types.ZeroAddress { + if transaction.From() != types.ZeroAddress { // Get the account balance // If the account is not initialized yet in state, // assume it's an empty account accountBalance := big.NewInt(0) - acc, err := e.store.GetAccount(header.StateRoot, transaction.From) + acc, err := e.store.GetAccount(header.StateRoot, transaction.From()) if err != nil && !errors.Is(err, ErrStateNotFound) { // An unrelated error occurred, return it @@ -582,6 +583,14 @@ func (e *Eth) EstimateGas(arg *txnArgs, rawNum *BlockNumber) (interface{}, error } availableBalance = new(big.Int).Set(accountBalance) + + if transaction.Value() != nil { + if valueInt.Cmp(availableBalance) > 0 { + return 0, ErrInsufficientFunds + } + + availableBalance.Sub(availableBalance, valueInt) + } } // Recalculate the gas ceiling based on the available funds (if any) @@ -633,7 +642,7 @@ func (e *Eth) EstimateGas(arg *txnArgs, rawNum *BlockNumber) (interface{}, error testTransaction := func(gas uint64, shouldOmitErr bool) (bool, interface{}, error) { var data interface{} - transaction.Gas = gas + transaction.SetGas(gas) result, applyErr := e.store.ApplyTxn(header, transaction, nil, true) diff --git a/jsonrpc/eth_endpoint_test.go b/jsonrpc/eth_endpoint_test.go index b485819fac..98e3eba23b 100644 --- a/jsonrpc/eth_endpoint_test.go +++ b/jsonrpc/eth_endpoint_test.go @@ -46,7 +46,7 @@ func TestEth_DecodeTxn(t *testing.T) { Data: nil, Nonce: toArgUint64Ptr(0), }, - res: &types.Transaction{ + res: types.NewTx(&types.MixedTxn{ From: addr1, To: &addr2, Gas: 21000, @@ -56,7 +56,7 @@ func TestEth_DecodeTxn(t *testing.T) { Value: oneEther, Input: []byte{}, Nonce: 0, - }, + }), err: nil, }, { @@ -68,7 +68,7 @@ func TestEth_DecodeTxn(t *testing.T) { Value: toArgBytesPtr(oneEther.Bytes()), Data: nil, }, - res: &types.Transaction{ + res: types.NewTx(&types.MixedTxn{ From: types.ZeroAddress, To: &addr2, Gas: 21000, @@ -78,7 +78,7 @@ func TestEth_DecodeTxn(t *testing.T) { Value: oneEther, Input: []byte{}, Nonce: 0, - }, + }), err: nil, }, { @@ -96,7 +96,7 @@ func TestEth_DecodeTxn(t *testing.T) { Value: toArgBytesPtr(oneEther.Bytes()), Data: nil, }, - res: &types.Transaction{ + res: types.NewTx(&types.MixedTxn{ From: addr1, To: &addr2, Gas: 21000, @@ -106,7 +106,7 @@ func TestEth_DecodeTxn(t *testing.T) { Value: oneEther, Input: []byte{}, Nonce: 10, - }, + }), err: nil, }, { @@ -119,7 +119,7 @@ func TestEth_DecodeTxn(t *testing.T) { Data: nil, Nonce: toArgUint64Ptr(1), }, - res: &types.Transaction{ + res: types.NewTx(&types.MixedTxn{ From: addr1, To: &addr2, Gas: 21000, @@ -129,7 +129,7 @@ func TestEth_DecodeTxn(t *testing.T) { Value: new(big.Int).SetBytes([]byte{}), Input: []byte{}, Nonce: 1, - }, + }), err: nil, }, { @@ -141,7 +141,7 @@ func TestEth_DecodeTxn(t *testing.T) { Data: nil, Nonce: toArgUint64Ptr(1), }, - res: &types.Transaction{ + res: types.NewTx(&types.MixedTxn{ From: addr1, To: &addr2, Gas: 0, @@ -151,7 +151,7 @@ func TestEth_DecodeTxn(t *testing.T) { Value: new(big.Int).SetBytes([]byte{}), Input: []byte{}, Nonce: 1, - }, + }), err: nil, }, } @@ -278,7 +278,7 @@ func TestEth_TxnType(t *testing.T) { Type: toArgUint64Ptr(uint64(types.DynamicFeeTx)), } - expectedRes := &types.Transaction{ + expectedRes := types.NewTx(&types.MixedTxn{ From: addr1, To: &addr2, Gas: 21000, @@ -289,7 +289,7 @@ func TestEth_TxnType(t *testing.T) { Input: []byte{}, Nonce: 0, Type: types.DynamicFeeTx, - } + }) res, err := DecodeTxn(args, 1, store, false) expectedRes.ComputeHash() diff --git a/jsonrpc/eth_state_test.go b/jsonrpc/eth_state_test.go index 7c6f81aab6..f931294145 100644 --- a/jsonrpc/eth_state_test.go +++ b/jsonrpc/eth_state_test.go @@ -672,7 +672,7 @@ func TestEth_EstimateGas_GasLimit(t *testing.T) { header *types.Header, txn *types.Transaction, ) (*runtime.ExecutionResult, error) { - if txn.Gas < testCase.intrinsicGasCost { + if txn.Gas() < testCase.intrinsicGasCost { return &runtime.ExecutionResult{}, state.ErrNotEnoughIntrinsicGas } diff --git a/jsonrpc/eth_txpool_test.go b/jsonrpc/eth_txpool_test.go index 39099cdbac..eb3cf9e252 100644 --- a/jsonrpc/eth_txpool_test.go +++ b/jsonrpc/eth_txpool_test.go @@ -11,20 +11,19 @@ import ( func TestEth_TxnPool_SendRawTransaction(t *testing.T) { store := &mockStoreTxn{} eth := newTestEthEndpoint(store) - - txn := &types.Transaction{ + txn := types.NewTx(&types.MixedTxn{ From: addr0, V: big.NewInt(1), - } + }) txn.ComputeHash() data := txn.MarshalRLP() _, err := eth.SendRawTransaction(data) assert.NoError(t, err) - assert.NotEqual(t, store.txn.Hash, types.ZeroHash) + assert.NotEqual(t, store.txn.Hash(), types.ZeroHash) // the hash in the txn pool should match the one we send - if txn.Hash != store.txn.Hash { + if txn.Hash() != store.txn.Hash() { t.Fatal("bad") } } @@ -33,17 +32,16 @@ func TestEth_TxnPool_SendTransaction(t *testing.T) { store := &mockStoreTxn{} store.AddAccount(addr0) eth := newTestEthEndpoint(store) - - txToSend := &types.Transaction{ + txToSend := types.NewTx(&types.MixedTxn{ From: addr0, To: argAddrPtr(addr0), Nonce: uint64(0), GasPrice: big.NewInt(int64(1)), - } + }) _, err := eth.SendRawTransaction(txToSend.MarshalRLP()) assert.NoError(t, err) - assert.NotEqual(t, store.txn.Hash, types.ZeroHash) + assert.NotEqual(t, store.txn.Hash(), types.ZeroHash) } type mockStoreTxn struct { diff --git a/jsonrpc/filter_manager.go b/jsonrpc/filter_manager.go index 086565be5f..03bacde8ff 100644 --- a/jsonrpc/filter_manager.go +++ b/jsonrpc/filter_manager.go @@ -495,7 +495,7 @@ func (f *FilterManager) getLogsFromBlock(query *LogQuery, block *types.Block) ([ for idx, receipt := range receipts { for _, log := range receipt.Logs { if query.Match(log) { - logs = append(logs, toLog(log, logIdx, uint64(idx), block.Header, block.Transactions[idx].Hash)) + logs = append(logs, toLog(log, logIdx, uint64(idx), block.Header, block.Transactions[idx].Hash())) } logIdx++ @@ -802,7 +802,7 @@ func (f *FilterManager) appendLogsToFilters(header *block) error { for indx, receipt := range receipts { if receipt.TxHash == types.ZeroHash { // Extract tx Hash - receipt.TxHash = block.Transactions[indx].Hash + receipt.TxHash = block.Transactions[indx].Hash() } // check the logs with the filters for _, log := range receipt.Logs { diff --git a/jsonrpc/filter_manager_fuzz_test.go b/jsonrpc/filter_manager_fuzz_test.go index 81ecab76aa..bb52cd1472 100644 --- a/jsonrpc/filter_manager_fuzz_test.go +++ b/jsonrpc/filter_manager_fuzz_test.go @@ -34,13 +34,19 @@ func FuzzGetLogsForQuery(f *testing.F) { }, Transactions: []*types.Transaction{ { - Value: big.NewInt(10), + Inner: &types.MixedTxn{ + Value: big.NewInt(10), + }, }, { - Value: big.NewInt(11), + Inner: &types.MixedTxn{ + Value: big.NewInt(11), + }, }, { - Value: big.NewInt(12), + Inner: &types.MixedTxn{ + Value: big.NewInt(12), + }, }, }, } diff --git a/jsonrpc/filter_manager_test.go b/jsonrpc/filter_manager_test.go index 98dee97f8c..7eefb8b618 100644 --- a/jsonrpc/filter_manager_test.go +++ b/jsonrpc/filter_manager_test.go @@ -114,15 +114,9 @@ func Test_GetLogsForQuery(t *testing.T) { Hash: types.StringToHash(strconv.Itoa(i)), }, Transactions: []*types.Transaction{ - { - Value: big.NewInt(10), - }, - { - Value: big.NewInt(11), - }, - { - Value: big.NewInt(12), - }, + types.NewTx(&types.MixedTxn{Value: big.NewInt(10)}), + types.NewTx(&types.MixedTxn{Value: big.NewInt(11)}), + types.NewTx(&types.MixedTxn{Value: big.NewInt(12)}), }, } } diff --git a/jsonrpc/helper.go b/jsonrpc/helper.go index 48a894686a..5c220d1b58 100644 --- a/jsonrpc/helper.go +++ b/jsonrpc/helper.go @@ -219,7 +219,7 @@ func DecodeTxn(arg *txnArgs, blockNumber uint64, store nonceGetter, forceSetNonc txType = types.TxType(*arg.Type) } - txn := &types.Transaction{ + txn := types.NewTx(&types.MixedTxn{ From: *arg.From, Gas: uint64(*arg.Gas), GasPrice: new(big.Int).SetBytes(*arg.GasPrice), @@ -229,10 +229,10 @@ func DecodeTxn(arg *txnArgs, blockNumber uint64, store nonceGetter, forceSetNonc Input: input, Nonce: uint64(*arg.Nonce), Type: txType, - } + }) if arg.To != nil { - txn.To = arg.To + txn.SetTo(arg.To) } txn.ComputeHash() diff --git a/jsonrpc/helper_test.go b/jsonrpc/helper_test.go index dca4866a85..c63af1a47a 100644 --- a/jsonrpc/helper_test.go +++ b/jsonrpc/helper_test.go @@ -14,7 +14,7 @@ import ( func createTestTransaction(hash types.Hash) *types.Transaction { recipient := types.StringToAddress("2") - return &types.Transaction{ + return types.NewTx(&types.MixedTxn{ Hash: hash, From: types.StringToAddress("1"), To: &recipient, @@ -23,7 +23,7 @@ func createTestTransaction(hash types.Hash) *types.Transaction { V: big.NewInt(1), R: big.NewInt(2), S: big.NewInt(3), - } + }) } func createTestHeader(height uint64, setterFn func(h *types.Header)) *types.Header { @@ -333,10 +333,10 @@ func TestGetTxAndBlockByTxHash(t *testing.T) { }{ { name: "should return tx and block", - txHash: testTx1.Hash, + txHash: testTx1.Hash(), store: &debugEndpointMockStore{ readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { - assert.Equal(t, testTx1.Hash, hash) + assert.Equal(t, testTx1.Hash(), hash) return blockWithTx.Hash(), true }, @@ -352,10 +352,10 @@ func TestGetTxAndBlockByTxHash(t *testing.T) { }, { name: "should return nil if ReadTxLookup returns nothing", - txHash: testTx1.Hash, + txHash: testTx1.Hash(), store: &debugEndpointMockStore{ readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { - assert.Equal(t, testTx1.Hash, hash) + assert.Equal(t, testTx1.Hash(), hash) return types.ZeroHash, false }, @@ -365,10 +365,10 @@ func TestGetTxAndBlockByTxHash(t *testing.T) { }, { name: "should return nil if GetBlockByHash returns nothing", - txHash: testTx1.Hash, + txHash: testTx1.Hash(), store: &debugEndpointMockStore{ readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { - assert.Equal(t, testTx1.Hash, hash) + assert.Equal(t, testTx1.Hash(), hash) return blockWithTx.Hash(), true }, @@ -384,10 +384,10 @@ func TestGetTxAndBlockByTxHash(t *testing.T) { }, { name: "should return nil if the block doesn't include the tx", - txHash: testTx1.Hash, + txHash: testTx1.Hash(), store: &debugEndpointMockStore{ readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { - assert.Equal(t, testTx1.Hash, hash) + assert.Equal(t, testTx1.Hash(), hash) return blockWithTx.Hash(), true }, @@ -707,7 +707,7 @@ func TestDecodeTxn(t *testing.T) { Nonce: &nonce, }, store: &debugEndpointMockStore{}, - expected: &types.Transaction{ + expected: types.NewTx(&types.MixedTxn{ From: from, To: &to, Gas: uint64(gas), @@ -717,7 +717,7 @@ func TestDecodeTxn(t *testing.T) { Value: new(big.Int).SetBytes([]byte(value)), Input: input, Nonce: uint64(nonce), - }, + }), err: false, }, { @@ -731,7 +731,7 @@ func TestDecodeTxn(t *testing.T) { Nonce: &nonce, }, store: &debugEndpointMockStore{}, - expected: &types.Transaction{ + expected: types.NewTx(&types.MixedTxn{ From: types.ZeroAddress, To: &to, Gas: uint64(gas), @@ -741,7 +741,7 @@ func TestDecodeTxn(t *testing.T) { Value: new(big.Int).SetBytes([]byte(value)), Input: input, Nonce: uint64(0), - }, + }), err: false, }, { @@ -766,7 +766,7 @@ func TestDecodeTxn(t *testing.T) { }, nil }, }, - expected: &types.Transaction{ + expected: types.NewTx(&types.MixedTxn{ From: from, To: &to, Gas: uint64(gas), @@ -776,7 +776,7 @@ func TestDecodeTxn(t *testing.T) { Value: new(big.Int).SetBytes([]byte(value)), Input: input, Nonce: uint64(stateNonce), - }, + }), err: false, }, { @@ -792,7 +792,7 @@ func TestDecodeTxn(t *testing.T) { Nonce: &nonce, }, store: &debugEndpointMockStore{}, - expected: &types.Transaction{ + expected: types.NewTx(&types.MixedTxn{ From: from, To: &to, Gas: uint64(gas), @@ -802,7 +802,7 @@ func TestDecodeTxn(t *testing.T) { Value: new(big.Int).SetBytes([]byte(value)), Input: data, Nonce: uint64(nonce), - }, + }), err: false, }, { @@ -813,7 +813,7 @@ func TestDecodeTxn(t *testing.T) { Nonce: &nonce, }, store: &debugEndpointMockStore{}, - expected: &types.Transaction{ + expected: types.NewTx(&types.MixedTxn{ From: from, To: &to, Gas: uint64(0), @@ -823,7 +823,7 @@ func TestDecodeTxn(t *testing.T) { Value: new(big.Int), Input: []byte{}, Nonce: uint64(nonce), - }, + }), err: false, }, { diff --git a/jsonrpc/txpool_endpoint.go b/jsonrpc/txpool_endpoint.go index b2578b88fa..5c2bb5e69f 100644 --- a/jsonrpc/txpool_endpoint.go +++ b/jsonrpc/txpool_endpoint.go @@ -51,7 +51,7 @@ func (t *TxPool) Content() (interface{}, error) { result[addr] = make(map[uint64]*transaction, len(txs)) for _, tx := range txs { - result[addr][tx.Nonce] = toTransaction(tx, nil, &types.ZeroHash, nil) + result[addr][tx.Nonce()] = toTransaction(tx, nil, &types.ZeroHash, nil) } } @@ -78,9 +78,9 @@ func (t *TxPool) Inspect() (interface{}, error) { result[addr.String()] = make(map[string]string, len(txs)) for _, tx := range txs { - nonceStr := strconv.FormatUint(tx.Nonce, 10) + nonceStr := strconv.FormatUint(tx.Nonce(), 10) result[addr.String()][nonceStr] = fmt.Sprintf( - "%d wei + %d gas x %d wei", tx.Value, tx.Gas, tx.GetGasPrice(baseFee), + "%d wei + %d gas x %d wei", tx.Value(), tx.Gas(), tx.GetGasPrice(baseFee), ) } } diff --git a/jsonrpc/txpool_endpoint_test.go b/jsonrpc/txpool_endpoint_test.go index b9b9628294..5d892f4249 100644 --- a/jsonrpc/txpool_endpoint_test.go +++ b/jsonrpc/txpool_endpoint_test.go @@ -45,31 +45,31 @@ func TestContentEndpoint(t *testing.T) { assert.Equal(t, 0, len(response.Queued)) assert.Equal(t, 2, len(response.Pending[address1])) - txData := response.Pending[address1][testTx1.Nonce] + txData := response.Pending[address1][testTx1.Nonce()] assert.NotNil(t, txData) - assert.Equal(t, testTx1.Gas, uint64(txData.Gas)) - assert.Equal(t, *testTx1.GasPrice, big.Int(*txData.GasPrice)) + assert.Equal(t, testTx1.Gas(), uint64(txData.Gas)) + assert.Equal(t, *(testTx1.GasPrice()), big.Int(*txData.GasPrice)) assert.Equal(t, (*argBig)(nil), txData.GasFeeCap) assert.Equal(t, (*argBig)(nil), txData.GasTipCap) - assert.Equal(t, testTx1.To, txData.To) - assert.Equal(t, testTx1.From, txData.From) - assert.Equal(t, *testTx1.Value, big.Int(txData.Value)) - assert.Equal(t, testTx1.Input, []byte(txData.Input)) + assert.Equal(t, testTx1.To(), txData.To) + assert.Equal(t, testTx1.From(), txData.From) + assert.Equal(t, *(testTx1.Value()), big.Int(txData.Value)) + assert.Equal(t, testTx1.Input(), []byte(txData.Input)) assert.Equal(t, (*argUint64)(nil), txData.BlockNumber) assert.Equal(t, (*argUint64)(nil), txData.TxIndex) - txData = response.Pending[address1][testTx2.Nonce] + txData = response.Pending[address1][testTx2.Nonce()] assert.NotNil(t, txData) assert.Equal(t, (argUint64)(types.DynamicFeeTx), txData.Type) - assert.Equal(t, testTx2.Gas, uint64(txData.Gas)) + assert.Equal(t, testTx2.Gas(), uint64(txData.Gas)) assert.Equal(t, (*argBig)(nil), txData.GasPrice) - assert.Equal(t, *testTx2.GasFeeCap, big.Int(*txData.GasFeeCap)) - assert.Equal(t, *testTx2.GasTipCap, big.Int(*txData.GasTipCap)) - assert.Equal(t, testTx2.To, txData.To) - assert.Equal(t, testTx2.From, txData.From) - assert.Equal(t, *testTx2.ChainID, big.Int(*txData.ChainID)) - assert.Equal(t, *testTx2.Value, big.Int(txData.Value)) - assert.Equal(t, testTx2.Input, []byte(txData.Input)) + assert.Equal(t, *(testTx2.GasFeeCap()), big.Int(*txData.GasFeeCap)) + assert.Equal(t, *(testTx2.GasTipCap()), big.Int(*txData.GasTipCap)) + assert.Equal(t, testTx2.To(), txData.To) + assert.Equal(t, testTx2.From(), txData.From) + assert.Equal(t, *(testTx2.ChainID()), big.Int(*txData.ChainID)) + assert.Equal(t, *(testTx2.Value()), big.Int(txData.Value)) + assert.Equal(t, testTx2.Input(), []byte(txData.Input)) assert.Equal(t, (*argUint64)(nil), txData.BlockNumber) assert.Equal(t, (*argUint64)(nil), txData.TxIndex) }) @@ -94,31 +94,31 @@ func TestContentEndpoint(t *testing.T) { assert.Equal(t, 1, len(response.Queued[address1])) assert.Equal(t, 1, len(response.Queued[address2])) - txData := response.Queued[address1][testTx1.Nonce] + txData := response.Queued[address1][testTx1.Nonce()] assert.NotNil(t, txData) - assert.Equal(t, testTx1.Gas, uint64(txData.Gas)) - assert.Equal(t, *testTx1.GasPrice, big.Int(*txData.GasPrice)) + assert.Equal(t, testTx1.Gas(), uint64(txData.Gas)) + assert.Equal(t, *(testTx1.GasPrice()), big.Int(*txData.GasPrice)) assert.Equal(t, (*argBig)(nil), txData.GasFeeCap) assert.Equal(t, (*argBig)(nil), txData.GasTipCap) - assert.Equal(t, testTx1.To, txData.To) - assert.Equal(t, testTx1.From, txData.From) - assert.Equal(t, *testTx1.Value, big.Int(txData.Value)) - assert.Equal(t, testTx1.Input, []byte(txData.Input)) + assert.Equal(t, testTx1.To(), txData.To) + assert.Equal(t, testTx1.From(), txData.From) + assert.Equal(t, *(testTx1.Value()), big.Int(txData.Value)) + assert.Equal(t, testTx1.Input(), []byte(txData.Input)) assert.Equal(t, (*argUint64)(nil), txData.BlockNumber) assert.Equal(t, (*argUint64)(nil), txData.TxIndex) - txData = response.Queued[address2][testTx2.Nonce] + txData = response.Queued[address2][testTx2.Nonce()] assert.NotNil(t, txData) assert.Equal(t, (argUint64)(types.DynamicFeeTx), txData.Type) - assert.Equal(t, testTx2.Gas, uint64(txData.Gas)) + assert.Equal(t, testTx2.Gas(), uint64(txData.Gas)) assert.Equal(t, (*argBig)(nil), txData.GasPrice) - assert.Equal(t, *testTx2.GasFeeCap, big.Int(*txData.GasFeeCap)) - assert.Equal(t, *testTx2.GasTipCap, big.Int(*txData.GasTipCap)) - assert.Equal(t, testTx2.To, txData.To) - assert.Equal(t, testTx2.From, txData.From) - assert.Equal(t, *testTx2.ChainID, big.Int(*txData.ChainID)) - assert.Equal(t, *testTx2.Value, big.Int(txData.Value)) - assert.Equal(t, testTx2.Input, []byte(txData.Input)) + assert.Equal(t, *(testTx2.GasFeeCap()), big.Int(*txData.GasFeeCap)) + assert.Equal(t, *(testTx2.GasTipCap()), big.Int(*txData.GasTipCap)) + assert.Equal(t, testTx2.To(), txData.To) + assert.Equal(t, testTx2.From(), txData.From) + assert.Equal(t, *(testTx2.ChainID()), big.Int(*txData.ChainID)) + assert.Equal(t, *(testTx2.Value()), big.Int(txData.Value)) + assert.Equal(t, testTx2.Input(), []byte(txData.Input)) assert.Equal(t, (*argUint64)(nil), txData.BlockNumber) assert.Equal(t, (*argUint64)(nil), txData.TxIndex) }) @@ -190,9 +190,9 @@ func TestInspectEndpoint(t *testing.T) { assert.Equal(t, 0, len(response.Pending)) assert.Equal(t, 1, len(response.Queued)) assert.Equal(t, uint64(1), response.CurrentCapacity) - transactionInfo := response.Queued[testTx.From.String()] + transactionInfo := response.Queued[testTx.From().String()] assert.NotNil(t, transactionInfo) - assert.NotNil(t, transactionInfo[strconv.FormatUint(testTx.Nonce, 10)]) + assert.NotNil(t, transactionInfo[strconv.FormatUint(testTx.Nonce(), 10)]) }) t.Run("returns correct data for pending transactions", func(t *testing.T) { @@ -213,10 +213,10 @@ func TestInspectEndpoint(t *testing.T) { assert.Equal(t, 1, len(response.Pending)) assert.Equal(t, 0, len(response.Queued)) assert.Equal(t, uint64(2), response.CurrentCapacity) - transactionInfo := response.Pending[testTx.From.String()] + transactionInfo := response.Pending[testTx.From().String()] assert.NotNil(t, transactionInfo) - assert.NotNil(t, transactionInfo[strconv.FormatUint(testTx.Nonce, 10)]) - assert.NotNil(t, transactionInfo[strconv.FormatUint(testTx2.Nonce, 10)]) + assert.NotNil(t, transactionInfo[strconv.FormatUint(testTx.Nonce(), 10)]) + assert.NotNil(t, transactionInfo[strconv.FormatUint(testTx2.Nonce(), 10)]) }) } @@ -294,7 +294,7 @@ func (s *mockTxPoolStore) GetBaseFee() uint64 { } func newTestTransaction(nonce uint64, from types.Address) *types.Transaction { - txn := &types.Transaction{ + txn := types.NewTx(&types.MixedTxn{ Nonce: nonce, GasPrice: big.NewInt(1), Gas: nonce * 100, @@ -305,7 +305,7 @@ func newTestTransaction(nonce uint64, from types.Address) *types.Transaction { V: big.NewInt(1), R: big.NewInt(1), S: big.NewInt(1), - } + }) txn.ComputeHash() @@ -313,7 +313,7 @@ func newTestTransaction(nonce uint64, from types.Address) *types.Transaction { } func newTestDynamicFeeTransaction(nonce uint64, from types.Address) *types.Transaction { - txn := &types.Transaction{ + txn := types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, Nonce: nonce, GasTipCap: big.NewInt(2), @@ -327,7 +327,7 @@ func newTestDynamicFeeTransaction(nonce uint64, from types.Address) *types.Trans V: big.NewInt(1), R: big.NewInt(1), S: big.NewInt(1), - } + }) txn.ComputeHash() diff --git a/jsonrpc/types.go b/jsonrpc/types.go index 3bc717ea3a..b1a842eab9 100644 --- a/jsonrpc/types.go +++ b/jsonrpc/types.go @@ -18,24 +18,25 @@ type transactionOrHash interface { } type transaction struct { - Nonce argUint64 `json:"nonce"` - GasPrice *argBig `json:"gasPrice,omitempty"` - GasTipCap *argBig `json:"maxPriorityFeePerGas,omitempty"` - GasFeeCap *argBig `json:"maxFeePerGas,omitempty"` - Gas argUint64 `json:"gas"` - To *types.Address `json:"to"` - Value argBig `json:"value"` - Input argBytes `json:"input"` - V argBig `json:"v"` - R argBig `json:"r"` - S argBig `json:"s"` - Hash types.Hash `json:"hash"` - From types.Address `json:"from"` - BlockHash *types.Hash `json:"blockHash"` - BlockNumber *argUint64 `json:"blockNumber"` - TxIndex *argUint64 `json:"transactionIndex"` - ChainID *argBig `json:"chainId,omitempty"` - Type argUint64 `json:"type"` + Nonce argUint64 `json:"nonce"` + GasPrice *argBig `json:"gasPrice,omitempty"` + GasTipCap *argBig `json:"maxPriorityFeePerGas,omitempty"` + GasFeeCap *argBig `json:"maxFeePerGas,omitempty"` + Gas argUint64 `json:"gas"` + To *types.Address `json:"to"` + Value argBig `json:"value"` + Input argBytes `json:"input"` + V argBig `json:"v"` + R argBig `json:"r"` + S argBig `json:"s"` + Hash types.Hash `json:"hash"` + From types.Address `json:"from"` + BlockHash *types.Hash `json:"blockHash"` + BlockNumber *argUint64 `json:"blockNumber"` + TxIndex *argUint64 `json:"transactionIndex"` + ChainID *argBig `json:"chainId,omitempty"` + Type argUint64 `json:"type"` + AccessList types.TxAccessList `json:"accessList,omitempty"` } func (t transaction) getHash() types.Hash { return t.Hash } @@ -59,39 +60,40 @@ func toTransaction( blockHash *types.Hash, txIndex *int, ) *transaction { + v, r, s := t.RawSignatureValues() res := &transaction{ - Nonce: argUint64(t.Nonce), - Gas: argUint64(t.Gas), - To: t.To, - Value: argBig(*t.Value), - Input: t.Input, - V: argBig(*t.V), - R: argBig(*t.R), - S: argBig(*t.S), - Hash: t.Hash, - From: t.From, - Type: argUint64(t.Type), + Nonce: argUint64(t.Nonce()), + Gas: argUint64(t.Gas()), + To: t.To(), + Value: argBig(*t.Value()), + Input: t.Input(), + V: argBig(*v), + R: argBig(*r), + S: argBig(*s), + Hash: t.Hash(), + From: t.From(), + Type: argUint64(t.Type()), BlockNumber: blockNumber, BlockHash: blockHash, } - if t.GasPrice != nil { - gasPrice := argBig(*t.GasPrice) + if t.GasPrice() != nil { + gasPrice := argBig(*(t.GasPrice())) res.GasPrice = &gasPrice } - if t.GasTipCap != nil { - gasTipCap := argBig(*t.GasTipCap) + if t.GasTipCap() != nil { + gasTipCap := argBig(*(t.GasTipCap())) res.GasTipCap = &gasTipCap } - if t.GasFeeCap != nil { - gasFeeCap := argBig(*t.GasFeeCap) + if t.GasFeeCap() != nil { + gasFeeCap := argBig(*(t.GasFeeCap())) res.GasFeeCap = &gasFeeCap } - if t.ChainID != nil { - chainID := argBig(*t.ChainID) + if t.ChainID() != nil { + chainID := argBig(*(t.ChainID())) res.ChainID = &chainID } @@ -99,6 +101,10 @@ func toTransaction( res.TxIndex = argUintPtr(uint64(*txIndex)) } + if t.AccessList() != nil { + res.AccessList = t.AccessList() + } + return res } @@ -167,7 +173,7 @@ func toBlock(b *types.Block, fullTx bool) *block { for idx, txn := range b.Transactions { if fullTx { - txn.GasPrice = txn.GetGasPrice(b.Header.BaseFee) + txn.SetGasPrice(txn.GetGasPrice(b.Header.BaseFee)) res.Transactions = append( res.Transactions, toTransaction( @@ -180,7 +186,7 @@ func toBlock(b *types.Block, fullTx bool) *block { } else { res.Transactions = append( res.Transactions, - transactionHash(txn.Hash), + transactionHash(txn.Hash()), ) } } @@ -215,14 +221,14 @@ func toReceipt(src *types.Receipt, tx *types.Transaction, CumulativeGasUsed: argUint64(src.CumulativeGasUsed), LogsBloom: src.LogsBloom, Status: argUint64(*src.Status), - TxHash: tx.Hash, + TxHash: tx.Hash(), TxIndex: argUint64(txIndex), BlockHash: header.Hash, BlockNumber: argUint64(header.Number), GasUsed: argUint64(src.GasUsed), ContractAddress: src.ContractAddress, - FromAddr: tx.From, - ToAddr: tx.To, + FromAddr: tx.From(), + ToAddr: tx.To(), Logs: logs, } } @@ -376,17 +382,18 @@ func encodeToHex(b []byte) []byte { // txnArgs is the transaction argument for the rpc endpoints type txnArgs struct { - From *types.Address - To *types.Address - Gas *argUint64 - GasPrice *argBytes - GasTipCap *argBytes - GasFeeCap *argBytes - Value *argBytes - Data *argBytes - Input *argBytes - Nonce *argUint64 - Type *argUint64 + From *types.Address + To *types.Address + Gas *argUint64 + GasPrice *argBytes + GasTipCap *argBytes + GasFeeCap *argBytes + Value *argBytes + Data *argBytes + Input *argBytes + Nonce *argUint64 + Type *argUint64 + AccessList *types.TxAccessList } type progression struct { diff --git a/jsonrpc/types_test.go b/jsonrpc/types_test.go index 77bbcf6b64..b3a0f9e738 100644 --- a/jsonrpc/types_test.go +++ b/jsonrpc/types_test.go @@ -103,7 +103,7 @@ func TestToTransaction_Returns_V_R_S_ValuesWithoutLeading0(t *testing.T) { v, _ := hex.DecodeHex(hexWithLeading0) r, _ := hex.DecodeHex(hexWithLeading0) s, _ := hex.DecodeHex(hexWithLeading0) - txn := &types.Transaction{ + txn := types.NewTx(&types.MixedTxn{ Nonce: 0, GasPrice: big.NewInt(0), Gas: 0, @@ -115,7 +115,7 @@ func TestToTransaction_Returns_V_R_S_ValuesWithoutLeading0(t *testing.T) { S: new(big.Int).SetBytes(s), Hash: types.Hash{}, From: types.Address{}, - } + }) jsonTx := toTransaction(txn, nil, nil, nil) @@ -134,7 +134,7 @@ func TestToTransaction_EIP1559(t *testing.T) { v, _ := hex.DecodeHex(hexWithLeading0) r, _ := hex.DecodeHex(hexWithLeading0) s, _ := hex.DecodeHex(hexWithLeading0) - txn := &types.Transaction{ + txn := types.NewTx(&types.MixedTxn{ Nonce: 0, GasPrice: nil, GasTipCap: big.NewInt(10), @@ -148,7 +148,7 @@ func TestToTransaction_EIP1559(t *testing.T) { S: new(big.Int).SetBytes(s), Hash: types.Hash{}, From: types.Address{}, - } + }) jsonTx := toTransaction(txn, nil, nil, nil) @@ -311,21 +311,21 @@ func Test_toReceipt(t *testing.T) { t.Run("no logs", func(t *testing.T) { tx := createTestTransaction(types.StringToHash("tx1")) recipient := types.StringToAddress("2") - tx.From = types.StringToAddress("1") - tx.To = &recipient + tx.SetFrom(types.StringToAddress("1")) + tx.SetTo(&recipient) header := createTestHeader(15, nil) - rec := createTestReceipt(nil, cumulativeGasUsed, gasUsed, tx.Hash) + rec := createTestReceipt(nil, cumulativeGasUsed, gasUsed, tx.Hash()) testReceipt("testsuite/receipt-no-logs.json", toReceipt(rec, tx, 0, header, nil)) }) t.Run("with contract address", func(t *testing.T) { tx := createTestTransaction(types.StringToHash("tx1")) - tx.To = nil + tx.SetTo(nil) contractAddr := types.StringToAddress("3") header := createTestHeader(20, nil) - rec := createTestReceipt(nil, cumulativeGasUsed, gasUsed, tx.Hash) + rec := createTestReceipt(nil, cumulativeGasUsed, gasUsed, tx.Hash()) rec.ContractAddress = &contractAddr testReceipt("testsuite/receipt-contract-deployment.json", toReceipt(rec, tx, 0, header, nil)) }) @@ -333,14 +333,14 @@ func Test_toReceipt(t *testing.T) { t.Run("with logs", func(t *testing.T) { tx := createTestTransaction(types.StringToHash("tx1")) recipient := types.StringToAddress("2") - tx.From = types.StringToAddress("1") - tx.To = &recipient + tx.SetFrom(types.StringToAddress("1")) + tx.SetTo(&recipient) header := createTestHeader(30, nil) logs := createTestLogs(2, recipient) - originReceipt := createTestReceipt(logs, cumulativeGasUsed, gasUsed, tx.Hash) + originReceipt := createTestReceipt(logs, cumulativeGasUsed, gasUsed, tx.Hash()) txIdx := uint64(1) - receipt := toReceipt(originReceipt, tx, txIdx, header, toLogs(logs, 0, txIdx, header, tx.Hash)) + receipt := toReceipt(originReceipt, tx, txIdx, header, toLogs(logs, 0, txIdx, header, tx.Hash())) testReceipt("testsuite/receipt-with-logs.json", receipt) }) } diff --git a/server/server.go b/server/server.go index 47462eec45..b4495f4181 100644 --- a/server/server.go +++ b/server/server.go @@ -284,7 +284,7 @@ func NewServer(config *Config) (*Server, error) { config.Chain.Genesis.StateRoot = genesisRoot // Use the london signer with eip-155 as a fallback one - var signer crypto.TxSigner = crypto.NewLondonSigner( + var signer crypto.TxSigner = crypto.NewLondonOrBerlinSigner( uint64(m.config.Chain.Params.ChainID), config.Chain.Params.Forks.IsActive(chain.Homestead, 0), crypto.NewEIP155Signer( @@ -786,7 +786,7 @@ func (j *jsonRPCHub) TraceTxn( var targetTx *types.Transaction for _, tx := range block.Transactions { - if tx.Hash == targetTxHash { + if tx.Hash() == targetTxHash { targetTx = tx break diff --git a/state/executor.go b/state/executor.go index aaa31cabdc..362b103ad6 100644 --- a/state/executor.go +++ b/state/executor.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "math/big" + "sort" "github.com/hashicorp/go-hclog" @@ -25,6 +26,9 @@ const ( TxGas uint64 = 21000 // Per transaction not creating a contract TxGasContractCreation uint64 = 53000 // Per transaction that creates a contract + + TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list + TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list ) // GetHashByNumber returns the hash function of a block number @@ -77,15 +81,8 @@ func (e *Executor) WriteGenesis( ChainID: e.config.ChainID, } - transition := &Transition{ - logger: e.logger, - ctx: env, - state: txn, - auxState: e.state, - gasPool: uint64(env.GasLimit), - config: config, - precompiles: precompiled.NewPrecompiled(), - } + transition := NewTransition(e.logger, config, snap, txn) + transition.ctx = env for addr, account := range alloc { if account.Balance != nil { @@ -142,7 +139,7 @@ func (e *Executor) ProcessBlock( } for _, t := range block.Transactions { - if t.Gas > block.Header.GasLimit { + if t.Gas() > block.Header.GasLimit { continue } @@ -176,7 +173,7 @@ func (e *Executor) BeginTxn( ) (*Transition, error) { forkConfig := e.config.Forks.At(header.Number) - auxSnap2, err := e.state.NewSnapshotAt(parentRoot) + snap, err := e.state.NewSnapshotAt(parentRoot) if err != nil { return nil, err } @@ -189,7 +186,7 @@ func (e *Executor) BeginTxn( } } - newTxn := NewTxn(auxSnap2) + newTxn := NewTxn(snap) txCtx := runtime.TxContext{ Coinbase: coinbaseReceiver, @@ -202,60 +199,47 @@ func (e *Executor) BeginTxn( BurnContract: burnContract, } - txn := &Transition{ - logger: e.logger, - ctx: txCtx, - state: newTxn, - snap: auxSnap2, - getHash: e.GetHash(header), - auxState: e.state, - config: forkConfig, - gasPool: uint64(txCtx.GasLimit), - - receipts: []*types.Receipt{}, - totalGas: 0, - - evm: evm.NewEVM(), - precompiles: precompiled.NewPrecompiled(), - PostHook: e.PostHook, - } + t := NewTransition(e.logger, forkConfig, snap, newTxn) + t.PostHook = e.PostHook + t.getHash = e.GetHash(header) + t.ctx = txCtx + t.gasPool = uint64(txCtx.GasLimit) // enable contract deployment allow list (if any) if e.config.ContractDeployerAllowList != nil { - txn.deploymentAllowList = addresslist.NewAddressList(txn, contracts.AllowListContractsAddr) + t.deploymentAllowList = addresslist.NewAddressList(t, contracts.AllowListContractsAddr) } if e.config.ContractDeployerBlockList != nil { - txn.deploymentBlockList = addresslist.NewAddressList(txn, contracts.BlockListContractsAddr) + t.deploymentBlockList = addresslist.NewAddressList(t, contracts.BlockListContractsAddr) } // enable transactions allow list (if any) if e.config.TransactionsAllowList != nil { - txn.txnAllowList = addresslist.NewAddressList(txn, contracts.AllowListTransactionsAddr) + t.txnAllowList = addresslist.NewAddressList(t, contracts.AllowListTransactionsAddr) } if e.config.TransactionsBlockList != nil { - txn.txnBlockList = addresslist.NewAddressList(txn, contracts.BlockListTransactionsAddr) + t.txnBlockList = addresslist.NewAddressList(t, contracts.BlockListTransactionsAddr) } // enable transactions allow list (if any) if e.config.BridgeAllowList != nil { - txn.bridgeAllowList = addresslist.NewAddressList(txn, contracts.AllowListBridgeAddr) + t.bridgeAllowList = addresslist.NewAddressList(t, contracts.AllowListBridgeAddr) } if e.config.BridgeBlockList != nil { - txn.bridgeBlockList = addresslist.NewAddressList(txn, contracts.BlockListBridgeAddr) + t.bridgeBlockList = addresslist.NewAddressList(t, contracts.BlockListBridgeAddr) } - return txn, nil + return t, nil } type Transition struct { logger hclog.Logger // dummy - auxState State - snap Snapshot + snap Snapshot config chain.ForksInTime state *Txn @@ -280,15 +264,24 @@ type Transition struct { txnBlockList *addresslist.AddressList bridgeAllowList *addresslist.AddressList bridgeBlockList *addresslist.AddressList + + // journaling + journal *runtime.Journal + journalRevisions []runtime.JournalRevision + + accessList *runtime.AccessList } -func NewTransition(config chain.ForksInTime, snap Snapshot, radix *Txn) *Transition { +func NewTransition(logger hclog.Logger, config chain.ForksInTime, snap Snapshot, radix *Txn) *Transition { return &Transition{ + logger: logger, config: config, state: radix, snap: snap, evm: evm.NewEVM(), precompiles: precompiled.NewPrecompiled(), + journal: &runtime.Journal{}, + accessList: runtime.NewAccessList(), } } @@ -334,17 +327,16 @@ var emptyFrom = types.Address{} // Write writes another transaction to the executor func (t *Transition) Write(txn *types.Transaction) error { - var err error - - if txn.From == emptyFrom && - (txn.Type == types.LegacyTx || txn.Type == types.DynamicFeeTx) { + if txn.From() == emptyFrom && txn.Type() != types.StateTx { // Decrypt the from address signer := crypto.NewSigner(t.config, uint64(t.ctx.ChainID)) - txn.From, err = signer.Sender(txn) + from, err := signer.Sender(txn) if err != nil { return NewTransitionApplicationError(err, false) } + + txn.SetFrom(from) } // Make a local copy and apply the transaction @@ -363,8 +355,8 @@ func (t *Transition) Write(txn *types.Transaction) error { receipt := &types.Receipt{ CumulativeGasUsed: t.totalGas, - TransactionType: txn.Type, - TxHash: txn.Hash, + TransactionType: txn.Type(), + TxHash: txn.Hash(), GasUsed: result.GasUsed, } @@ -380,8 +372,8 @@ func (t *Transition) Write(txn *types.Transaction) error { } // if the transaction created a contract, store the creation address in the receipt. - if msg.To == nil { - receipt.ContractAddress = crypto.CreateAddress(msg.From, txn.Nonce).Ptr() + if msg.To() == nil { + receipt.ContractAddress = crypto.CreateAddress(msg.From(), txn.Nonce()).Ptr() } // Set the receipt logs and create a bloom for filtering @@ -432,7 +424,7 @@ func (t Transition) checkSenderAccount(msg *types.Transaction) bool { return true } - codeHash := t.state.GetCodeHash(msg.From) + codeHash := t.state.GetCodeHash(msg.From()) return codeHash == types.ZeroHash || codeHash == types.EmptyCodeHash } @@ -440,15 +432,17 @@ func (t Transition) checkSenderAccount(msg *types.Transaction) bool { // Apply applies a new transaction func (t *Transition) Apply(msg *types.Transaction) (*runtime.ExecutionResult, error) { if !t.checkSenderAccount(msg) { - return nil, fmt.Errorf("%w: address %v, codehash: %v", ErrSenderNoEOA, msg.From.String(), - t.state.GetCodeHash(msg.From).String()) + sender := msg.From() + + return nil, fmt.Errorf("%w: address %s, codehash: %v", ErrSenderNoEOA, sender.String(), + t.state.GetCodeHash(sender).String()) } - s := t.state.Snapshot() + s := t.Snapshot() result, err := t.apply(msg) if err != nil { - if revertErr := t.state.RevertToSnapshot(s); revertErr != nil { + if revertErr := t.RevertToSnapshot(s); revertErr != nil { return nil, revertErr } } @@ -467,9 +461,21 @@ func (t *Transition) ContextPtr() *runtime.TxContext { } func (t *Transition) subGasLimitPrice(msg *types.Transaction) error { - upfrontGasCost := new(big.Int).Mul(new(big.Int).SetUint64(msg.Gas), msg.GetGasPrice(t.ctx.BaseFee.Uint64())) + upfrontGasCost := new(big.Int).Mul(new(big.Int).SetUint64(msg.Gas()), msg.GetGasPrice(t.ctx.BaseFee.Uint64())) + balanceCheck := new(big.Int).Set(upfrontGasCost) + + if msg.Type() == types.DynamicFeeTx { + balanceCheck.Add(balanceCheck, msg.Value()) + balanceCheck.SetUint64(msg.Gas()) + balanceCheck = balanceCheck.Mul(balanceCheck, msg.GasFeeCap()) + balanceCheck.Add(balanceCheck, msg.Value()) + } + + if have, want := t.state.GetBalance(msg.From()), balanceCheck; have.Cmp(want) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, msg.From(), have, want) + } - if err := t.state.SubBalance(msg.From, upfrontGasCost); err != nil { + if err := t.state.SubBalance(msg.From(), upfrontGasCost); err != nil { if errors.Is(err, runtime.ErrNotEnoughFunds) { return ErrNotEnoughFundsForGas } @@ -481,10 +487,17 @@ func (t *Transition) subGasLimitPrice(msg *types.Transaction) error { } func (t *Transition) nonceCheck(msg *types.Transaction) error { - nonce := t.state.GetNonce(msg.From) + currentNonce := t.state.GetNonce(msg.From()) - if nonce != msg.Nonce { - return ErrNonceIncorrect + if msgNonce := msg.Nonce(); currentNonce < msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh, + msg.From(), msgNonce, currentNonce) + } else if currentNonce > msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow, + msg.From(), msgNonce, currentNonce) + } else if currentNonce+1 < currentNonce { + return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, + msg.From(), currentNonce) } return nil @@ -497,24 +510,24 @@ func (t *Transition) checkDynamicFees(msg *types.Transaction) error { return nil } - if msg.Type == types.DynamicFeeTx { - if msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0 { + if msg.Type() == types.DynamicFeeTx { + if msg.GasFeeCap().BitLen() == 0 && msg.GasTipCap().BitLen() == 0 { return nil } - if l := msg.GasFeeCap.BitLen(); l > 256 { + if l := msg.GasFeeCap().BitLen(); l > 256 { return fmt.Errorf("%w: address %v, GasFeeCap bit length: %d", ErrFeeCapVeryHigh, - msg.From.String(), l) + msg.From().String(), l) } - if l := msg.GasTipCap.BitLen(); l > 256 { + if l := msg.GasTipCap().BitLen(); l > 256 { return fmt.Errorf("%w: address %v, GasTipCap bit length: %d", ErrTipVeryHigh, - msg.From.String(), l) + msg.From().String(), l) } - if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 { + if msg.GasFeeCap().Cmp(msg.GasTipCap()) < 0 { return fmt.Errorf("%w: address %v, GasTipCap: %s, GasFeeCap: %s", ErrTipAboveFeeCap, - msg.From.String(), msg.GasTipCap, msg.GasFeeCap) + msg.From().String(), msg.GasTipCap(), msg.GasFeeCap()) } } @@ -522,7 +535,7 @@ func (t *Transition) checkDynamicFees(msg *types.Transaction) error { // as part of header validation. if gasFeeCap := msg.GetGasFeeCap(); gasFeeCap.Cmp(t.ctx.BaseFee) < 0 { return fmt.Errorf("%w: address %v, GasFeeCap/GasPrice: %s, BaseFee: %s", ErrFeeCapTooLow, - msg.From.String(), gasFeeCap, t.ctx.BaseFee) + msg.From().String(), gasFeeCap, t.ctx.BaseFee) } return nil @@ -532,12 +545,15 @@ func (t *Transition) checkDynamicFees(msg *types.Transaction) error { // surfacing of these errors reject the transaction thus not including it in the block var ( - ErrNonceIncorrect = errors.New("incorrect nonce") + ErrNonceTooLow = errors.New("nonce too low") + ErrNonceTooHigh = errors.New("nonce too high") + ErrNonceMax = errors.New("nonce has max value") ErrSenderNoEOA = errors.New("sender not an eoa") ErrNotEnoughFundsForGas = errors.New("not enough funds to cover gas costs") ErrBlockLimitReached = errors.New("gas limit reached in the pool") ErrIntrinsicGasOverflow = errors.New("overflow in intrinsic gas calculation") ErrNotEnoughIntrinsicGas = errors.New("not enough gas supplied for intrinsic gas costs") + ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") // ErrTipAboveFeeCap is a sanity error to ensure no one is able to specify a // transaction with a tip higher than the total fee cap. @@ -588,7 +604,7 @@ func NewGasLimitReachedTransitionApplicationError(err error) *GasLimitReachedTra func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, error) { var err error - if msg.Type == types.StateTx { + if msg.Type() == types.StateTx { err = checkAndProcessStateTx(msg) } else { err = checkAndProcessTx(msg, t) @@ -599,12 +615,12 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er } // the amount of gas required is available in the block - if err = t.subGasPool(msg.Gas); err != nil { + if err = t.subGasPool(msg.Gas()); err != nil { return nil, NewGasLimitReachedTransitionApplicationError(err) } if t.ctx.Tracer != nil { - t.ctx.Tracer.TxStart(msg.Gas) + t.ctx.Tracer.TxStart(msg.Gas()) } // 4. there is no overflow when calculating intrinsic gas @@ -614,31 +630,49 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er } // the purchased gas is enough to cover intrinsic usage - gasLeft := msg.Gas - intrinsicGasCost + gasLeft := msg.Gas() - intrinsicGasCost // because we are working with unsigned integers for gas, the `>` operator is used instead of the more intuitive `<` - if gasLeft > msg.Gas { + if gasLeft > msg.Gas() { return nil, NewTransitionApplicationError(ErrNotEnoughIntrinsicGas, false) } gasPrice := msg.GetGasPrice(t.ctx.BaseFee.Uint64()) - value := new(big.Int).Set(msg.Value) + value := new(big.Int) + + if msg.Value() != nil { + value = value.Set(msg.Value()) + } // set the specific transaction fields in the context t.ctx.GasPrice = types.BytesToHash(gasPrice.Bytes()) - t.ctx.Origin = msg.From + t.ctx.Origin = msg.From() + + // set up initial access list + initialAccessList := runtime.NewAccessList() + if t.config.Berlin { // check if berlin fork is activated or not + initialAccessList.PrepareAccessList(msg.From(), msg.To(), t.precompiles.Addrs, msg.AccessList()) + } + + t.accessList = initialAccessList var result *runtime.ExecutionResult if msg.IsContractCreation() { - result = t.Create2(msg.From, msg.Input, value, gasLeft) + result = t.Create2(msg.From(), msg.Input(), value, gasLeft) } else { - if err := t.state.IncrNonce(msg.From); err != nil { + if err := t.state.IncrNonce(msg.From()); err != nil { return nil, err } - result = t.Call2(msg.From, *msg.To, msg.Input, value, gasLeft) + + result = t.Call2(msg.From(), *(msg.To()), msg.Input(), value, gasLeft) + } + + refundQuotient := LegacyRefundQuotient + if t.config.London { + refundQuotient = LondonRefundQuotient } refund := t.state.GetRefund() - result.UpdateGasUsed(msg.Gas, refund) + result.UpdateGasUsed(msg.Gas(), refund, refundQuotient) if t.ctx.Tracer != nil { t.ctx.Tracer.TxEnd(result.GasLeft) @@ -646,8 +680,7 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er // Refund the sender remaining := new(big.Int).Mul(new(big.Int).SetUint64(result.GasLeft), gasPrice) - - t.state.AddBalance(msg.From, remaining) + t.state.AddBalance(msg.From(), remaining) // Spec: https://eips.ethereum.org/EIPS/eip-1559#specification // Define effective tip based on tx type. @@ -665,12 +698,14 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er coinbaseFee := new(big.Int).Mul(new(big.Int).SetUint64(result.GasUsed), effectiveTip) t.state.AddBalance(t.ctx.Coinbase, coinbaseFee) + //nolint:godox + // TODO - burning of base fee should not be done in the EVM // Burn some amount if the london hardfork is applied. // Basically, burn amount is just transferred to the current burn contract. - if t.config.London && msg.Type != types.StateTx { - burnAmount := new(big.Int).Mul(new(big.Int).SetUint64(result.GasUsed), t.ctx.BaseFee) - t.state.AddBalance(t.ctx.BurnContract, burnAmount) - } + // if t.config.London && msg.Type() != types.StateTx { + // burnAmount := new(big.Int).Mul(new(big.Int).SetUint64(result.GasUsed), t.ctx.BaseFee) + // t.state.AddBalance(t.ctx.BurnContract, burnAmount) + // } // return gas to the pool t.addGasPool(result.GasLeft) @@ -786,7 +821,7 @@ func (t *Transition) applyCall( } } - snapshot := t.state.Snapshot() + snapshot := t.Snapshot() t.state.TouchAccount(c.Address) if callType == runtime.Call { @@ -805,7 +840,7 @@ func (t *Transition) applyCall( result = t.run(c, host) if result.Failed() { - if err := t.state.RevertToSnapshot(snapshot); err != nil { + if err := t.RevertToSnapshot(snapshot); err != nil { return &runtime.ExecutionResult{ GasLeft: c.Gas, Err: err, @@ -846,6 +881,13 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim } } + //Berlin: check + // we add this to the access-list before taking a snapshot. Even if the creation fails, + // the access-list change should not be rolled back according to EIP2929 specs + if t.config.Berlin { + t.AddAddressToAccessList(c.Address) + } + // Check if there is a collision and the address already exists if t.hasCodeOrNonce(c.Address) { return &runtime.ExecutionResult{ @@ -855,7 +897,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim } // Take snapshot of the current state - snapshot := t.state.Snapshot() + snapshot := t.Snapshot() if t.config.EIP158 { // Force the creation of the account @@ -918,7 +960,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim result = t.run(c, host) if result.Failed() { - if err := t.state.RevertToSnapshot(snapshot); err != nil { + if err := t.RevertToSnapshot(snapshot); err != nil { return &runtime.ExecutionResult{ Err: err, } @@ -929,7 +971,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim if t.config.EIP158 && len(result.ReturnValue) > SpuriousDragonMaxCodeSize { // Contract size exceeds 'SpuriousDragon' size limit - if err := t.state.RevertToSnapshot(snapshot); err != nil { + if err := t.RevertToSnapshot(snapshot); err != nil { return &runtime.ExecutionResult{ Err: err, } @@ -941,6 +983,20 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim } } + // Reject code starting with 0xEF if EIP-3541 is enabled. + if result.Err == nil && len(result.ReturnValue) >= 1 && result.ReturnValue[0] == 0xEF && t.config.London { + if err := t.RevertToSnapshot(snapshot); err != nil { + return &runtime.ExecutionResult{ + Err: err, + } + } + + return &runtime.ExecutionResult{ + GasLeft: 0, + Err: runtime.ErrInvalidCode, + } + } + gasCost := uint64(len(result.ReturnValue)) * 200 if result.GasLeft < gasCost { @@ -949,7 +1005,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim // Out of gas creating the contract if t.config.Homestead { - if err := t.state.RevertToSnapshot(snapshot); err != nil { + if err := t.RevertToSnapshot(snapshot); err != nil { return &runtime.ExecutionResult{ Err: err, } @@ -1061,7 +1117,7 @@ func (t *Transition) GetNonce(addr types.Address) uint64 { } func (t *Transition) Selfdestruct(addr types.Address, beneficiary types.Address) { - if !t.state.HasSuicided(addr) { + if !t.config.London && !t.state.HasSuicided(addr) { t.state.AddRefund(24000) } @@ -1137,7 +1193,7 @@ func TransactionGasCost(msg *types.Transaction, isHomestead, isIstanbul bool) (u cost += TxGas } - payload := msg.Input + payload := msg.Input() if len(payload) > 0 { zeros := uint64(0) @@ -1167,6 +1223,11 @@ func TransactionGasCost(msg *types.Transaction, isHomestead, isIstanbul bool) (u cost += zeros * 4 } + if msg.AccessList() != nil { + cost += uint64(len(msg.AccessList())) * TxAccessListAddressGas + cost += uint64(msg.AccessList().StorageKeys()) * TxAccessListStorageKeyGas + } + return cost, nil } @@ -1198,28 +1259,28 @@ func checkAndProcessTx(msg *types.Transaction, t *Transition) error { } func checkAndProcessStateTx(msg *types.Transaction) error { - if msg.GasPrice.Cmp(big.NewInt(0)) != 0 { + if msg.GasPrice().Cmp(big.NewInt(0)) != 0 { return NewTransitionApplicationError( errors.New("gasPrice of state transaction must be zero"), true, ) } - if msg.Gas != types.StateTransactionGasLimit { + if msg.Gas() != types.StateTransactionGasLimit { return NewTransitionApplicationError( fmt.Errorf("gas of state transaction must be %d", types.StateTransactionGasLimit), true, ) } - if msg.From != contracts.SystemCaller { + if msg.From() != contracts.SystemCaller { return NewTransitionApplicationError( - fmt.Errorf("state transaction sender must be %v, but got %v", contracts.SystemCaller, msg.From), + fmt.Errorf("state transaction sender must be %v, but got %v", contracts.SystemCaller, msg.From()), true, ) } - if msg.To == nil || *msg.To == types.ZeroAddress { + if msg.To() == nil || *(msg.To()) == types.ZeroAddress { return NewTransitionApplicationError( errors.New("to of state transaction must be specified"), true, @@ -1258,3 +1319,63 @@ func (t *Transition) captureCallEnd(c *runtime.Contract, result *runtime.Executi result.Err, ) } + +func (t *Transition) AddToJournal(j runtime.JournalEntry) { + t.journal.Append(j) +} + +func (t *Transition) Snapshot() int { + snapshot := t.state.Snapshot() + t.journalRevisions = append(t.journalRevisions, runtime.JournalRevision{ID: snapshot, Index: t.journal.Len()}) + + return snapshot +} + +func (t *Transition) RevertToSnapshot(snapshot int) error { + if err := t.state.RevertToSnapshot(snapshot); err != nil { + return err + } + + // Find the snapshot in the stack of valid snapshots. + idx := sort.Search(len(t.journalRevisions), func(i int) bool { + return t.journalRevisions[i].ID >= snapshot + }) + + if idx == len(t.journalRevisions) || t.journalRevisions[idx].ID != snapshot { + return fmt.Errorf("journal revision id %d cannot be reverted", snapshot) + } + + journalIndex := t.journalRevisions[idx].Index + + // Replay the journal to undo changes and remove invalidated snapshots + t.journal.Revert(t, journalIndex) + t.journalRevisions = t.journalRevisions[:idx] + + return nil +} + +func (t *Transition) AddSlotToAccessList(addr types.Address, slot types.Hash) { + t.journal.Append(&runtime.AccessListAddSlotChange{Address: addr, Slot: slot}) + t.accessList.AddSlot(addr, slot) +} + +func (t *Transition) AddAddressToAccessList(addr types.Address) { + t.journal.Append(&runtime.AccessListAddAccountChange{Address: addr}) + t.accessList.AddAddress(addr) +} + +func (t *Transition) ContainsAccessListAddress(addr types.Address) bool { + return t.accessList.ContainsAddress(addr) +} + +func (t *Transition) ContainsAccessListSlot(addr types.Address, slot types.Hash) (bool, bool) { + return t.accessList.Contains(addr, slot) +} + +func (t *Transition) DeleteAccessListAddress(addr types.Address) { + t.accessList.DeleteAddress(addr) +} + +func (t *Transition) DeleteAccessListSlot(addr types.Address, slot types.Hash) { + t.accessList.DeleteSlot(addr, slot) +} diff --git a/state/executor_test.go b/state/executor_test.go index 6d3ecb82d0..31428cbab5 100644 --- a/state/executor_test.go +++ b/state/executor_test.go @@ -5,11 +5,13 @@ import ( "math/big" "testing" + "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/state/runtime" + "github.com/0xPolygon/polygon-edge/state/runtime/evm" "github.com/0xPolygon/polygon-edge/types" ) @@ -35,7 +37,7 @@ func TestOverride(t *testing.T) { balance := big.NewInt(2) code := []byte{0x1} - tt := NewTransition(chain.ForksInTime{}, state, newTxn(state)) + tt := NewTransition(hclog.NewNullLogger(), chain.ForksInTime{}, state, newTxn(state)) require.Empty(t, tt.state.GetCode(types.ZeroAddress)) @@ -78,11 +80,11 @@ func Test_Transition_checkDynamicFees(t *testing.T) { { name: "happy path", baseFee: big.NewInt(100), - tx: &types.Transaction{ + tx: types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasFeeCap: big.NewInt(100), GasTipCap: big.NewInt(100), - }, + }), wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { assert.NoError(t, err, i) @@ -92,11 +94,11 @@ func Test_Transition_checkDynamicFees(t *testing.T) { { name: "happy path with empty values", baseFee: big.NewInt(0), - tx: &types.Transaction{ + tx: types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0), - }, + }), wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { assert.NoError(t, err, i) @@ -106,11 +108,11 @@ func Test_Transition_checkDynamicFees(t *testing.T) { { name: "gas fee cap less than base fee", baseFee: big.NewInt(20), - tx: &types.Transaction{ + tx: types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasFeeCap: big.NewInt(10), GasTipCap: big.NewInt(0), - }, + }), wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { expectedError := fmt.Sprintf("max fee per gas less than block base fee: "+ "address %s, GasFeeCap/GasPrice: 10, BaseFee: 20", types.ZeroAddress) @@ -122,11 +124,11 @@ func Test_Transition_checkDynamicFees(t *testing.T) { { name: "gas fee cap less than tip cap", baseFee: big.NewInt(5), - tx: &types.Transaction{ + tx: types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasFeeCap: big.NewInt(10), GasTipCap: big.NewInt(15), - }, + }), wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { expectedError := fmt.Sprintf("max priority fee per gas higher than max fee per gas: "+ "address %s, GasTipCap: 15, GasFeeCap: 10", types.ZeroAddress) @@ -157,3 +159,114 @@ func Test_Transition_checkDynamicFees(t *testing.T) { }) } } + +// Tests for EIP-2929 +func Test_Transition_EIP2929(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + code []byte + gasConsumed uint64 + }{ + { + name: "Test 1: Check access list for EXTCODEHASH,EXTCODESIZE and BALANCE opcodes", + code: []byte{ + // WarmStorageReadCostEIP2929 should be charged since precompiles address are in access list + uint8(evm.PUSH1), 1, uint8(evm.EXTCODEHASH), uint8(evm.POP), + uint8(evm.PUSH1), 2, uint8(evm.EXTCODESIZE), uint8(evm.POP), + uint8(evm.PUSH1), 3, uint8(evm.BALANCE), uint8(evm.POP), + + uint8(evm.PUSH1), 0xe1, uint8(evm.EXTCODEHASH), uint8(evm.POP), + uint8(evm.PUSH1), 0xe2, uint8(evm.EXTCODESIZE), uint8(evm.POP), + uint8(evm.PUSH1), 0xe3, uint8(evm.BALANCE), uint8(evm.POP), + // cost should be WarmStorageReadCostEIP2929, since addresses are present in access list + uint8(evm.PUSH1), 0xe2, uint8(evm.EXTCODEHASH), uint8(evm.POP), + uint8(evm.PUSH1), 0xe3, uint8(evm.EXTCODESIZE), uint8(evm.POP), + uint8(evm.PUSH1), 0xe1, uint8(evm.BALANCE), uint8(evm.POP), + + uint8(evm.ORIGIN), uint8(evm.BALANCE), uint8(evm.POP), + uint8(evm.ADDRESS), uint8(evm.BALANCE), uint8(evm.POP), + + uint8(evm.STOP), + }, + gasConsumed: uint64(8653), + }, + { + name: "Test 2: Check Storage opcodes", + code: []byte{ + // Add slot `0xe1` to access list, ColdAccountAccessCostEIP2929 charged + uint8(evm.PUSH1), 0xe1, uint8(evm.SLOAD), uint8(evm.POP), + // Write to `0xe1` which is already in access list, WarmStorageReadCostEIP2929 charged + uint8(evm.PUSH1), 0xf1, uint8(evm.PUSH1), 0xe1, uint8(evm.SSTORE), + // Write to `0xe2` which is not in access list, ColdAccountAccessCostEIP2929 charged + uint8(evm.PUSH1), 0xf1, uint8(evm.PUSH1), 0xe2, uint8(evm.SSTORE), + // Write again to `0xe2`, WarmStorageReadCostEIP2929 charged since `0xe2` already in access list + uint8(evm.PUSH1), 0x11, uint8(evm.PUSH1), 0xe2, uint8(evm.SSTORE), + // SLOAD `0xe2`, address present in access list + uint8(evm.PUSH1), 0xe2, uint8(evm.SLOAD), + // SLOAD `0xe3`, ColdStorageReadCostEIP2929 charged since address not present in access list + uint8(evm.PUSH1), 0xe3, uint8(evm.SLOAD), + }, + gasConsumed: uint64(46529), + }, + { + name: "Test 3: Check EXTCODECOPY opcode", + code: []byte{ + // extcodecopy( 0xff,0,0,0,0) + uint8(evm.PUSH1), 0x00, uint8(evm.PUSH1), 0x00, uint8(evm.PUSH1), 0x00, + uint8(evm.PUSH1), 0xff, uint8(evm.EXTCODECOPY), + // extcodecopy( 0xff,0,0,0,0) + uint8(evm.PUSH1), 0x00, uint8(evm.PUSH1), 0x00, uint8(evm.PUSH1), 0x00, + uint8(evm.PUSH1), 0xff, uint8(evm.EXTCODECOPY), + // extcodecopy( this,0,0,0,0) + uint8(evm.PUSH1), 0x00, uint8(evm.PUSH1), 0x00, uint8(evm.PUSH1), 0x00, + uint8(evm.ADDRESS), uint8(evm.EXTCODECOPY), + uint8(evm.STOP), + }, + gasConsumed: uint64(2835), + }, + { + name: "Test 4: Check Call opcodes", + code: []byte{ + // Precompile `0x02` + uint8(evm.PUSH1), 0x0, uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), + uint8(evm.PUSH1), 0x02, uint8(evm.PUSH1), 0x0, uint8(evm.CALL), uint8(evm.POP), + + // Call `0xce` + uint8(evm.PUSH1), 0x0, uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), + uint8(evm.PUSH1), 0xce, uint8(evm.PUSH1), 0x0, uint8(evm.CALL), uint8(evm.POP), + + // Delegate Call `0xce` + uint8(evm.PUSH1), 0x0, uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), + uint8(evm.PUSH1), 0xce, uint8(evm.PUSH1), 0x0, uint8(evm.DELEGATECALL), uint8(evm.POP), + + // Static Call `0xbb` + uint8(evm.PUSH1), 0x0, uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), + uint8(evm.PUSH1), 0xbb, uint8(evm.PUSH1), 0x0, uint8(evm.STATICCALL), uint8(evm.POP), + }, + gasConsumed: uint64(5492), + }, + } + + for _, testCase := range tests { + tt := testCase + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + state := newStateWithPreState(nil) + addr := types.BytesToAddress([]byte("contract")) + txn := newTxn(state) + txn.SetCode(addr, tt.code) + + enabledForks := chain.AllForksEnabled.At(0) + transition := NewTransition(hclog.NewNullLogger(), enabledForks, state, txn) + initialAccessList := runtime.NewAccessList() + initialAccessList.PrepareAccessList(transition.ctx.Origin, &addr, transition.precompiles.Addrs, nil) + transition.accessList = initialAccessList + + result := transition.Call2(transition.ctx.Origin, addr, nil, big.NewInt(0), uint64(1000000)) + assert.Equal(t, tt.gasConsumed, result.GasUsed, "Gas consumption for %s is inaccurate according to EIP 2929", tt.name) + }) + } +} diff --git a/state/runtime/access_list.go b/state/runtime/access_list.go new file mode 100644 index 0000000000..8b806931a5 --- /dev/null +++ b/state/runtime/access_list.go @@ -0,0 +1,115 @@ +package runtime + +import ( + "github.com/0xPolygon/polygon-edge/types" +) + +type AccessList map[types.Address]map[types.Hash]struct{} + +func NewAccessList() *AccessList { + al := make(AccessList) + + return &al +} + +// Checks if address is present within the access list. +func (al *AccessList) ContainsAddress(address types.Address) bool { + _, ok := (*al)[address] + + return ok +} + +// Contains checks if a slot is present in an account. +// Returns two boolean flags: `accountPresent` and `slotPresent`. +func (al *AccessList) Contains(address types.Address, slot types.Hash) (bool, bool) { + var ( + addrPresent, slotPresent bool + slots map[types.Hash]struct{} + ) + + slots, addrPresent = (*al)[address] + if addrPresent { + _, slotPresent = slots[slot] + } + + return addrPresent, slotPresent +} + +// Copy creates an deep copy of provided AccessList. +func (al *AccessList) Copy() *AccessList { + cp := make(AccessList, len(*al)) + + for addr, slotMap := range *al { + cpSlotMap := make(map[types.Hash]struct{}, len(slotMap)) + for slotHash := range slotMap { + cpSlotMap[slotHash] = struct{}{} + } + + cp[addr] = cpSlotMap + } + + return &cp +} + +// AddAddress adds an address to the access list +func (al *AccessList) AddAddress(address ...types.Address) { + for _, addr := range address { + if _, exists := (*al)[addr]; exists { + continue + } + + (*al)[addr] = make(map[types.Hash]struct{}) + } +} + +// This function adds the specified address and slot pairs to the access list +func (al *AccessList) AddSlot(address types.Address, slot ...types.Hash) { + slotMap, addressExists := (*al)[address] + if !addressExists { + slotMap = make(map[types.Hash]struct{}) + (*al)[address] = slotMap + } + + for _, s := range slot { + _, slotPresent := slotMap[s] + if !slotPresent { + slotMap[s] = struct{}{} + } + } +} + +// PrepareAccessList prepares the access list for a transaction by adding addresses and storage slots. +// The precompiled contract addresses are added to the access list as well. +func (al *AccessList) PrepareAccessList( + from types.Address, + to *types.Address, + precompiles []types.Address, + txAccessList types.TxAccessList) { + al.AddAddress(from) + + if to != nil { + al.AddAddress(*to) + } + + // add the precompiles + al.AddAddress(precompiles...) + + // add accessList provided with access list and dynamic tx + for _, accessListTuple := range txAccessList { + al.AddAddress(accessListTuple.Address) + al.AddSlot(accessListTuple.Address, accessListTuple.StorageKeys...) + } +} + +// DeleteAddress deletes the specified address from the AccessList. +func (al *AccessList) DeleteAddress(address types.Address) { + delete(*al, address) +} + +// DeleteSlot deletes the specified slot from the access list for the given address. +// If the address is not found in the access list, the method does nothing. +func (al *AccessList) DeleteSlot(address types.Address, slot types.Hash) { + if slotMap, ok := (*al)[address]; ok { + delete(slotMap, slot) + } +} diff --git a/state/runtime/access_list_test.go b/state/runtime/access_list_test.go new file mode 100644 index 0000000000..79e8311157 --- /dev/null +++ b/state/runtime/access_list_test.go @@ -0,0 +1,99 @@ +package runtime + +import ( + "testing" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/assert" +) + +var ( + address1 = types.BytesToAddress([]byte("address1")) + address2 = types.BytesToAddress([]byte("address2")) + address3 = types.BytesToAddress([]byte("address3")) + slotHash = types.BytesToHash([]byte("slot")) +) + +func createInitialAccessList() *AccessList { + initialAccessList := NewAccessList() + (*initialAccessList)[address1] = make(map[types.Hash]struct{}) + (*initialAccessList)[address1][slotHash] = struct{}{} + (*initialAccessList)[address2] = make(map[types.Hash]struct{}) + + return initialAccessList +} + +func TestContainsAddress(t *testing.T) { + t.Parallel() + + initialAccessList := createInitialAccessList() + assert.True(t, initialAccessList.ContainsAddress(address1)) + assert.False(t, initialAccessList.ContainsAddress(address3)) +} + +func TestContains(t *testing.T) { + t.Parallel() + + initialAccessList := createInitialAccessList() + + address1Present, slotHashPresent := initialAccessList.Contains(address1, slotHash) + assert.True(t, address1Present) + assert.True(t, slotHashPresent) + + address3Present, slotHashPresent := initialAccessList.Contains(address3, slotHash) + assert.False(t, address3Present) + assert.False(t, slotHashPresent) +} + +func TestCopy(t *testing.T) { + t.Parallel() + + initialAccessList := createInitialAccessList() + copiedAccessList := initialAccessList.Copy() + assert.Equal(t, initialAccessList, copiedAccessList) +} + +func TestAddAddress(t *testing.T) { + t.Parallel() + + initialAccessList := createInitialAccessList() + finalAccessList := createInitialAccessList() + + initialAccessList.AddAddress(address1) + assert.Equal(t, finalAccessList, initialAccessList) + + initialAccessList.AddAddress(address3) + + (*finalAccessList)[address3] = make(map[types.Hash]struct{}) + assert.Equal(t, finalAccessList, initialAccessList) +} + +func TestAddSlot(t *testing.T) { + t.Parallel() + + initialAccessList := createInitialAccessList() + finalAccessList := createInitialAccessList() + + // add address1 and slotHash + initialAccessList.AddSlot(address1, slotHash) + assert.Equal(t, finalAccessList, initialAccessList) + + // add address2 and slotHash + initialAccessList.AddSlot(address2, slotHash) + addr2Exists, slot2Exists := initialAccessList.Contains(address2, slotHash) + assert.True(t, addr2Exists) + assert.True(t, slot2Exists) + + (*finalAccessList)[address2][slotHash] = struct{}{} + assert.Equal(t, finalAccessList, initialAccessList) + + // add address3 and slotHash + initialAccessList.AddSlot(address3, slotHash) + addr3Exists, slot3Exists := initialAccessList.Contains(address3, slotHash) + assert.True(t, addr3Exists) + assert.True(t, slot3Exists) + + (*finalAccessList)[address3] = make(map[types.Hash]struct{}) + (*finalAccessList)[address3][slotHash] = struct{}{} + assert.Equal(t, finalAccessList, initialAccessList) +} diff --git a/state/runtime/evm/dispatch_table.go b/state/runtime/evm/dispatch_table.go index b20372e1a7..ec7b44b4c5 100644 --- a/state/runtime/evm/dispatch_table.go +++ b/state/runtime/evm/dispatch_table.go @@ -61,7 +61,7 @@ func init() { register(SLT, handler{inst: opSlt, stack: 2, gas: 3}) register(SGT, handler{inst: opSgt, stack: 2, gas: 3}) - register(SIGNEXTEND, handler{inst: opSignExtension, stack: 1, gas: 5}) + register(SIGNEXTEND, handler{inst: opSignExtension, stack: 2, gas: 5}) register(SHL, handler{inst: opShl, stack: 2, gas: 3}) register(SHR, handler{inst: opShr, stack: 2, gas: 3}) diff --git a/state/runtime/evm/evm_fuzz_test.go b/state/runtime/evm/evm_fuzz_test.go index c7c1889d4b..ed611e443f 100644 --- a/state/runtime/evm/evm_fuzz_test.go +++ b/state/runtime/evm/evm_fuzz_test.go @@ -58,11 +58,9 @@ func (m *mockHostF) SetStorage( } func (m *mockHostF) SetState(addr types.Address, key types.Hash, value types.Hash) { - return } func (m *mockHostF) SetNonPayable(nonPayable bool) { - return } func (m *mockHostF) GetBalance(addr types.Address) *big.Int { @@ -88,7 +86,6 @@ func (m *mockHostF) GetCode(addr types.Address) []byte { } func (m *mockHostF) Selfdestruct(addr types.Address, beneficiary types.Address) { - return } func (m *mockHostF) GetTxContext() runtime.TxContext { return runtime.TxContext{} @@ -99,7 +96,6 @@ func (m *mockHostF) GetBlockHash(number int64) types.Hash { } func (m *mockHostF) EmitLog(addr types.Address, topics []types.Hash, data []byte) { - return } func (m *mockHostF) Callx(c *runtime.Contract, h runtime.Host) *runtime.ExecutionResult { @@ -142,6 +138,15 @@ func (m *mockHostF) GetRefund() uint64 { return m.refund } +func (m *mockHostF) AddSlotToAccessList(addr types.Address, slot types.Hash) {} +func (m *mockHostF) AddAddressToAccessList(addr types.Address) {} +func (m *mockHostF) ContainsAccessListAddress(addr types.Address) bool { return false } +func (m *mockHostF) ContainsAccessListSlot(addr types.Address, slot types.Hash) (bool, bool) { + return false, false +} +func (m *mockHostF) DeleteAccessListAddress(addr types.Address) {} +func (m *mockHostF) DeleteAccessListSlot(addr types.Address, slot types.Hash) {} + func FuzzTestEVM(f *testing.F) { seed := []byte{ PUSH1, 0x01, PUSH1, 0x02, ADD, diff --git a/state/runtime/evm/evm_test.go b/state/runtime/evm/evm_test.go index ae401d21fd..6b20b48e29 100644 --- a/state/runtime/evm/evm_test.go +++ b/state/runtime/evm/evm_test.go @@ -29,7 +29,8 @@ func newMockContract(value *big.Int, gas uint64, code []byte) *runtime.Contract type mockHost struct { mock.Mock - tracer runtime.VMTracer + tracer runtime.VMTracer + accessList *runtime.AccessList } func (m *mockHost) AccountExists(addr types.Address) bool { @@ -135,6 +136,30 @@ func (m *mockHost) GetRefund() uint64 { panic("Not implemented in tests") //nolint:gocritic } +func (m *mockHost) AddSlotToAccessList(addr types.Address, slot types.Hash) { + m.accessList.AddSlot(addr, slot) +} + +func (m *mockHost) AddAddressToAccessList(addr types.Address) { + m.accessList.AddAddress(addr) +} + +func (m *mockHost) ContainsAccessListAddress(addr types.Address) bool { + return m.accessList.ContainsAddress(addr) +} + +func (m *mockHost) ContainsAccessListSlot(addr types.Address, slot types.Hash) (bool, bool) { + return m.accessList.Contains(addr, slot) +} + +func (m *mockHost) DeleteAccessListAddress(addr types.Address) { + m.accessList.DeleteAddress(addr) +} + +func (m *mockHost) DeleteAccessListSlot(addr types.Address, slot types.Hash) { + m.accessList.DeleteSlot(addr, slot) +} + func TestRun(t *testing.T) { t.Parallel() diff --git a/state/runtime/evm/instructions.go b/state/runtime/evm/instructions.go index 73b64924d6..8fc6d3bb19 100644 --- a/state/runtime/evm/instructions.go +++ b/state/runtime/evm/instructions.go @@ -16,12 +16,31 @@ import ( type instruction func(c *state) +const ( + ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST + ColdStorageReadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST_EIP2929 + WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST +) + var ( zero = big.NewInt(0) one = big.NewInt(1) wordSize = big.NewInt(32) ) +func (c *state) calculateGasForEIP2929(addr types.Address) uint64 { + var gas uint64 + if c.host.ContainsAccessListAddress(addr) { + gas = WarmStorageReadCostEIP2929 + } else { + gas = ColdAccountAccessCostEIP2929 + + c.host.AddAddressToAccessList(addr) + } + + return gas +} + func opAdd(c *state) { a := c.pop() b := c.top() @@ -465,7 +484,16 @@ func opSload(c *state) { loc := c.top() var gas uint64 - if c.config.Istanbul { + + if c.config.Berlin { + if _, slotPresent := c.host.ContainsAccessListSlot(c.msg.Address, bigToHash(loc)); !slotPresent { + gas = ColdStorageReadCostEIP2929 + + c.host.AddSlotToAccessList(c.msg.Address, bigToHash(loc)) + } else { + gas = WarmStorageReadCostEIP2929 + } + } else if c.config.Istanbul { // eip-1884 gas = 800 } else if c.config.EIP150 { @@ -503,35 +531,53 @@ func opSStore(c *state) { status := c.host.SetStorage(c.msg.Address, key, val, c.config) cost := uint64(0) + if c.config.Berlin { + if _, slotPresent := c.host.ContainsAccessListSlot(c.msg.Address, key); !slotPresent { + cost = ColdStorageReadCostEIP2929 + + c.host.AddSlotToAccessList(c.msg.Address, key) + } + } + switch status { case runtime.StorageUnchanged: - if c.config.Istanbul { + if c.config.Berlin { + cost += WarmStorageReadCostEIP2929 + } else if c.config.Istanbul { // eip-2200 - cost = 800 + cost += 800 } else if legacyGasMetering { - cost = 5000 + cost += 5000 } else { - cost = 200 + cost += 200 } case runtime.StorageModified: - cost = 5000 + cost += 5000 + if c.config.Berlin { + cost -= ColdStorageReadCostEIP2929 + } case runtime.StorageModifiedAgain: - if c.config.Istanbul { + if c.config.Berlin { + cost += WarmStorageReadCostEIP2929 + } else if c.config.Istanbul { // eip-2200 - cost = 800 + cost += 800 } else if legacyGasMetering { - cost = 5000 + cost += 5000 } else { - cost = 200 + cost += 200 } case runtime.StorageAdded: - cost = 20000 + cost += 20000 case runtime.StorageDeleted: - cost = 5000 + cost += 5000 + if c.config.Berlin { + cost -= ColdStorageReadCostEIP2929 + } } if !c.consumeGas(cost) { @@ -575,7 +621,9 @@ func opBalance(c *state) { addr, _ := c.popAddr() var gas uint64 - if c.config.Istanbul { + if c.config.Berlin { + gas = c.calculateGasForEIP2929(addr) + } else if c.config.Istanbul { // eip-1884 gas = 700 } else if c.config.EIP150 { @@ -658,7 +706,9 @@ func opExtCodeSize(c *state) { addr, _ := c.popAddr() var gas uint64 - if c.config.EIP150 { + if c.config.Berlin { + gas = c.calculateGasForEIP2929(addr) + } else if c.config.EIP150 { gas = 700 } else { gas = 20 @@ -693,7 +743,9 @@ func opExtCodeHash(c *state) { address, _ := c.popAddr() var gas uint64 - if c.config.Istanbul { + if c.config.Berlin { + gas = c.calculateGasForEIP2929(address) + } else if c.config.Istanbul { gas = 700 } else { gas = 400 @@ -767,7 +819,9 @@ func opExtCodeCopy(c *state) { } var gas uint64 - if c.config.EIP150 { + if c.config.Berlin { + gas = c.calculateGasForEIP2929(address) + } else if c.config.EIP150 { gas = 700 } else { gas = 20 @@ -950,6 +1004,13 @@ func opSelfDestruct(c *state) { } } + // EIP 2929 gas + if c.config.Berlin && !c.host.ContainsAccessListAddress(address) { + gas += ColdAccountAccessCostEIP2929 + + c.host.AddAddressToAccessList(address) + } + if !c.consumeGas(gas) { return } @@ -1243,7 +1304,9 @@ func (c *state) buildCallContract(op OpCode) (*runtime.Contract, uint64, uint64, } var gasCost uint64 - if c.config.EIP150 { + if c.config.Berlin { + gasCost = c.calculateGasForEIP2929(addr) + } else if c.config.EIP150 { gasCost = 700 } else { gasCost = 40 @@ -1355,9 +1418,6 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { // Calculate and consume gas cost - // var overflow bool - var gasCost uint64 - // Both CREATE and CREATE2 use memory var input []byte @@ -1368,17 +1428,6 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { return nil, nil } - // Consume memory resize gas (TODO, change with get2) (to be fixed in EVM-528) //nolint:godox - if !c.consumeGas(gasCost) { - return nil, nil - } - - if hasTransfer { - if c.host.GetBalance(c.msg.Address).Cmp(value) < 0 { - return nil, types.ErrInsufficientFunds - } - } - if op == CREATE2 { // Consume sha3 gas cost size := length.Uint64() @@ -1387,6 +1436,12 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { } } + if hasTransfer { + if c.host.GetBalance(c.msg.Address).Cmp(value) < 0 { + return nil, types.ErrInsufficientFunds + } + } + // Calculate and consume gas for the call gas := c.gas @@ -1407,7 +1462,15 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { address = crypto.CreateAddress2(c.msg.Address, bigToHash(salt), input) } - contract := runtime.NewContractCreation(c.msg.Depth+1, c.msg.Origin, c.msg.Address, address, value, gas, input) + contract := runtime.NewContractCreation( + c.msg.Depth+1, + c.msg.Origin, + c.msg.Address, + address, + value, + gas, + input, + ) return contract, nil } diff --git a/state/runtime/evm/instructions_test.go b/state/runtime/evm/instructions_test.go index 4e69221000..82592593d7 100644 --- a/state/runtime/evm/instructions_test.go +++ b/state/runtime/evm/instructions_test.go @@ -1,7 +1,6 @@ package evm import ( - "math" "math/big" "testing" @@ -1424,6 +1423,8 @@ type mockHostForInstructions struct { nonce uint64 code []byte callxResult *runtime.ExecutionResult + addresses map[types.Address]int + storages []map[types.Hash]types.Hash } func (m *mockHostForInstructions) GetNonce(types.Address) uint64 { @@ -1438,10 +1439,223 @@ func (m *mockHostForInstructions) GetCode(addr types.Address) []byte { return m.code } +func (m *mockHostForInstructions) GetStorage(addr types.Address, key types.Hash) types.Hash { + idx, ok := m.addresses[addr] + if !ok { + return types.ZeroHash + } + + res, ok := m.storages[idx][key] + if !ok { + return types.ZeroHash + } + + return res +} + var ( addr1 = types.StringToAddress("1") ) +func Test_opSload(t *testing.T) { + t.Parallel() + + type state struct { + gas uint64 + sp int + stack []*big.Int + memory []byte + accessList *runtime.AccessList + stop bool + err error + } + + address1 := types.StringToAddress("address1") + key1 := types.StringToHash("1") + val1 := types.StringToHash("2") + tests := []struct { + name string + op OpCode + contract *runtime.Contract + config *chain.ForksInTime + initState *state + resultState *state + mockHost *mockHostForInstructions + }{ + { + name: "charge ColdStorageReadCostEIP2929 if the (address, storage_key) pair is not accessed_storage_keys", + op: SLOAD, + contract: &runtime.Contract{ + Address: address1, + }, + config: &chain.ForksInTime{ + Berlin: true, + }, + initState: &state{ + gas: 10000, + sp: 1, + stack: []*big.Int{ + new(big.Int).SetBytes(key1.Bytes()), + }, + memory: []byte{0x01}, + accessList: runtime.NewAccessList(), + }, + resultState: &state{ + gas: 7900, + sp: 1, + stack: []*big.Int{ + new(big.Int).SetBytes(val1.Bytes()), + }, + memory: []byte{0x01}, + stop: false, + err: nil, + accessList: &runtime.AccessList{ + address1: { + key1: struct{}{}, + }, + }, + }, + mockHost: &mockHostForInstructions{ + addresses: map[types.Address]int{ + address1: 0, + }, + storages: []map[types.Hash]types.Hash{ + { + key1: val1, + }, + }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, + }, + }, + { + name: "charge WarmStorageReadCostEIP2929 if the (address, storage_key) pair is in access list", + op: SLOAD, + contract: &runtime.Contract{ + Address: address1, + }, + config: &chain.ForksInTime{ + Berlin: true, + }, + initState: &state{ + gas: 10000, + sp: 1, + stack: []*big.Int{ + new(big.Int).SetBytes(key1.Bytes()), + }, + memory: []byte{0x01}, + accessList: &runtime.AccessList{ + address1: { + key1: struct{}{}, + }, + }, + }, + resultState: &state{ + gas: 9900, + sp: 1, + stack: []*big.Int{ + new(big.Int).SetBytes(val1.Bytes()), + }, + memory: []byte{0x01}, + stop: false, + err: nil, + accessList: &runtime.AccessList{ + address1: { + key1: struct{}{}, + }, + }, + }, + mockHost: &mockHostForInstructions{ + addresses: map[types.Address]int{ + address1: 0, + }, + storages: []map[types.Hash]types.Hash{ + { + key1: val1, + }, + }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, + }, + }, + { + name: "charge Gas 800 when EIP2929 is not enabled and Istanbul is enabled", + op: SLOAD, + contract: &runtime.Contract{ + Address: address1, + }, + config: &chain.ForksInTime{ + Berlin: false, + Istanbul: true, + }, + initState: &state{ + gas: 10000, + sp: 1, + stack: []*big.Int{ + new(big.Int).SetBytes(key1.Bytes()), + }, + memory: []byte{0x01}, + accessList: nil, + }, + resultState: &state{ + gas: 9200, + sp: 1, + stack: []*big.Int{ + new(big.Int).SetBytes(val1.Bytes()), + }, + memory: []byte{0x01}, + stop: false, + err: nil, + accessList: nil, + }, + mockHost: &mockHostForInstructions{ + addresses: map[types.Address]int{ + address1: 0, + }, + storages: []map[types.Hash]types.Hash{ + { + key1: val1, + }, + }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + s, closeFn := getState(tt.config) + defer closeFn() + + s.msg = tt.contract + s.gas = tt.initState.gas + s.sp = tt.initState.sp + s.stack = tt.initState.stack + s.memory = tt.initState.memory + s.config = tt.config + tt.mockHost.accessList = tt.initState.accessList + s.host = tt.mockHost + + opSload(s) + + assert.Equal(t, tt.resultState.gas, s.gas, "gas in state after execution is not correct") + assert.Equal(t, tt.resultState.sp, s.sp, "sp in state after execution is not correct") + assert.Equal(t, tt.resultState.stack, s.stack, "stack in state after execution is not correct") + assert.Equal(t, tt.resultState.memory, s.memory, "memory in state after execution is not correct") + assert.Equal(t, tt.resultState.accessList, tt.mockHost.accessList, "accesslist in state after execution is not correct") + assert.Equal(t, tt.resultState.stop, s.stop, "stop in state after execution is not correct") + assert.Equal(t, tt.resultState.err, s.err, "err in state after execution is not correct") + }) + } +} + func TestCreate(t *testing.T) { type state struct { gas uint64 @@ -1503,6 +1717,9 @@ func TestCreate(t *testing.T) { GasLeft: 500, GasUsed: 500, }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, }, }, { @@ -1541,7 +1758,11 @@ func TestCreate(t *testing.T) { stop: true, err: errWriteProtection, }, - mockHost: &mockHostForInstructions{}, + mockHost: &mockHostForInstructions{ + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, + }, }, { name: "should throw errOpCodeNotFound when op is CREATE2 and config.Constantinople is disabled", @@ -1579,7 +1800,11 @@ func TestCreate(t *testing.T) { stop: true, err: errOpCodeNotFound, }, - mockHost: &mockHostForInstructions{}, + mockHost: &mockHostForInstructions{ + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, + }, }, { name: "should set zero address if op is CREATE and contract call throws ErrCodeStoreOutOfGas", @@ -1627,6 +1852,9 @@ func TestCreate(t *testing.T) { GasLeft: 1000, Err: runtime.ErrCodeStoreOutOfGas, }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, }, }, { @@ -1675,6 +1903,9 @@ func TestCreate(t *testing.T) { GasLeft: 1000, Err: errRevert, }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, }, }, { @@ -1726,6 +1957,9 @@ func TestCreate(t *testing.T) { GasLeft: 0, Err: runtime.ErrCodeStoreOutOfGas, }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, }, }, } @@ -2080,7 +2314,7 @@ func Test_opCall(t *testing.T) { }, config: allEnabledForks, initState: &state{ - gas: 1000, + gas: 2600, sp: 6, stack: []*big.Int{ big.NewInt(0x00), // outSize @@ -2096,87 +2330,92 @@ func Test_opCall(t *testing.T) { memory: []byte{0x01}, stop: false, err: nil, - gas: 300, - }, - mockHost: &mockHostForInstructions{ - callxResult: &runtime.ExecutionResult{ - ReturnValue: []byte{0x03}, - }, - }, - }, - { - name: "call cost overflow (EIP150 fork disabled)", - op: CALLCODE, - contract: &runtime.Contract{ - Static: false, - }, - config: chain.AllForksEnabled.RemoveFork(chain.EIP150).At(0), - initState: &state{ - gas: 6640, - sp: 7, - stack: []*big.Int{ - big.NewInt(0x00), // outSize - big.NewInt(0x00), // outOffset - big.NewInt(0x00), // inSize - big.NewInt(0x00), // inOffset - big.NewInt(0x01), // value - big.NewInt(0x03), // address - big.NewInt(0).SetUint64(math.MaxUint64), // initialGas - }, - memory: []byte{0x01}, - }, - resultState: &state{ - memory: []byte{0x01}, - stop: true, - err: errGasUintOverflow, - gas: 6640, }, mockHost: &mockHostForInstructions{ callxResult: &runtime.ExecutionResult{ ReturnValue: []byte{0x03}, }, - }, - }, - { - name: "available gas underflow", - op: CALLCODE, - contract: &runtime.Contract{ - Static: false, - }, - config: allEnabledForks, - initState: &state{ - gas: 6640, - sp: 7, - stack: []*big.Int{ - big.NewInt(0x00), // outSize - big.NewInt(0x00), // outOffset - big.NewInt(0x00), // inSize - big.NewInt(0x00), // inOffset - big.NewInt(0x01), // value - big.NewInt(0x03), // address - big.NewInt(0).SetUint64(math.MaxUint64), // initialGas - }, - memory: []byte{0x01}, - }, - resultState: &state{ - memory: []byte{0x01}, - stop: true, - err: errOutOfGas, - gas: 6640, - }, - mockHost: &mockHostForInstructions{ - callxResult: &runtime.ExecutionResult{ - ReturnValue: []byte{0x03}, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), }, }, }, + // { + // name: "call cost overflow (EIP150 fork disabled)", + // op: CALLCODE, + // contract: &runtime.Contract{ + // Static: false, + // }, + // config: chain.AllForksEnabled.RemoveFork(chain.EIP150).At(0), + // initState: &state{ + // gas: 6640, + // sp: 7, + // stack: []*big.Int{ + // big.NewInt(0x00), // outSize + // big.NewInt(0x00), // outOffset + // big.NewInt(0x00), // inSize + // big.NewInt(0x00), // inOffset + // big.NewInt(0x01), // value + // big.NewInt(0x03), // address + // big.NewInt(0).SetUint64(math.MaxUint64), // initialGas + // }, + // memory: []byte{0x01}, + // accessList: runtime.NewAccessList(), + // }, + // resultState: &state{ + // memory: []byte{0x01}, + // stop: true, + // err: errGasUintOverflow, + // gas: 6640, + // }, + // mockHost: &mockHostForInstructions{ + // callxResult: &runtime.ExecutionResult{ + // ReturnValue: []byte{0x03}, + // }, + // }, + // }, + // { + // name: "available gas underflow", + // op: CALLCODE, + // contract: &runtime.Contract{ + // Static: false, + // }, + // config: allEnabledForks, + // initState: &state{ + // gas: 6640, + // sp: 7, + // stack: []*big.Int{ + // big.NewInt(0x00), // outSize + // big.NewInt(0x00), // outOffset + // big.NewInt(0x00), // inSize + // big.NewInt(0x00), // inOffset + // big.NewInt(0x01), // value + // big.NewInt(0x03), // address + // big.NewInt(0).SetUint64(math.MaxUint64), // initialGas + // }, + // memory: []byte{0x01}, + // accessList: runtime.NewAccessList(), + // }, + // resultState: &state{ + // memory: []byte{0x01}, + // stop: true, + // err: errOutOfGas, + // gas: 6640, + // }, + // mockHost: &mockHostForInstructions{ + // callxResult: &runtime.ExecutionResult{ + // ReturnValue: []byte{0x03}, + // }, + // }, + // }, } for _, tt := range tests { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - state, closeFn := getState(&chain.ForksInTime{}) + + state, closeFn := getState(&test.config) defer closeFn() state.gas = test.initState.gas diff --git a/state/runtime/journal.go b/state/runtime/journal.go new file mode 100644 index 0000000000..86048eaed3 --- /dev/null +++ b/state/runtime/journal.go @@ -0,0 +1,57 @@ +package runtime + +import ( + "github.com/0xPolygon/polygon-edge/types" +) + +type JournalRevision struct { + ID int + Index int +} + +type JournalEntry interface { + Revert(host Host) +} + +type Journal struct { + entries []JournalEntry +} + +func (j *Journal) Append(entry JournalEntry) { + j.entries = append(j.entries, entry) +} + +func (j *Journal) Revert(host Host, snapshot int) { + for i := len(j.entries) - 1; i >= snapshot; i-- { + // Undo the changes made by the operation + j.entries[i].Revert(host) + } + + j.entries = j.entries[:snapshot] +} + +func (j *Journal) Len() int { + return len(j.entries) +} + +type ( + AccessListAddAccountChange struct { + Address types.Address + } + AccessListAddSlotChange struct { + Address types.Address + Slot types.Hash + } +) + +var _ JournalEntry = (*AccessListAddAccountChange)(nil) + +func (ch AccessListAddAccountChange) Revert(host Host) { + host.DeleteAccessListAddress(ch.Address) +} + +var _ JournalEntry = (*AccessListAddSlotChange)(nil) + +func (ch AccessListAddSlotChange) Revert(host Host) { + host.DeleteAccessListSlot(ch.Address, ch.Slot) +} diff --git a/state/runtime/precompiled/base.go b/state/runtime/precompiled/base.go index 06f1e76429..9997d4ebdc 100644 --- a/state/runtime/precompiled/base.go +++ b/state/runtime/precompiled/base.go @@ -39,7 +39,11 @@ func (e *ecrecover) run(input []byte, caller types.Address, _ runtime.Host) ([]b return nil, nil } - pubKey, err := crypto.Ecrecover(input[:32], append(input[64:128], v)) + sig := make([]byte, 65) + copy(sig, input[64:128]) + sig[64] = v + + pubKey, err := crypto.Ecrecover(input[:32], sig) if err != nil { return nil, nil } diff --git a/state/runtime/precompiled/base_test.go b/state/runtime/precompiled/base_test.go index c93982e0eb..dc2aeada73 100644 --- a/state/runtime/precompiled/base_test.go +++ b/state/runtime/precompiled/base_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/assert" + "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/helper/hex" "github.com/0xPolygon/polygon-edge/types" ) @@ -13,16 +14,22 @@ type precompiledTest struct { Name string Input string Expected string + Gas uint64 } -func testPrecompiled(t *testing.T, p contract, cases []precompiledTest) { +func testPrecompiled(t *testing.T, p contract, cases []precompiledTest, enabledForks ...*chain.ForksInTime) { t.Helper() for _, c := range cases { t.Run(c.Name, func(t *testing.T) { h, _ := hex.DecodeString(c.Input) - found, err := p.run(h, types.ZeroAddress, nil) + if c.Gas != 0 && len(enabledForks) > 0 { + gas := p.gas(h, enabledForks[0]) + assert.Equal(t, c.Gas, gas, "Incorrect gas estimation") + } + + found, err := p.run(h, types.ZeroAddress, nil) assert.NoError(t, err) assert.Equal(t, c.Expected, hex.EncodeToString(found)) }) diff --git a/state/runtime/precompiled/modexp.go b/state/runtime/precompiled/modexp.go index c4ef6a6873..c8d3f02edb 100644 --- a/state/runtime/precompiled/modexp.go +++ b/state/runtime/precompiled/modexp.go @@ -10,13 +10,16 @@ import ( "github.com/0xPolygon/polygon-edge/types" ) +const minGasPrice = 200 // minimum gas price for the modexp precompiled contract type modExp struct { p *Precompiled } var ( big1 = big.NewInt(1) + big3 = big.NewInt(3) big4 = big.NewInt(4) + big7 = big.NewInt(7) big8 = big.NewInt(8) big16 = big.NewInt(16) big32 = big.NewInt(32) @@ -110,6 +113,8 @@ func (m *modExp) gas(input []byte, config *chain.ForksInTime) uint64 { expHead.SetBytes(val) } + adjExpLen := adjustedExponentLength(expLen, expHead) + // a := mult_complexity(max(length_of_MODULUS, length_of_BASE) gasCost := new(big.Int) if modLen.Cmp(baseLen) >= 0 { @@ -118,10 +123,43 @@ func (m *modExp) gas(input []byte, config *chain.ForksInTime) uint64 { gasCost.Set(baseLen) } + //EIP-2565 gas cost calculation + if config.Berlin { + // EIP-2565 has three changes + // 1. Different multComplexity (inlined here) + // in EIP-2565 (https://eips.ethereum.org/EIPS/eip-2565): + // + // def mult_complexity(x): + // ceiling(x/8)^2 + // + // where is x is max(length_of_MODULUS, length_of_BASE) + gasCost.Add(gasCost, big7) + gasCost.Div(gasCost, big8) + gasCost.Mul(gasCost, gasCost) + + // a = a * max(ADJUSTED_EXPONENT_LENGTH, 1) + if adjExpLen.Cmp(big1) >= 0 { + gasCost.Mul(gasCost, adjExpLen) + } else { + gasCost.Mul(gasCost, big1) + } + // 2. Different divisor (`GQUADDIVISOR`) (3) + gasCost.Div(gasCost, big3) + // cap to the max uint64 + if !gasCost.IsUint64() { + return math.MaxUint64 + } + // 3. Minimum price of 200 gas + if gasCost.Uint64() < minGasPrice { + return minGasPrice + } + + return gasCost.Uint64() + } + gasCost = multComplexity(gasCost) // a = a * max(ADJUSTED_EXPONENT_LENGTH, 1) - adjExpLen := adjustedExponentLength(expLen, expHead) if adjExpLen.Cmp(big1) >= 0 { gasCost.Mul(gasCost, adjExpLen) } else { diff --git a/state/runtime/precompiled/modexp_test.go b/state/runtime/precompiled/modexp_test.go index 0199e9208f..e88f1e41ad 100644 --- a/state/runtime/precompiled/modexp_test.go +++ b/state/runtime/precompiled/modexp_test.go @@ -2,6 +2,8 @@ package precompiled import ( "testing" + + "github.com/0xPolygon/polygon-edge/chain" ) var modExpTests = []precompiledTest{ @@ -86,7 +88,106 @@ var modExpTests = []precompiledTest{ }, } +var modExpTestsEIP2565 = []precompiledTest{ + { + Input: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002003fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + Expected: "0000000000000000000000000000000000000000000000000000000000000001", + Name: "eip_example1", + Gas: 1360, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + Expected: "0000000000000000000000000000000000000000000000000000000000000000", + Name: "eip_example2", + Gas: 1360, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb502fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + Expected: "60008f1614cc01dcfb6bfb09c625cf90b47d4468db81b5f8b7a39d42f332eab9b2da8f2d95311648a8f243f4bb13cfb3d8f7f2a3c014122ebb3ed41b02783adc", + Name: "nagydani-1-square", + Gas: 200, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb503fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + Expected: "4834a46ba565db27903b1c720c9d593e84e4cbd6ad2e64b31885d944f68cd801f92225a8961c952ddf2797fa4701b330c85c4b363798100b921a1a22a46a7fec", + Name: "nagydani-1-qube", + Gas: 200, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5010001fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + Expected: "c36d804180c35d4426b57b50c5bfcca5c01856d104564cd513b461d3c8b8409128a5573e416d0ebe38f5f736766d9dc27143e4da981dfa4d67f7dc474cbee6d2", + Name: "nagydani-1-pow0x10001", + Gas: 341, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5102e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + Expected: "981dd99c3b113fae3e3eaa9435c0dc96779a23c12a53d1084b4f67b0b053a27560f627b873e3f16ad78f28c94f14b6392def26e4d8896c5e3c984e50fa0b3aa44f1da78b913187c6128baa9340b1e9c9a0fd02cb78885e72576da4a8f7e5a113e173a7a2889fde9d407bd9f06eb05bc8fc7b4229377a32941a02bf4edcc06d70", + Name: "nagydani-2-square", + Gas: 200, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5103e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + Expected: "d89ceb68c32da4f6364978d62aaa40d7b09b59ec61eb3c0159c87ec3a91037f7dc6967594e530a69d049b64adfa39c8fa208ea970cfe4b7bcd359d345744405afe1cbf761647e32b3184c7fbe87cee8c6c7ff3b378faba6c68b83b6889cb40f1603ee68c56b4c03d48c595c826c041112dc941878f8c5be828154afd4a16311f", + Name: "nagydani-2-qube", + Gas: 200, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51010001e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + Expected: "ad85e8ef13fd1dd46eae44af8b91ad1ccae5b7a1c92944f92a19f21b0b658139e0cabe9c1f679507c2de354bf2c91ebd965d1e633978a830d517d2f6f8dd5fd58065d58559de7e2334a878f8ec6992d9b9e77430d4764e863d77c0f87beede8f2f7f2ab2e7222f85cc9d98b8467f4bb72e87ef2882423ebdb6daf02dddac6db2", + Name: "nagydani-2-pow0x10001", + Gas: 1365, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb02d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + Expected: "affc7507ea6d84751ec6b3f0d7b99dbcc263f33330e450d1b3ff0bc3d0874320bf4edd57debd587306988157958cb3cfd369cc0c9c198706f635c9e0f15d047df5cb44d03e2727f26b083c4ad8485080e1293f171c1ed52aef5993a5815c35108e848c951cf1e334490b4a539a139e57b68f44fee583306f5b85ffa57206b3ee5660458858534e5386b9584af3c7f67806e84c189d695e5eb96e1272d06ec2df5dc5fabc6e94b793718c60c36be0a4d031fc84cd658aa72294b2e16fc240aef70cb9e591248e38bd49c5a554d1afa01f38dab72733092f7555334bbef6c8c430119840492380aa95fa025dcf699f0a39669d812b0c6946b6091e6e235337b6f8", + Name: "nagydani-3-square", + Gas: 341, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb03d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + Expected: "1b280ecd6a6bf906b806d527c2a831e23b238f89da48449003a88ac3ac7150d6a5e9e6b3be4054c7da11dd1e470ec29a606f5115801b5bf53bc1900271d7c3ff3cd5ed790d1c219a9800437a689f2388ba1a11d68f6a8e5b74e9a3b1fac6ee85fc6afbac599f93c391f5dc82a759e3c6c0ab45ce3f5d25d9b0c1bf94cf701ea6466fc9a478dacc5754e593172b5111eeba88557048bceae401337cd4c1182ad9f700852bc8c99933a193f0b94cf1aedbefc48be3bc93ef5cb276d7c2d5462ac8bb0c8fe8923a1db2afe1c6b90d59c534994a6a633f0ead1d638fdc293486bb634ff2c8ec9e7297c04241a61c37e3ae95b11d53343d4ba2b4cc33d2cfa7eb705e", + Name: "nagydani-3-qube", + Gas: 341, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb010001d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + Expected: "37843d7c67920b5f177372fa56e2a09117df585f81df8b300fba245b1175f488c99476019857198ed459ed8d9799c377330e49f4180c4bf8e8f66240c64f65ede93d601f957b95b83efdee1e1bfde74169ff77002eaf078c71815a9220c80b2e3b3ff22c2f358111d816ebf83c2999026b6de50bfc711ff68705d2f40b753424aefc9f70f08d908b5a20276ad613b4ab4309a3ea72f0c17ea9df6b3367d44fb3acab11c333909e02e81ea2ed404a712d3ea96bba87461720e2d98723e7acd0520ac1a5212dbedcd8dc0c1abf61d4719e319ff4758a774790b8d463cdfe131d1b2dcfee52d002694e98e720cb6ae7ccea353bc503269ba35f0f63bf8d7b672a76", + Name: "nagydani-3-pow0x10001", + Gas: 5461, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8102df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + Expected: "8a5aea5f50dcc03dc7a7a272b5aeebc040554dbc1ffe36753c4fc75f7ed5f6c2cc0de3a922bf96c78bf0643a73025ad21f45a4a5cadd717612c511ab2bff1190fe5f1ae05ba9f8fe3624de1de2a817da6072ddcdb933b50216811dbe6a9ca79d3a3c6b3a476b079fd0d05f04fb154e2dd3e5cb83b148a006f2bcbf0042efb2ae7b916ea81b27aac25c3bf9a8b6d35440062ad8eae34a83f3ffa2cc7b40346b62174a4422584f72f95316f6b2bee9ff232ba9739301c97c99a9ded26c45d72676eb856ad6ecc81d36a6de36d7f9dafafee11baa43a4b0d5e4ecffa7b9b7dcefd58c397dd373e6db4acd2b2c02717712e6289bed7c813b670c4a0c6735aa7f3b0f1ce556eae9fcc94b501b2c8781ba50a8c6220e8246371c3c7359fe4ef9da786ca7d98256754ca4e496be0a9174bedbecb384bdf470779186d6a833f068d2838a88d90ef3ad48ff963b67c39cc5a3ee123baf7bf3125f64e77af7f30e105d72c4b9b5b237ed251e4c122c6d8c1405e736299c3afd6db16a28c6a9cfa68241e53de4cd388271fe534a6a9b0dbea6171d170db1b89858468885d08fecbd54c8e471c3e25d48e97ba450b96d0d87e00ac732aaa0d3ce4309c1064bd8a4c0808a97e0143e43a24cfa847635125cd41c13e0574487963e9d725c01375db99c31da67b4cf65eff555f0c0ac416c727ff8d438ad7c42030551d68c2e7adda0abb1ca7c10", + Name: "nagydani-4-square", + Gas: 1365, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8103df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + Expected: "5a2664252aba2d6e19d9600da582cdd1f09d7a890ac48e6b8da15ae7c6ff1856fc67a841ac2314d283ffa3ca81a0ecf7c27d89ef91a5a893297928f5da0245c99645676b481b7e20a566ee6a4f2481942bee191deec5544600bb2441fd0fb19e2ee7d801ad8911c6b7750affec367a4b29a22942c0f5f4744a4e77a8b654da2a82571037099e9c6d930794efe5cdca73c7b6c0844e386bdca8ea01b3d7807146bb81365e2cdc6475f8c23e0ff84463126189dc9789f72bbce2e3d2d114d728a272f1345122de23df54c922ec7a16e5c2a8f84da8871482bd258c20a7c09bbcd64c7a96a51029bbfe848736a6ba7bf9d931a9b7de0bcaf3635034d4958b20ae9ab3a95a147b0421dd5f7ebff46c971010ebfc4adbbe0ad94d5498c853e7142c450d8c71de4b2f84edbf8acd2e16d00c8115b150b1c30e553dbb82635e781379fe2a56360420ff7e9f70cc64c00aba7e26ed13c7c19622865ae07248daced36416080f35f8cc157a857ed70ea4f347f17d1bee80fa038abd6e39b1ba06b97264388b21364f7c56e192d4b62d9b161405f32ab1e2594e86243e56fcf2cb30d21adef15b9940f91af681da24328c883d892670c6aa47940867a81830a82b82716895db810df1b834640abefb7db2092dd92912cb9a735175bc447be40a503cf22dfe565b4ed7a3293ca0dfd63a507430b323ee248ec82e843b673c97ad730728cebc", + Name: "nagydani-4-qube", + Gas: 1365, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81010001df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + Expected: "bed8b970c4a34849fc6926b08e40e20b21c15ed68d18f228904878d4370b56322d0da5789da0318768a374758e6375bfe4641fca5285ec7171828922160f48f5ca7efbfee4d5148612c38ad683ae4e3c3a053d2b7c098cf2b34f2cb19146eadd53c86b2d7ccf3d83b2c370bfb840913ee3879b1057a6b4e07e110b6bcd5e958bc71a14798c91d518cc70abee264b0d25a4110962a764b364ac0b0dd1ee8abc8426d775ec0f22b7e47b32576afaf1b5a48f64573ed1c5c29f50ab412188d9685307323d990802b81dacc06c6e05a1e901830ba9fcc67688dc29c5e27bde0a6e845ca925f5454b6fb3747edfaa2a5820838fb759eadf57f7cb5cec57fc213ddd8a4298fa079c3c0f472b07fb15aa6a7f0a3780bd296ff6a62e58ef443870b02260bd4fd2bbc98255674b8e1f1f9f8d33c7170b0ebbea4523b695911abbf26e41885344823bd0587115fdd83b721a4e8457a31c9a84b3d3520a07e0e35df7f48e5a9d534d0ec7feef1ff74de6a11e7f93eab95175b6ce22c68d78a642ad642837897ec11349205d8593ac19300207572c38d29ca5dfa03bc14cdbc32153c80e5cc3e739403d34c75915e49beb43094cc6dcafb3665b305ddec9286934ae66ec6b777ca528728c851318eb0f207b39f1caaf96db6eeead6b55ed08f451939314577d42bcc9f97c0b52d0234f88fd07e4c1d7780fdebc025cfffcb572cb27a8c33963", + Name: "nagydani-4-pow0x10001", + Gas: 21845, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf02e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + Expected: "d61fe4e3f32ac260915b5b03b78a86d11bfc41d973fce5b0cc59035cf8289a8a2e3878ea15fa46565b0d806e2f85b53873ea20ed653869b688adf83f3ef444535bf91598ff7e80f334fb782539b92f39f55310cc4b35349ab7b278346eda9bc37c0d8acd3557fae38197f412f8d9e57ce6a76b7205c23564cab06e5615be7c6f05c3d05ec690cba91da5e89d55b152ff8dd2157dc5458190025cf94b1ad98f7cbe64e9482faba95e6b33844afc640892872b44a9932096508f4a782a4805323808f23e54b6ff9b841dbfa87db3505ae4f687972c18ea0f0d0af89d36c1c2a5b14560c153c3fee406f5cf15cfd1c0bb45d767426d465f2f14c158495069d0c5955a00150707862ecaae30624ebacdd8ac33e4e6aab3ff90b6ba445a84689386b9e945d01823a65874444316e83767290fcff630d2477f49d5d8ffdd200e08ee1274270f86ed14c687895f6caf5ce528bd970c20d2408a9ba66216324c6a011ac4999098362dbd98a038129a2d40c8da6ab88318aa3046cb660327cc44236d9e5d2163bd0959062195c51ed93d0088b6f92051fc99050ece2538749165976233697ab4b610385366e5ce0b02ad6b61c168ecfbedcdf74278a38de340fd7a5fead8e588e294795f9b011e2e60377a89e25c90e145397cdeabc60fd32444a6b7642a611a83c464d8b8976666351b4865c37b02e6dc21dbcdf5f930341707b618cc0f03c3122646b3385c9df9f2ec730eec9d49e7dfc9153b6e6289da8c4f0ebea9ccc1b751948e3bb7171c9e4d57423b0eeeb79095c030cb52677b3f7e0b45c30f645391f3f9c957afa549c4e0b2465b03c67993cd200b1af01035962edbc4c9e89b31c82ac121987d6529dafdeef67a132dc04b6dc68e77f22862040b75e2ceb9ff16da0fca534e6db7bd12fa7b7f51b6c08c1e23dfcdb7acbd2da0b51c87ffbced065a612e9b1c8bba9b7e2d8d7a2f04fcc4aaf355b60d764879a76b5e16762d5f2f55d585d0c8e82df6940960cddfb72c91dfa71f6b4e1c6ca25dfc39a878e998a663c04fe29d5e83b9586d047b4d7ff70a9f0d44f127e7d741685ca75f11629128d916a0ffef4be586a30c4b70389cc746e84ebf177c01ee8a4511cfbb9d1ecf7f7b33c7dd8177896e10bbc82f838dcd6db7ac67de62bf46b6a640fb580c5d1d2708f3862e3d2b645d0d18e49ef088053e3a220adc0e033c2afcfe61c90e32151152eb3caaf746c5e377d541cafc6cbb0cc0fa48b5caf1728f2e1957f5addfc234f1a9d89e40d49356c9172d0561a695fce6dab1d412321bbf407f63766ffd7b6b3d79bcfa07991c5a9709849c1008689e3b47c50d613980bec239fb64185249d055b30375ccb4354d71fe4d05648fbf6c80634dfc3575f2f24abb714c1e4c95e8896763bf4316e954c7ad19e5780ab7a040ca6fb9271f90a8b22ae738daf6cb", + Name: "nagydani-5-square", + Gas: 5461, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf03e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + Expected: "5f9c70ec884926a89461056ad20ac4c30155e817f807e4d3f5bb743d789c83386762435c3627773fa77da5144451f2a8aad8adba88e0b669f5377c5e9bad70e45c86fe952b613f015a9953b8a5de5eaee4566acf98d41e327d93a35bd5cef4607d025e58951167957df4ff9b1627649d3943805472e5e293d3efb687cfd1e503faafeb2840a3e3b3f85d016051a58e1c9498aab72e63b748d834b31eb05d85dcde65e27834e266b85c75cc4ec0135135e0601cb93eeeb6e0010c8ceb65c4c319623c5e573a2c8c9fbbf7df68a930beb412d3f4dfd146175484f45d7afaa0d2e60684af9b34730f7c8438465ad3e1d0c3237336722f2aa51095bd5759f4b8ab4dda111b684aa3dac62a761722e7ae43495b7709933512c81c4e3c9133a51f7ce9f2b51fcec064f65779666960b4e45df3900f54311f5613e8012dd1b8efd359eda31a778264c72aa8bb419d862734d769076bce2810011989a45374e5c5d8729fec21427f0bf397eacbb4220f603cf463a4b0c94efd858ffd9768cd60d6ce68d755e0fbad007ce5c2223d70c7018345a102e4ab3c60a13a9e7794303156d4c2063e919f2153c13961fb324c80b240742f47773a7a8e25b3e3fb19b00ce839346c6eb3c732fbc6b888df0b1fe0a3d07b053a2e9402c267b2d62f794d8a2840526e3ade15ce2264496ccd7519571dfde47f7a4bb16292241c20b2be59f3f8fb4f6383f232d838c5a22d8c95b6834d9d2ca493f5a505ebe8899503b0e8f9b19e6e2dd81c1628b80016d02097e0134de51054c4e7674824d4d758760fc52377d2cad145e259aa2ffaf54139e1a66b1e0c1c191e32ac59474c6b526f5b3ba07d3e5ec286eddf531fcd5292869be58c9f22ef91026159f7cf9d05ef66b4299f4da48cc1635bf2243051d342d378a22c83390553e873713c0454ce5f3234397111ac3fe3207b86f0ed9fc025c81903e1748103692074f83824fda6341be4f95ff00b0a9a208c267e12fa01825054cc0513629bf3dbb56dc5b90d4316f87654a8be18227978ea0a8a522760cad620d0d14fd38920fb7321314062914275a5f99f677145a6979b156bd82ecd36f23f8e1273cc2759ecc0b2c69d94dad5211d1bed939dd87ed9e07b91d49713a6e16ade0a98aea789f04994e318e4ff2c8a188cd8d43aeb52c6daa3bc29b4af50ea82a247c5cd67b573b34cbadcc0a376d3bbd530d50367b42705d870f2e27a8197ef46070528bfe408360faa2ebb8bf76e9f388572842bcb119f4d84ee34ae31f5cc594f23705a49197b181fb78ed1ec99499c690f843a4d0cf2e226d118e9372271054fbabdcc5c92ae9fefaef0589cd0e722eaf30c1703ec4289c7fd81beaa8a455ccee5298e31e2080c10c366a6fcf56f7d13582ad0bcad037c612b710fc595b70fbefaaca23623b60c6c39b11beb8e5843b6b3dac60f", + Name: "nagydani-5-qube", + Gas: 5461, + }, { + Input: "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf010001e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + Expected: "5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500", + Name: "nagydani-5-pow0x10001", + Gas: 87381, + }, +} + func TestModExp(t *testing.T) { p := &Precompiled{} - testPrecompiled(t, &modExp{p}, modExpTests) + enabledForks := chain.AllForksEnabled.At(0) + enabledForks.Berlin = false + + testPrecompiled(t, &modExp{p}, modExpTests, &enabledForks) +} + +func TestModExpWithEIP2565(t *testing.T) { + p := &Precompiled{} + enabledForks := chain.AllForksEnabled.At(0) + + testPrecompiled(t, &modExp{p}, modExpTestsEIP2565, &enabledForks) } diff --git a/state/runtime/precompiled/native_transfer_test.go b/state/runtime/precompiled/native_transfer_test.go index a6309f6d42..4bdf95b4fa 100644 --- a/state/runtime/precompiled/native_transfer_test.go +++ b/state/runtime/precompiled/native_transfer_test.go @@ -171,6 +171,34 @@ func (d dummyHost) GetNonce(addr types.Address) uint64 { return 0 } +func (d *dummyHost) AddSlotToAccessList(addr types.Address, slot types.Hash) { + d.t.Fatalf("AddSlotToAccessList is not implemented") +} + +func (d *dummyHost) AddAddressToAccessList(addr types.Address) { + d.t.Fatalf("AddAddressToAccessList is not implemented") +} + +func (d *dummyHost) ContainsAccessListAddress(addr types.Address) bool { + d.t.Fatalf("ContainsAccessListAddress is not implemented") + + return false +} + +func (d *dummyHost) ContainsAccessListSlot(addr types.Address, slot types.Hash) (bool, bool) { + d.t.Fatalf("ContainsAccessListSlot is not implemented") + + return false, false +} + +func (d *dummyHost) DeleteAccessListAddress(addr types.Address) { + d.t.Fatalf("DeleteAccessListAddress is not implemented") +} + +func (d *dummyHost) DeleteAccessListSlot(addr types.Address, slot types.Hash) { + d.t.Fatalf("DeleteAccessListSlot is not implemented") +} + func (d dummyHost) Transfer(from types.Address, to types.Address, amount *big.Int) error { if d.balances == nil { d.balances = map[types.Address]*big.Int{} diff --git a/state/runtime/precompiled/precompiled.go b/state/runtime/precompiled/precompiled.go index 8b33d5eb80..f5f8347a27 100644 --- a/state/runtime/precompiled/precompiled.go +++ b/state/runtime/precompiled/precompiled.go @@ -45,6 +45,7 @@ type contract interface { type Precompiled struct { buf []byte contracts map[types.Address]contract + Addrs []types.Address } // NewPrecompiled creates a new runtime for the precompiled contracts @@ -80,12 +81,14 @@ func (p *Precompiled) setupContracts() { p.register(contracts.BLSAggSigsVerificationPrecompile.String(), &blsAggSignsVerification{}) } -func (p *Precompiled) register(addrStr string, b contract) { +func (p *Precompiled) register(precompileAddrRaw string, b contract) { if len(p.contracts) == 0 { p.contracts = map[types.Address]contract{} } - p.contracts[types.StringToAddress(addrStr)] = b + precompileAddr := types.StringToAddress(precompileAddrRaw) + p.contracts[precompileAddr] = b + p.Addrs = append(p.Addrs, precompileAddr) } var ( diff --git a/state/runtime/runtime.go b/state/runtime/runtime.go index 248e9f3fd1..c180c7c777 100644 --- a/state/runtime/runtime.go +++ b/state/runtime/runtime.go @@ -80,6 +80,12 @@ type Host interface { Transfer(from types.Address, to types.Address, amount *big.Int) error GetTracer() VMTracer GetRefund() uint64 + AddSlotToAccessList(addr types.Address, slot types.Hash) + AddAddressToAccessList(addr types.Address) + ContainsAccessListAddress(addr types.Address) bool + ContainsAccessListSlot(addr types.Address, slot types.Hash) (bool, bool) + DeleteAccessListAddress(addr types.Address) + DeleteAccessListSlot(addr types.Address, slot types.Hash) } type VMTracer interface { @@ -119,11 +125,11 @@ func (r *ExecutionResult) Succeeded() bool { return r.Err == nil } func (r *ExecutionResult) Failed() bool { return r.Err != nil } func (r *ExecutionResult) Reverted() bool { return errors.Is(r.Err, ErrExecutionReverted) } -func (r *ExecutionResult) UpdateGasUsed(gasLimit uint64, refund uint64) { +func (r *ExecutionResult) UpdateGasUsed(gasLimit uint64, refund, refundQuotient uint64) { r.GasUsed = gasLimit - r.GasLeft // Refund can go up to half the gas used - if maxRefund := r.GasUsed / 2; refund > maxRefund { + if maxRefund := r.GasUsed / refundQuotient; refund > maxRefund { refund = maxRefund } @@ -143,6 +149,7 @@ var ( ErrUnauthorizedCaller = errors.New("unauthorized caller") ErrInvalidInputData = errors.New("invalid input data") ErrNotAuth = errors.New("not in allow list") + ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ) // StackUnderflowError wraps an evm error when the items on the stack less diff --git a/state/transition_test.go b/state/transition_test.go index d8e507c8be..1779d38c72 100644 --- a/state/transition_test.go +++ b/state/transition_test.go @@ -16,9 +16,10 @@ func newTestTransition(preState map[types.Address]*PreState) *Transition { } return &Transition{ - logger: hclog.NewNullLogger(), - state: newTestTxn(preState), - ctx: runtime.TxContext{BaseFee: big.NewInt(0)}, + logger: hclog.NewNullLogger(), + state: newTestTxn(preState), + ctx: runtime.TxContext{BaseFee: big.NewInt(0)}, + journal: &runtime.Journal{}, } } @@ -47,7 +48,7 @@ func TestSubGasLimitPrice(t *testing.T) { expectedErr: nil, }, { - name: "should fail by ErrNotEnoughFunds", + name: "should fail by ErrInsufficientFunds", preState: map[types.Address]*PreState{ addr1: { Nonce: 0, @@ -58,8 +59,8 @@ func TestSubGasLimitPrice(t *testing.T) { from: addr1, gas: 10, gasPrice: 10, - // should return ErrNotEnoughFundsForGas when state.SubBalance returns ErrNotEnoughFunds - expectedErr: ErrNotEnoughFundsForGas, + // should return ErrInsufficientFunds when state.SubBalance returns ErrNotEnoughFunds + expectedErr: ErrInsufficientFunds, }, } @@ -69,20 +70,25 @@ func TestSubGasLimitPrice(t *testing.T) { t.Parallel() transition := newTestTransition(tt.preState) - msg := &types.Transaction{ + msg := types.NewTx(&types.MixedTxn{ From: tt.from, Gas: tt.gas, GasPrice: big.NewInt(tt.gasPrice), - } + }) err := transition.subGasLimitPrice(msg) - assert.Equal(t, tt.expectedErr, err) + if tt.expectedErr != nil { + assert.ErrorContains(t, err, tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + } + if err == nil { // should reduce cost for gas from balance - reducedAmount := new(big.Int).Mul(msg.GasPrice, big.NewInt(int64(msg.Gas))) - newBalance := transition.GetBalance(msg.From) - diff := new(big.Int).Sub(big.NewInt(int64(tt.preState[msg.From].Balance)), newBalance) + reducedAmount := new(big.Int).Mul(msg.GasPrice(), big.NewInt(int64(msg.Gas()))) + newBalance := transition.GetBalance(msg.From()) + diff := new(big.Int).Sub(big.NewInt(int64(tt.preState[msg.From()].Balance)), newBalance) assert.Zero(t, diff.Cmp(reducedAmount)) } }) diff --git a/state/txn.go b/state/txn.go index 20efe11a65..0eeb618ff7 100644 --- a/state/txn.go +++ b/state/txn.go @@ -16,6 +16,14 @@ import ( var emptyStateHash = types.StringToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") +const ( + BerlinClearingRefund = uint64(15000) + LondonClearingRefund = uint64(4800) + + LegacyRefundQuotient = uint64(2) + LondonRefundQuotient = uint64(5) +) + type readSnapshot interface { GetStorage(addr types.Address, root types.Hash, key types.Hash) types.Hash GetAccount(addr types.Address) (*Account, error) @@ -247,13 +255,18 @@ func (txn *Txn) SetStorage( return runtime.StorageModified } + clearingRefund := BerlinClearingRefund + if config.London { + clearingRefund = LondonClearingRefund + } + if original == current { if original == types.ZeroHash { // create slot (2.1.1) return runtime.StorageAdded } if value == types.ZeroHash { // delete slot (2.1.2b) - txn.AddRefund(15000) + txn.AddRefund(clearingRefund) return runtime.StorageDeleted } @@ -263,22 +276,28 @@ func (txn *Txn) SetStorage( if original != types.ZeroHash { // Storage slot was populated before this transaction started if current == types.ZeroHash { // recreate slot (2.2.1.1) - txn.SubRefund(15000) + txn.SubRefund(clearingRefund) } else if value == types.ZeroHash { // delete slot (2.2.1.2) - txn.AddRefund(15000) + txn.AddRefund(clearingRefund) } } if original == value { if original == types.ZeroHash { // reset to original nonexistent slot (2.2.2.1) // Storage was used as memory (allocation and deallocation occurred within the same contract) - if config.Istanbul { + if config.Berlin { + // Refund: SstoreSetGasEIP2200 - WarmStorageReadCostEIP2929 + txn.AddRefund(19900) + } else if config.Istanbul { txn.AddRefund(19200) } else { txn.AddRefund(19800) } } else { // reset to original existing slot (2.2.2.2) - if config.Istanbul { + if config.Berlin { + // Refund: SstoreResetGasEIP2200 - ColdStorageReadCostEIP2929 - WarmStorageReadCostEIP2929 + txn.AddRefund(2800) + } else if config.Istanbul { txn.AddRefund(4200) } else { txn.AddRefund(4800) diff --git a/tests/state_test.go b/tests/state_test.go index 96dd2f33d8..d35912a349 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" "testing" + "time" "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/crypto" @@ -66,7 +67,7 @@ func RunSpecificTest(t *testing.T, file string, c testCase, fc *forkConfig, inde // Try to recover tx with current signer if len(p.TxBytes) != 0 { - var ttx types.Transaction + ttx := &types.Transaction{} err := ttx.UnmarshalRLP(p.TxBytes) if err != nil { return err @@ -74,7 +75,7 @@ func RunSpecificTest(t *testing.T, file string, c testCase, fc *forkConfig, inde signer := crypto.NewSigner(currentForks, 1) - if _, err := signer.Sender(&ttx); err != nil { + if _, err := signer.Sender(ttx); err != nil { return err } } @@ -163,34 +164,31 @@ func TestState(t *testing.T) { skip := []string{ "RevertPrecompiledTouch", + "RevertPrecompiledTouch_storage", + "loopMul", + "CALLBlake2f_MaxRounds", } - // There are two folders in spec tests, one for the current tests for the Istanbul fork - // and one for the legacy tests for the other forks - folders, err := listFolders(stateTests) + folders, err := listFolders([]string{stateTests}) if err != nil { t.Fatal(err) } for _, folder := range folders { - files, err := listFiles(folder) + files, err := listFiles(folder, ".json") if err != nil { t.Fatal(err) } for _, file := range files { - if !strings.HasSuffix(file, ".json") { - continue - } - if contains(long, file) && testing.Short() { - t.Skipf("Long tests are skipped in short mode") + t.Logf("Long test '%s' is skipped in short mode\n", file) continue } if contains(skip, file) { - t.Skip() + t.Logf("Test '%s' is skipped\n", file) continue } @@ -220,7 +218,11 @@ func TestState(t *testing.T) { fc := &forkConfig{name: fork, forks: forks} for idx, postStateEntry := range postState { + start := time.Now() err := RunSpecificTest(t, file, tc, fc, idx, postStateEntry) + + t.Logf("'%s' executed. Fork: %s. Case: %d, Duration=%v\n", file, fork, idx, time.Since(start)) + require.NoError(t, tc.checkError(fork, idx, err)) } } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index b0bf7d3f72..41f1d1cc30 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "io/fs" "math/big" "os" "path/filepath" @@ -202,10 +203,7 @@ func buildState(allocs map[types.Address]*chain.GenesisAccount) (state.State, st txn.CreateAccount(addr) txn.SetNonce(addr, alloc.Nonce) txn.SetBalance(addr, alloc.Balance) - - if len(alloc.Code) != 0 { - txn.SetCode(addr, alloc.Code) - } + txn.SetCode(addr, alloc.Code) for k, v := range alloc.Storage { txn.SetState(addr, k, v) @@ -266,15 +264,16 @@ type postState []postEntry // //nolint:godox type stTransaction struct { - Data []string `json:"data"` - Value []string `json:"value"` - Nonce uint64 `json:"nonce"` - To *types.Address `json:"to"` - GasLimit []uint64 `json:"gasLimit"` - GasPrice *big.Int `json:"gasPrice"` - MaxFeePerGas *big.Int `json:"maxFeePerGas"` - MaxPriorityFeePerGas *big.Int `json:"maxPriorityFeePerGas"` - From types.Address // derived field + Data []string `json:"data"` + Value []string `json:"value"` + Nonce uint64 `json:"nonce"` + To *types.Address `json:"to"` + GasLimit []uint64 `json:"gasLimit"` + GasPrice *big.Int `json:"gasPrice"` + MaxFeePerGas *big.Int `json:"maxFeePerGas"` + MaxPriorityFeePerGas *big.Int `json:"maxPriorityFeePerGas"` + From types.Address // derived field + AccessLists []*types.TxAccessList `json:"accessLists,omitempty"` } func (t *stTransaction) At(i indexes, baseFee *big.Int) (*types.Transaction, error) { @@ -290,8 +289,14 @@ func (t *stTransaction) At(i indexes, baseFee *big.Int) (*types.Transaction, err return nil, fmt.Errorf("value index %d out of bounds (%d)", i.Value, len(t.Value)) } + var accessList types.TxAccessList + if t.AccessLists != nil && t.AccessLists[i.Data] != nil { + accessList = *t.AccessLists[i.Data] + } + gasPrice := t.GasPrice + isDynamiFeeTx := false // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { if t.MaxFeePerGas == nil { @@ -300,10 +305,14 @@ func (t *stTransaction) At(i indexes, baseFee *big.Int) (*types.Transaction, err if t.MaxFeePerGas == nil { t.MaxFeePerGas = new(big.Int) + } else { + isDynamiFeeTx = true } if t.MaxPriorityFeePerGas == nil { t.MaxPriorityFeePerGas = t.MaxFeePerGas + } else { + isDynamiFeeTx = true } gasPrice = common.BigMin(new(big.Int).Add(t.MaxPriorityFeePerGas, baseFee), t.MaxFeePerGas) @@ -325,31 +334,53 @@ func (t *stTransaction) At(i indexes, baseFee *big.Int) (*types.Transaction, err value = v } - return &types.Transaction{ - From: t.From, - To: t.To, - Nonce: t.Nonce, - Value: value, - Gas: t.GasLimit[i.Gas], - GasPrice: gasPrice, - GasFeeCap: t.MaxFeePerGas, - GasTipCap: t.MaxPriorityFeePerGas, - Input: hex.MustDecodeHex(t.Data[i.Data]), - }, nil + // if tx is not dynamic and accessList is not nil, create an access list transaction + if !isDynamiFeeTx && accessList != nil { + return types.NewTx(&types.AccessListTxn{ + From: t.From, + To: t.To, + Nonce: t.Nonce, + Value: value, + Gas: t.GasLimit[i.Gas], + GasPrice: gasPrice, + Input: hex.MustDecodeHex(t.Data[i.Data]), + AccessList: accessList, + }), nil + } + + txType := types.LegacyTx + if isDynamiFeeTx { + txType = types.DynamicFeeTx + } + + return types.NewTx(&types.MixedTxn{ + Type: txType, + From: t.From, + To: t.To, + Nonce: t.Nonce, + Value: value, + Gas: t.GasLimit[i.Gas], + GasPrice: gasPrice, + GasFeeCap: t.MaxFeePerGas, + GasTipCap: t.MaxPriorityFeePerGas, + Input: hex.MustDecodeHex(t.Data[i.Data]), + AccessList: accessList, + }), nil } func (t *stTransaction) UnmarshalJSON(input []byte) error { type txUnmarshall struct { - Data []string `json:"data,omitempty"` - GasLimit []string `json:"gasLimit,omitempty"` - Value []string `json:"value,omitempty"` - GasPrice string `json:"gasPrice,omitempty"` - MaxFeePerGas string `json:"maxFeePerGas,omitempty"` - MaxPriorityFeePerGas string `json:"maxPriorityFeePerGas,omitempty"` - Nonce string `json:"nonce,omitempty"` - PrivateKey string `json:"secretKey,omitempty"` - Sender string `json:"sender"` - To string `json:"to,omitempty"` + Data []string `json:"data,omitempty"` + GasLimit []string `json:"gasLimit,omitempty"` + Value []string `json:"value,omitempty"` + GasPrice string `json:"gasPrice,omitempty"` + MaxFeePerGas string `json:"maxFeePerGas,omitempty"` + MaxPriorityFeePerGas string `json:"maxPriorityFeePerGas,omitempty"` + Nonce string `json:"nonce,omitempty"` + PrivateKey string `json:"secretKey,omitempty"` + Sender string `json:"sender"` + To string `json:"to,omitempty"` + AccessLists []*types.TxAccessList `json:"accessLists,omitempty"` } var dec txUnmarshall @@ -359,6 +390,7 @@ func (t *stTransaction) UnmarshalJSON(input []byte) error { t.Data = dec.Data t.Value = dec.Value + t.AccessLists = dec.AccessLists for _, i := range dec.GasLimit { j, err := stringToUint64(i) @@ -458,6 +490,7 @@ var Forks = map[string]*chain.Forks{ chain.Constantinople: chain.NewFork(0), }, "ConstantinopleFix": { + chain.EIP3607: chain.NewFork(0), chain.Homestead: chain.NewFork(0), chain.EIP150: chain.NewFork(0), chain.EIP155: chain.NewFork(0), @@ -465,9 +498,9 @@ var Forks = map[string]*chain.Forks{ chain.Byzantium: chain.NewFork(0), chain.Constantinople: chain.NewFork(0), chain.Petersburg: chain.NewFork(0), - chain.EIP3607: chain.NewFork(0), }, "Istanbul": { + chain.EIP3607: chain.NewFork(0), chain.Homestead: chain.NewFork(0), chain.EIP150: chain.NewFork(0), chain.EIP155: chain.NewFork(0), @@ -476,7 +509,6 @@ var Forks = map[string]*chain.Forks{ chain.Constantinople: chain.NewFork(0), chain.Petersburg: chain.NewFork(0), chain.Istanbul: chain.NewFork(0), - chain.EIP3607: chain.NewFork(0), }, "FrontierToHomesteadAt5": { chain.EIP3607: chain.NewFork(0), @@ -525,17 +557,44 @@ var Forks = map[string]*chain.Forks{ chain.Petersburg: chain.NewFork(0), chain.Istanbul: chain.NewFork(5), }, - // "London": { - // chain.Homestead: chain.NewFork(0), - // chain.EIP150: chain.NewFork(0), - // chain.EIP155: chain.NewFork(0), - // chain.EIP158: chain.NewFork(0), - // chain.Byzantium: chain.NewFork(0), - // chain.Constantinople: chain.NewFork(0), - // chain.Petersburg: chain.NewFork(0), - // chain.Istanbul: chain.NewFork(0), - // chain.London: chain.NewFork(0), - // }, + "Berlin": { + chain.EIP3607: chain.NewFork(0), + chain.Homestead: chain.NewFork(0), + chain.EIP150: chain.NewFork(0), + chain.EIP155: chain.NewFork(0), + chain.EIP158: chain.NewFork(0), + chain.Byzantium: chain.NewFork(0), + chain.Constantinople: chain.NewFork(0), + chain.Petersburg: chain.NewFork(0), + chain.Istanbul: chain.NewFork(0), + chain.Berlin: chain.NewFork(0), + }, + "BerlinToLondonAt5": { + chain.EIP3607: chain.NewFork(0), + chain.Homestead: chain.NewFork(0), + chain.EIP150: chain.NewFork(0), + chain.EIP155: chain.NewFork(0), + chain.EIP158: chain.NewFork(0), + chain.Byzantium: chain.NewFork(0), + chain.Constantinople: chain.NewFork(0), + chain.Petersburg: chain.NewFork(0), + chain.Istanbul: chain.NewFork(0), + chain.Berlin: chain.NewFork(0), + chain.London: chain.NewFork(5), + }, + "London": { + chain.EIP3607: chain.NewFork(0), + chain.Homestead: chain.NewFork(0), + chain.EIP150: chain.NewFork(0), + chain.EIP155: chain.NewFork(0), + chain.EIP158: chain.NewFork(0), + chain.Byzantium: chain.NewFork(0), + chain.Constantinople: chain.NewFork(0), + chain.Petersburg: chain.NewFork(0), + chain.Istanbul: chain.NewFork(0), + chain.Berlin: chain.NewFork(0), + chain.London: chain.NewFork(0), + }, } func contains(l []string, name string) bool { @@ -548,41 +607,57 @@ func contains(l []string, name string) bool { return false } -func listFolders(tests ...string) ([]string, error) { +func listFolders(paths []string) ([]string, error) { var folders []string - for _, t := range tests { - dir, err := os.Open(t) - if err != nil { - return nil, err - } - defer dir.Close() + for _, rootPath := range paths { + err := filepath.WalkDir(rootPath, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } - fileInfos, err := dir.Readdir(-1) - if err != nil { - return nil, err - } + if d.IsDir() { + files, err := os.ReadDir(path) + if err != nil { + return err + } - for _, fileInfo := range fileInfos { - if fileInfo.IsDir() && t != "path" { - folders = append(folders, filepath.Join(t, fileInfo.Name())) + if len(files) > 0 { + folders = append(folders, path) + } } + + return nil + }) + + if err != nil { + return nil, err } } return folders, nil } -func listFiles(folder string) ([]string, error) { +func listFiles(folder string, extensions ...string) ([]string, error) { var files []string - err := filepath.Walk(folder, func(path string, info os.FileInfo, err error) error { + err := filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } - if !info.IsDir() { - files = append(files, path) + if !d.IsDir() { + if len(extensions) > 0 { + // filter files by extensions + for _, ext := range extensions { + if strings.HasSuffix(path, ext) { + files = append(files, path) + } + } + } else { + // if no extensions filter is provided, add all files + files = append(files, path) + } } return nil diff --git a/tests/tests b/tests/tests index 428f218d7d..853b1e03b1 160000 --- a/tests/tests +++ b/tests/tests @@ -1 +1 @@ -Subproject commit 428f218d7d6f4a52544e12684afbfe6e2882ffbf +Subproject commit 853b1e03b1078d370614002851ba1ee9803d9fcf diff --git a/txpool/account.go b/txpool/account.go index 169e7b0983..408ea62928 100644 --- a/txpool/account.go +++ b/txpool/account.go @@ -35,13 +35,6 @@ func (m *accountsMap) initOnce(addr types.Address, nonce uint64) *account { return newAccount } -// exists checks if an account exists within the map. -func (m *accountsMap) exists(addr types.Address) bool { - _, ok := m.Load(addr) - - return ok -} - // getPrimaries collects the heads (first-in-line transaction) // from each of the promoted queues. func (m *accountsMap) getPrimaries() (primaries []*types.Transaction) { @@ -160,7 +153,7 @@ func (m *nonceToTxLookup) get(nonce uint64) *types.Transaction { } func (m *nonceToTxLookup) set(tx *types.Transaction) { - m.mapping[tx.Nonce] = tx + m.mapping[tx.Nonce()] = tx } func (m *nonceToTxLookup) reset() { @@ -169,7 +162,7 @@ func (m *nonceToTxLookup) reset() { func (m *nonceToTxLookup) remove(txs ...*types.Transaction) { for _, tx := range txs { - delete(m.mapping, tx.Nonce) + delete(m.mapping, tx.Nonce()) } } @@ -260,9 +253,9 @@ func (a *account) reset(nonce uint64, promoteCh chan<- promoteRequest) ( // it is important to signal promotion while // the locks are held to ensure no other // handler will mutate the account - if first := a.enqueued.peek(); first != nil && first.Nonce == nonce { + if first := a.enqueued.peek(); first != nil && first.Nonce() == nonce { // first enqueued tx is expected -> signal promotion - promoteCh <- promoteRequest{account: first.From} + promoteCh <- promoteRequest{account: first.From()} } return @@ -272,7 +265,7 @@ func (a *account) reset(nonce uint64, promoteCh chan<- promoteRequest) ( func (a *account) enqueue(tx *types.Transaction, replace bool) { replaceInQueue := func(queue minNonceQueue) bool { for i, x := range queue { - if x.Nonce == tx.Nonce { + if x.Nonce() == tx.Nonce() { queue[i] = tx // replace return true @@ -311,18 +304,18 @@ func (a *account) promote() (promoted []*types.Transaction, pruned []*types.Tran // sanity check currentNonce := a.getNonce() - if a.enqueued.length() == 0 || a.enqueued.peek().Nonce > currentNonce { + if a.enqueued.length() == 0 || a.enqueued.peek().Nonce() > currentNonce { // nothing to promote return } - nextNonce := a.enqueued.peek().Nonce + nextNonce := a.enqueued.peek().Nonce() // move all promotable txs (enqueued txs that are sequential in nonce) // to the account's promoted queue for { tx := a.enqueued.peek() - if tx == nil || tx.Nonce != nextNonce { + if tx == nil || tx.Nonce() != nextNonce { break } @@ -333,7 +326,7 @@ func (a *account) promote() (promoted []*types.Transaction, pruned []*types.Tran a.promoted.push(tx) // update counters - nextNonce = tx.Nonce + 1 + nextNonce = tx.Nonce() + 1 // prune the transactions with lower nonce pruned = append(pruned, a.enqueued.prune(nextNonce)...) diff --git a/txpool/lookup_map.go b/txpool/lookup_map.go index ebf49fba91..3150b48ae9 100644 --- a/txpool/lookup_map.go +++ b/txpool/lookup_map.go @@ -18,11 +18,11 @@ func (m *lookupMap) add(tx *types.Transaction) bool { m.Lock() defer m.Unlock() - if _, exists := m.all[tx.Hash]; exists { + if _, exists := m.all[tx.Hash()]; exists { return false } - m.all[tx.Hash] = tx + m.all[tx.Hash()] = tx return true } @@ -33,7 +33,7 @@ func (m *lookupMap) remove(txs ...*types.Transaction) { defer m.Unlock() for _, tx := range txs { - delete(m.all, tx.Hash) + delete(m.all, tx.Hash()) } } diff --git a/txpool/mock_test.go b/txpool/mock_test.go index 3cacffe9db..558ae99145 100644 --- a/txpool/mock_test.go +++ b/txpool/mock_test.go @@ -87,5 +87,5 @@ type mockSigner struct { } func (s *mockSigner) Sender(tx *types.Transaction) (types.Address, error) { - return tx.From, nil + return tx.From(), nil } diff --git a/txpool/operator.go b/txpool/operator.go index 4d7bdb5186..6cf2fef0da 100644 --- a/txpool/operator.go +++ b/txpool/operator.go @@ -24,7 +24,7 @@ func (p *TxPool) AddTxn(ctx context.Context, raw *proto.AddTxnReq) (*proto.AddTx return nil, fmt.Errorf("transaction's field raw is empty") } - txn := new(types.Transaction) + txn := &types.Transaction{} if err := txn.UnmarshalRLP(raw.Raw.Value); err != nil { return nil, err } @@ -35,7 +35,7 @@ func (p *TxPool) AddTxn(ctx context.Context, raw *proto.AddTxnReq) (*proto.AddTx return nil, err } - txn.From = from + txn.SetFrom(from) } if err := p.AddTx(txn); err != nil { @@ -43,7 +43,7 @@ func (p *TxPool) AddTxn(ctx context.Context, raw *proto.AddTxnReq) (*proto.AddTx } return &proto.AddTxnResp{ - TxHash: txn.Hash.String(), + TxHash: txn.Hash().String(), }, nil } diff --git a/txpool/queue_account.go b/txpool/queue_account.go index d0e8a9dc24..a2e27ffdc5 100644 --- a/txpool/queue_account.go +++ b/txpool/queue_account.go @@ -50,7 +50,7 @@ func (q *accountQueue) prune(nonce uint64) ( pruned []*types.Transaction, ) { for { - if tx := q.peek(); tx == nil || tx.Nonce >= nonce { + if tx := q.peek(); tx == nil || tx.Nonce() >= nonce { break } @@ -123,11 +123,11 @@ func (q *minNonceQueue) Swap(i, j int) { func (q *minNonceQueue) Less(i, j int) bool { // The higher gas price Tx comes first if the nonces are same - if (*q)[i].Nonce == (*q)[j].Nonce { - return (*q)[i].GasPrice.Cmp((*q)[j].GasPrice) > 0 + if (*q)[i].Nonce() == (*q)[j].Nonce() { + return (*q)[i].GasPrice().Cmp((*q)[j].GasPrice()) > 0 } - return (*q)[i].Nonce < (*q)[j].Nonce + return (*q)[i].Nonce() < (*q)[j].Nonce() } func (q *minNonceQueue) Push(x interface{}) { diff --git a/txpool/queue_priced.go b/txpool/queue_priced.go index 0bdf12a72f..0b7ee597f1 100644 --- a/txpool/queue_priced.go +++ b/txpool/queue_priced.go @@ -101,7 +101,7 @@ func (q *maxPriceQueue) Less(i, j int) bool { case 1: return true default: - return q.txs[i].Nonce < q.txs[j].Nonce + return q.txs[i].Nonce() < q.txs[j].Nonce() } } diff --git a/txpool/queue_priced_test.go b/txpool/queue_priced_test.go index e5b3dd7300..b619068454 100644 --- a/txpool/queue_priced_test.go +++ b/txpool/queue_priced_test.go @@ -23,45 +23,45 @@ func Test_maxPriceQueue(t *testing.T) { baseFee: 1000, unsorted: []*types.Transaction{ // Highest tx fee - { + types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasPrice: big.NewInt(0), GasFeeCap: big.NewInt(2000), GasTipCap: big.NewInt(500), - }, + }), // Lowest tx fee - { + types.NewTx(&types.MixedTxn{ Type: types.LegacyTx, GasPrice: big.NewInt(100), - }, + }), // Middle tx fee - { + types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasPrice: big.NewInt(0), GasFeeCap: big.NewInt(1500), GasTipCap: big.NewInt(200), - }, + }), }, sorted: []*types.Transaction{ // Highest tx fee - { + types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasPrice: big.NewInt(0), GasFeeCap: big.NewInt(2000), GasTipCap: big.NewInt(500), - }, + }), // Middle tx fee - { + types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasPrice: big.NewInt(0), GasFeeCap: big.NewInt(1500), GasTipCap: big.NewInt(200), - }, + }), // Lowest tx fee - { + types.NewTx(&types.MixedTxn{ Type: types.LegacyTx, GasPrice: big.NewInt(100), - }, + }), }, }, { @@ -69,49 +69,49 @@ func Test_maxPriceQueue(t *testing.T) { baseFee: 1000, unsorted: []*types.Transaction{ // Highest tx fee - { + types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasFeeCap: big.NewInt(2000), GasTipCap: big.NewInt(500), Nonce: 3, - }, + }), // Lowest tx fee - { + types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasFeeCap: big.NewInt(2000), GasTipCap: big.NewInt(500), Nonce: 1, - }, + }), // Middle tx fee - { + types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasFeeCap: big.NewInt(2000), GasTipCap: big.NewInt(500), Nonce: 2, - }, + }), }, sorted: []*types.Transaction{ // Highest tx fee - { + types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasFeeCap: big.NewInt(2000), GasTipCap: big.NewInt(500), Nonce: 1, - }, + }), // Middle tx fee - { + types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasFeeCap: big.NewInt(2000), GasTipCap: big.NewInt(500), Nonce: 2, - }, + }), // Lowest tx fee - { + types.NewTx(&types.MixedTxn{ Type: types.DynamicFeeTx, GasFeeCap: big.NewInt(2000), GasTipCap: big.NewInt(500), Nonce: 3, - }, + }), }, }, { @@ -119,37 +119,37 @@ func Test_maxPriceQueue(t *testing.T) { baseFee: 0, unsorted: []*types.Transaction{ // Highest tx fee - { + types.NewTx(&types.MixedTxn{ GasFeeCap: big.NewInt(3000), GasTipCap: big.NewInt(100), - }, + }), // Lowest tx fee - { + types.NewTx(&types.MixedTxn{ GasFeeCap: big.NewInt(1000), GasTipCap: big.NewInt(100), - }, + }), // Middle tx fee - { + types.NewTx(&types.MixedTxn{ GasFeeCap: big.NewInt(2000), GasTipCap: big.NewInt(100), - }, + }), }, sorted: []*types.Transaction{ // Highest tx fee - { - GasFeeCap: big.NewInt(3000), + types.NewTx(&types.MixedTxn{ GasTipCap: big.NewInt(100), - }, + GasFeeCap: big.NewInt(3000), + }), // Middle tx fee - { + types.NewTx(&types.MixedTxn{ GasFeeCap: big.NewInt(2000), GasTipCap: big.NewInt(100), - }, + }), // Lowest tx fee - { + types.NewTx(&types.MixedTxn{ GasFeeCap: big.NewInt(1000), GasTipCap: big.NewInt(100), - }, + }), }, }, { @@ -157,37 +157,43 @@ func Test_maxPriceQueue(t *testing.T) { baseFee: 0, unsorted: []*types.Transaction{ // Highest tx fee - { + types.NewTx(&types.MixedTxn{ + GasPrice: nil, GasFeeCap: big.NewInt(1000), GasTipCap: big.NewInt(300), - }, + }), // Lowest tx fee - { + types.NewTx(&types.MixedTxn{ + GasPrice: nil, GasFeeCap: big.NewInt(1000), GasTipCap: big.NewInt(100), - }, + }), // Middle tx fee - { + types.NewTx(&types.MixedTxn{ + GasPrice: nil, GasFeeCap: big.NewInt(1000), GasTipCap: big.NewInt(200), - }, + }), }, sorted: []*types.Transaction{ // Highest tx fee - { + types.NewTx(&types.MixedTxn{ + GasPrice: nil, GasFeeCap: big.NewInt(1000), GasTipCap: big.NewInt(300), - }, + }), // Middle tx fee - { + types.NewTx(&types.MixedTxn{ + GasPrice: nil, GasFeeCap: big.NewInt(1000), GasTipCap: big.NewInt(200), - }, + }), // Lowest tx fee - { + types.NewTx(&types.MixedTxn{ + GasPrice: nil, GasFeeCap: big.NewInt(1000), GasTipCap: big.NewInt(100), - }, + }), }, }, { @@ -195,31 +201,31 @@ func Test_maxPriceQueue(t *testing.T) { baseFee: 0, unsorted: []*types.Transaction{ // Highest tx fee - { + types.NewTx(&types.MixedTxn{ GasPrice: big.NewInt(1000), - }, + }), // Lowest tx fee - { + types.NewTx(&types.MixedTxn{ GasPrice: big.NewInt(100), - }, + }), // Middle tx fee - { + types.NewTx(&types.MixedTxn{ GasPrice: big.NewInt(500), - }, + }), }, sorted: []*types.Transaction{ // Highest tx fee - { + types.NewTx(&types.MixedTxn{ GasPrice: big.NewInt(1000), - }, + }), // Middle tx fee - { + types.NewTx(&types.MixedTxn{ GasPrice: big.NewInt(500), - }, + }), // Lowest tx fee - { + types.NewTx(&types.MixedTxn{ GasPrice: big.NewInt(100), - }, + }), }, }, { @@ -297,23 +303,23 @@ func generateTx(i int) *types.Transaction { types.DynamicFeeTx, } - tx := &types.Transaction{ + tx := types.NewTx(&types.MixedTxn{ Type: txTypes[r.Intn(len(txTypes))], - } + }) - switch tx.Type { + switch tx.Type() { case types.LegacyTx: minGasPrice := 1000 * i maxGasPrice := 100000 * i - tx.GasPrice = new(big.Int).SetInt64(int64(rand.Intn(maxGasPrice-minGasPrice) + minGasPrice)) + tx.SetGasPrice(new(big.Int).SetInt64(int64(rand.Intn(maxGasPrice-minGasPrice) + minGasPrice))) case types.DynamicFeeTx: minGasFeeCap := 1000 * i maxGasFeeCap := 100000 * i - tx.GasFeeCap = new(big.Int).SetInt64(int64(rand.Intn(maxGasFeeCap-minGasFeeCap) + minGasFeeCap)) + tx.SetGasFeeCap(new(big.Int).SetInt64(int64(rand.Intn(maxGasFeeCap-minGasFeeCap) + minGasFeeCap))) minGasTipCap := 100 * i maxGasTipCap := 10000 * i - tx.GasTipCap = new(big.Int).SetInt64(int64(rand.Intn(maxGasTipCap-minGasTipCap) + minGasTipCap)) + tx.SetGasTipCap(new(big.Int).SetInt64(int64(rand.Intn(maxGasTipCap-minGasTipCap) + minGasTipCap))) } return tx diff --git a/txpool/txpool.go b/txpool/txpool.go index 593b2c731a..53b041e7d1 100644 --- a/txpool/txpool.go +++ b/txpool/txpool.go @@ -107,14 +107,6 @@ type Config struct { /* All requests are passed to the main loop through their designated channels. */ -// An enqueueRequest is created for any transaction -// meant to be enqueued onto some account. -// This request is created for (new) transactions -// that passed validation in addTx. -type enqueueRequest struct { - tx *types.Transaction -} - // A promoteRequest is created each time some account // is eligible for promotion. This request is signaled // on 2 occasions: @@ -358,7 +350,7 @@ func (p *TxPool) Peek() *types.Transaction { // from that account (if any). func (p *TxPool) Pop(tx *types.Transaction) { // fetch the associated account - account := p.accounts.get(tx.From) + account := p.accounts.get(tx.From()) account.promoted.lock(true) account.nonceToTx.lock() @@ -392,8 +384,8 @@ func (p *TxPool) Pop(tx *types.Transaction) { // Drop clears the entire account associated with the given transaction // and reverts its next (expected) nonce. func (p *TxPool) Drop(tx *types.Transaction) { - account := p.accounts.get(tx.From) - p.dropAccount(account, tx.Nonce, tx) + account := p.accounts.get(tx.From()) + p.dropAccount(account, tx.Nonce(), tx) } // dropAccount clears all promoted and enqueued tx from the account @@ -439,13 +431,13 @@ func (p *TxPool) dropAccount(account *account, nextNonce uint64, tx *types.Trans dropped = account.enqueued.clear() clearAccountQueue(dropped) - p.eventManager.signalEvent(proto.EventType_DROPPED, tx.Hash) + p.eventManager.signalEvent(proto.EventType_DROPPED, tx.Hash()) if p.logger.IsDebug() { p.logger.Debug("dropped account txs", "num", droppedCount, "next_nonce", nextNonce, - "address", tx.From.String(), + "address", tx.From().String(), ) } } @@ -454,12 +446,12 @@ func (p *TxPool) dropAccount(account *account, nextNonce uint64, tx *types.Trans // due to a recoverable error. If an account has been demoted too many times (maxAccountDemotions), // it is Dropped instead. func (p *TxPool) Demote(tx *types.Transaction) { - account := p.accounts.get(tx.From) + account := p.accounts.get(tx.From()) if account.Demotions() >= maxAccountDemotions { if p.logger.IsDebug() { p.logger.Debug( "Demote: threshold reached - dropping account", - "addr", tx.From.String(), + "addr", tx.From().String(), ) } @@ -473,7 +465,7 @@ func (p *TxPool) Demote(tx *types.Transaction) { account.incrementDemotions() - p.eventManager.signalEvent(proto.EventType_DEMOTED, tx.Hash) + p.eventManager.signalEvent(proto.EventType_DEMOTED, tx.Hash()) } // ResetWithHeaders processes the transactions from the new @@ -509,7 +501,7 @@ func (p *TxPool) processEvent(event *blockchain.Event) { for _, tx := range block.Transactions { var err error - addr := tx.From + addr := tx.From() if addr == types.ZeroAddress { // From field is not set, extract the signer if addr, err = p.signer.Sender(tx); err != nil { @@ -552,11 +544,11 @@ func (p *TxPool) processEvent(event *blockchain.Event) { // constraints before entering the pool. func (p *TxPool) validateTx(tx *types.Transaction) error { // Check the transaction type. State transactions are not expected to be added to the pool - if tx.Type == types.StateTx { + if tx.Type() == types.StateTx { metrics.IncrCounter([]string{txPoolMetrics, "invalid_tx_type"}, 1) return fmt.Errorf("%w: type %d rejected, state transactions are not expected to be added to the pool", - ErrInvalidTxType, tx.Type) + ErrInvalidTxType, tx.Type()) } // Check the transaction size to overcome DOS Attacks @@ -567,7 +559,7 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { } // Check if the transaction has a strictly positive value - if tx.Value.Sign() < 0 { + if tx.Value().Sign() < 0 { metrics.IncrCounter([]string{txPoolMetrics, "negative_value_tx"}, 1) return ErrNegativeValue @@ -585,16 +577,16 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { // If the from field is set, check that // it matches the signer - if tx.From != types.ZeroAddress && - tx.From != from { + if tx.From() != types.ZeroAddress && + tx.From() != from { metrics.IncrCounter([]string{txPoolMetrics, "invalid_sender_txs"}, 1) return ErrInvalidSender } // If no address was set, update it - if tx.From == types.ZeroAddress { - tx.From = from + if tx.From() == types.ZeroAddress { + tx.SetFrom(from) } // Grab current block number @@ -605,7 +597,7 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { forks := p.forks.At(currentBlockNumber) // Check if transaction can deploy smart contract - if tx.IsContractCreation() && forks.EIP158 && len(tx.Input) > state.TxPoolMaxInitCodeSize { + if tx.IsContractCreation() && forks.EIP158 && len(tx.Input()) > state.TxPoolMaxInitCodeSize { metrics.IncrCounter([]string{txPoolMetrics, "contract_deploy_too_large_txs"}, 1) return runtime.ErrMaxCodeSizeExceeded @@ -616,48 +608,62 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { latestBlockGasLimit := currentHeader.GasLimit baseFee := p.GetBaseFee() // base fee is calculated for the next block - if tx.Type == types.DynamicFeeTx { + if tx.Type() == types.AccessListTx { + // Reject access list tx if berlin hardfork(eip-2930) is not enabled + if !forks.Berlin { + metrics.IncrCounter([]string{txPoolMetrics, "invalid_tx_type"}, 1) + + return ErrInvalidTxType + } + + // check if the given tx is not underpriced (same as Legacy approach) + if tx.GetGasPrice(p.GetBaseFee()).Cmp(big.NewInt(0).SetUint64(p.priceLimit)) < 0 { + metrics.IncrCounter([]string{txPoolMetrics, "underpriced_tx"}, 1) + + return ErrUnderpriced + } + } else if tx.Type() == types.DynamicFeeTx { // Reject dynamic fee tx if london hardfork is not enabled if !forks.London { metrics.IncrCounter([]string{txPoolMetrics, "tx_type"}, 1) - return fmt.Errorf("%w: type %d rejected, london hardfork is not enabled", ErrTxTypeNotSupported, tx.Type) + return fmt.Errorf("%w: type %d rejected, london hardfork is not enabled", ErrTxTypeNotSupported, tx.Type()) } // Check EIP-1559-related fields and make sure they are correct - if tx.GasFeeCap == nil || tx.GasTipCap == nil { + if tx.GasFeeCap() == nil || tx.GasTipCap() == nil { metrics.IncrCounter([]string{txPoolMetrics, "underpriced_tx"}, 1) return ErrUnderpriced } - if tx.GasFeeCap.BitLen() > 256 { + if tx.GasFeeCap().BitLen() > 256 { metrics.IncrCounter([]string{txPoolMetrics, "fee_cap_too_high_dynamic_tx"}, 1) return ErrFeeCapVeryHigh } - if tx.GasTipCap.BitLen() > 256 { + if tx.GasTipCap().BitLen() > 256 { metrics.IncrCounter([]string{txPoolMetrics, "tip_too_high_dynamic_tx"}, 1) return ErrTipVeryHigh } - if tx.GasFeeCap.Cmp(tx.GasTipCap) < 0 { + if tx.GasFeeCap().Cmp(tx.GasTipCap()) < 0 { metrics.IncrCounter([]string{txPoolMetrics, "tip_above_fee_cap_dynamic_tx"}, 1) return ErrTipAboveFeeCap } // Reject underpriced transactions - if tx.GasFeeCap.Cmp(new(big.Int).SetUint64(baseFee)) < 0 { + if tx.GasFeeCap().Cmp(new(big.Int).SetUint64(baseFee)) < 0 { metrics.IncrCounter([]string{txPoolMetrics, "underpriced_tx"}, 1) return ErrUnderpriced } } else { // Legacy approach to check if the given tx is not underpriced when london hardfork is enabled - if forks.London && tx.GasPrice.Cmp(new(big.Int).SetUint64(baseFee)) < 0 { + if forks.London && tx.GasPrice().Cmp(new(big.Int).SetUint64(baseFee)) < 0 { metrics.IncrCounter([]string{txPoolMetrics, "underpriced_tx"}, 1) return ErrUnderpriced @@ -672,13 +678,13 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { } // Check nonce ordering - if p.store.GetNonce(stateRoot, tx.From) > tx.Nonce { + if p.store.GetNonce(stateRoot, tx.From()) > tx.Nonce() { metrics.IncrCounter([]string{txPoolMetrics, "nonce_too_low_tx"}, 1) return ErrNonceTooLow } - accountBalance, balanceErr := p.store.GetBalance(stateRoot, tx.From) + accountBalance, balanceErr := p.store.GetBalance(stateRoot, tx.From()) if balanceErr != nil { metrics.IncrCounter([]string{txPoolMetrics, "invalid_account_state_tx"}, 1) @@ -700,13 +706,13 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { return err } - if tx.Gas < intrinsicGas { + if tx.Gas() < intrinsicGas { metrics.IncrCounter([]string{txPoolMetrics, "intrinsic_gas_low_tx"}, 1) return ErrIntrinsicGas } - if tx.Gas > latestBlockGasLimit { + if tx.Gas() > latestBlockGasLimit { metrics.IncrCounter([]string{txPoolMetrics, "block_gas_limit_exceeded_tx"}, 1) return ErrBlockLimitExceeded @@ -739,7 +745,7 @@ func (p *TxPool) pruneAccountsWithNonceHoles() { return true } - if firstTx.Nonce == account.getNonce() { + if firstTx.Nonce() == account.getNonce() { return true } @@ -760,7 +766,7 @@ func (p *TxPool) pruneAccountsWithNonceHoles() { // (only once) and an enqueueRequest is signaled. func (p *TxPool) addTx(origin txOrigin, tx *types.Transaction) error { if p.logger.IsDebug() { - p.logger.Debug("add tx", "origin", origin.String(), "hash", tx.Hash.String()) + p.logger.Debug("add tx", "origin", origin.String(), "hash", tx.Hash().String()) } // validate incoming tx @@ -769,15 +775,15 @@ func (p *TxPool) addTx(origin txOrigin, tx *types.Transaction) error { } // add chainID to the tx - only dynamic fee tx - if tx.Type == types.DynamicFeeTx { - tx.ChainID = p.chainID + if tx.Type() == types.DynamicFeeTx { + tx.SetChainID(p.chainID) } // calculate tx hash tx.ComputeHash() // initialize account for this address once or retrieve existing one - account := p.getOrCreateAccount(tx.From) + account := p.getOrCreateAccount(tx.From()) account.promoted.lock(true) account.enqueued.lock(true) @@ -795,7 +801,7 @@ func (p *TxPool) addTx(origin txOrigin, tx *types.Transaction) error { if p.gauge.highPressure() { p.signalPruning() - if tx.Nonce > accountNonce { + if tx.Nonce() > accountNonce { metrics.IncrCounter([]string{txPoolMetrics, "rejected_future_tx"}, 1) return ErrRejectFutureTx @@ -803,9 +809,9 @@ func (p *TxPool) addTx(origin txOrigin, tx *types.Transaction) error { } // try to find if there is transaction with same nonce for this account - oldTxWithSameNonce := account.nonceToTx.get(tx.Nonce) + oldTxWithSameNonce := account.nonceToTx.get(tx.Nonce()) if oldTxWithSameNonce != nil { - if oldTxWithSameNonce.Hash == tx.Hash { + if oldTxWithSameNonce.Hash() == tx.Hash() { metrics.IncrCounter([]string{txPoolMetrics, "already_known_tx"}, 1) return ErrAlreadyKnown @@ -817,12 +823,12 @@ func (p *TxPool) addTx(origin txOrigin, tx *types.Transaction) error { return ErrReplacementUnderpriced } } else { - if account.enqueued.length() == account.maxEnqueued && tx.Nonce != accountNonce { + if account.enqueued.length() == account.maxEnqueued && tx.Nonce() != accountNonce { return ErrMaxEnqueuedLimitReached } // reject low nonce tx - if tx.Nonce < accountNonce { + if tx.Nonce() < accountNonce { metrics.IncrCounter([]string{txPoolMetrics, "nonce_too_low_tx"}, 1) return ErrNonceTooLow @@ -867,24 +873,24 @@ func (p *TxPool) addTx(origin txOrigin, tx *types.Transaction) error { account.enqueue(tx, oldTxWithSameNonce != nil) // add or replace tx into account - go p.invokePromotion(tx, tx.Nonce <= accountNonce) // don't signal promotion for higher nonce txs + go p.invokePromotion(tx, tx.Nonce() <= accountNonce) // don't signal promotion for higher nonce txs return nil } func (p *TxPool) invokePromotion(tx *types.Transaction, callPromote bool) { - p.eventManager.signalEvent(proto.EventType_ADDED, tx.Hash) + p.eventManager.signalEvent(proto.EventType_ADDED, tx.Hash()) if p.logger.IsDebug() { - p.logger.Debug("enqueue request", "hash", tx.Hash.String()) + p.logger.Debug("enqueue request", "hash", tx.Hash().String()) } - p.eventManager.signalEvent(proto.EventType_ENQUEUED, tx.Hash) + p.eventManager.signalEvent(proto.EventType_ENQUEUED, tx.Hash()) if callPromote { select { case <-p.shutdownCh: - case p.promoteReqCh <- promoteRequest{account: tx.From}: // BLOCKING + case p.promoteReqCh <- promoteRequest{account: tx.From()}: // BLOCKING } } } @@ -932,7 +938,7 @@ func (p *TxPool) addGossipTx(obj interface{}, _ peer.ID) { return } - tx := new(types.Transaction) + tx := &types.Transaction{} // decode tx if err := tx.UnmarshalRLP(raw.Raw.Value); err != nil { @@ -945,13 +951,13 @@ func (p *TxPool) addGossipTx(obj interface{}, _ peer.ID) { if err := p.addTx(gossip, tx); err != nil { if errors.Is(err, ErrAlreadyKnown) { if p.logger.IsDebug() { - p.logger.Debug("rejecting known tx (gossip)", "hash", tx.Hash.String()) + p.logger.Debug("rejecting known tx (gossip)", "hash", tx.Hash().String()) } return } - p.logger.Error("failed to add broadcast tx", "err", err, "hash", tx.Hash.String()) + p.logger.Error("failed to add broadcast tx", "err", err, "hash", tx.Hash().String()) } } @@ -1040,7 +1046,7 @@ func (p *TxPool) updateAccountSkipsCounts(latestActiveAccounts map[types.Address } // account has been skipped too many times - nextNonce := p.store.GetNonce(stateRoot, firstTx.From) + nextNonce := p.store.GetNonce(stateRoot, firstTx.From()) p.dropAccount(account, nextNonce, firstTx) account.resetSkips() @@ -1073,7 +1079,7 @@ func (p *TxPool) Length() uint64 { // toHash returns the hash(es) of given transaction(s) func toHash(txs ...*types.Transaction) (hashes []types.Hash) { for _, tx := range txs { - hashes = append(hashes, tx.Hash) + hashes = append(hashes, tx.Hash()) } return diff --git a/txpool/txpool_test.go b/txpool/txpool_test.go index 9d52c56156..4e1ae0e369 100644 --- a/txpool/txpool_test.go +++ b/txpool/txpool_test.go @@ -56,14 +56,14 @@ func newTx(addr types.Address, nonce, slots uint64) *types.Transaction { return nil } - return &types.Transaction{ + return types.NewTx(&types.MixedTxn{ From: addr, Nonce: nonce, Value: big.NewInt(1), GasPrice: big.NewInt(0).SetUint64(defaultPriceLimit), Gas: validGasLimit, Input: input, - } + }) } // returns a new txpool with default test config @@ -141,7 +141,7 @@ func TestAddTxErrors(t *testing.T) { pool := setupPool() tx := newTx(defaultAddr, 0, 1) - tx.Type = types.StateTx + tx.SetTransactionType(types.StateTx) err := pool.addTx(local, signTx(tx)) @@ -161,7 +161,7 @@ func TestAddTxErrors(t *testing.T) { pool.forks.RemoveFork(chain.London) tx := newTx(defaultAddr, 0, 1) - tx.Type = types.DynamicFeeTx + tx.SetTransactionType(types.DynamicFeeTx) err := pool.addTx(local, signTx(tx)) @@ -180,7 +180,7 @@ func TestAddTxErrors(t *testing.T) { pool := setupPool() tx := newTx(defaultAddr, 0, 1) - tx.Value = big.NewInt(-5) + tx.SetValue(big.NewInt(-5)) assert.ErrorIs(t, pool.addTx(local, signTx(tx)), @@ -193,8 +193,8 @@ func TestAddTxErrors(t *testing.T) { pool := setupPool() tx := newTx(defaultAddr, 0, 1) - tx.Value = big.NewInt(1) - tx.Gas = 10000000000001 + tx.SetValue(big.NewInt(1)) + tx.SetGas(10000000000001) tx = signTx(tx) @@ -299,7 +299,7 @@ func TestAddTxErrors(t *testing.T) { pool := setupPool() tx := newTx(defaultAddr, 0, 1) - tx.Gas = 1 + tx.SetGas(1) tx = signTx(tx) assert.ErrorIs(t, @@ -330,7 +330,7 @@ func TestAddTxErrors(t *testing.T) { pool := setupPool() tx := newTx(defaultAddr, 0, 1) - tx.GasPrice = big.NewInt(200) + tx.SetGasPrice(big.NewInt(200)) tx = signTx(tx) // send the tx beforehand @@ -338,7 +338,7 @@ func TestAddTxErrors(t *testing.T) { <-pool.promoteReqCh tx = newTx(defaultAddr, 0, 1) - tx.GasPrice = big.NewInt(100) + tx.SetGasPrice(big.NewInt(100)) tx = signTx(tx) assert.ErrorIs(t, @@ -358,7 +358,7 @@ func TestAddTxErrors(t *testing.T) { _, err := rand.Read(data) assert.NoError(t, err) - tx.Input = data + tx.SetInput(data) tx = signTx(tx) assert.ErrorIs(t, @@ -387,7 +387,7 @@ func TestAddTxErrors(t *testing.T) { pool := setupPool() tx := newTx(defaultAddr, 0, 1) - tx.GasPrice.SetUint64(1000000000000) + tx.GasPrice().SetUint64(1000000000000) tx = signTx(tx) assert.ErrorIs(t, @@ -445,7 +445,7 @@ func TestPruneAccountsWithNonceHoles(t *testing.T) { // assert no nonce hole assert.Equal(t, pool.accounts.get(addr1).getNonce(), - pool.accounts.get(addr1).enqueued.peek().Nonce, + pool.accounts.get(addr1).enqueued.peek().Nonce(), ) pool.pruneAccountsWithNonceHoles() @@ -476,7 +476,7 @@ func TestPruneAccountsWithNonceHoles(t *testing.T) { // assert nonce hole assert.NotEqual(t, pool.accounts.get(addr1).getNonce(), - pool.accounts.get(addr1).enqueued.peek().Nonce, + pool.accounts.get(addr1).enqueued.peek().Nonce(), ) pool.pruneAccountsWithNonceHoles() @@ -567,7 +567,7 @@ func TestAddTxHighPressure(t *testing.T) { tx := newTx(addr1, 5, 1) assert.NoError(t, pool.addTx(local, tx)) - _, exists := pool.index.get(tx.Hash) + _, exists := pool.index.get(tx.Hash()) assert.True(t, exists) acc := pool.accounts.get(addr1) @@ -649,7 +649,7 @@ func TestDropKnownGossipTx(t *testing.T) { // send tx as local assert.NoError(t, pool.addTx(local, tx)) - _, exists := pool.index.get(tx.Hash) + _, exists := pool.index.get(tx.Hash()) assert.True(t, exists) // send tx as gossip (will be discarded) @@ -843,7 +843,7 @@ func TestAddTx(t *testing.T) { slots uint64, ) *types.Transaction { tx := newTx(addr, nonce, slots) - tx.GasPrice.SetUint64(gasPrice) + tx.GasPrice().SetUint64(gasPrice) return tx } @@ -858,7 +858,7 @@ func TestAddTx(t *testing.T) { // add the transactions assert.NoError(t, pool.addTx(local, tx2)) - _, exists := pool.index.get(tx2.Hash) + _, exists := pool.index.get(tx2.Hash()) assert.True(t, exists) // check the account nonce before promoting @@ -870,7 +870,7 @@ func TestAddTx(t *testing.T) { <-pool.promoteReqCh // at this point the pointer of the first tx should be overwritten by the second pricier tx - _, exists = pool.index.get(tx2.Hash) + _, exists = pool.index.get(tx2.Hash()) assert.True(t, exists) assert.Len(t, pool.index.all, int(1)) @@ -904,7 +904,7 @@ func TestAddTx(t *testing.T) { slots uint64, ) *types.Transaction { tx := newTx(addr, nonce, slots) - tx.GasPrice.SetUint64(gasPrice) + tx.GasPrice().SetUint64(gasPrice) return tx } @@ -919,7 +919,7 @@ func TestAddTx(t *testing.T) { // add the transactions assert.NoError(t, pool.addTx(local, tx2)) - _, exists := pool.index.get(tx2.Hash) + _, exists := pool.index.get(tx2.Hash()) assert.True(t, exists) // check the account nonce before promoting @@ -960,7 +960,7 @@ func TestAddTx(t *testing.T) { slots uint64, ) *types.Transaction { tx := newTx(addr, nonce, slots) - tx.GasPrice.SetUint64(gasPrice) + tx.GasPrice().SetUint64(gasPrice) return tx } @@ -976,10 +976,10 @@ func TestAddTx(t *testing.T) { assert.NoError(t, pool.addTx(local, tx1)) assert.NoError(t, pool.addTx(local, tx2)) - _, exists := pool.index.get(tx1.Hash) + _, exists := pool.index.get(tx1.Hash()) assert.False(t, exists) - _, exists = pool.index.get(tx2.Hash) + _, exists = pool.index.get(tx2.Hash()) assert.True(t, exists) // check the account nonce before promoting @@ -990,7 +990,7 @@ func TestAddTx(t *testing.T) { promReq2 := <-pool.promoteReqCh // at this point the pointer of the first tx should be overwritten by the second pricier tx - _, exists = pool.index.get(tx2.Hash) + _, exists = pool.index.get(tx2.Hash()) assert.True(t, exists) assert.Len(t, pool.index.all, int(1)) @@ -1011,7 +1011,7 @@ func TestAddTx(t *testing.T) { assert.Equal(t, uint64(0), pool.accounts.get(addr1).enqueued.length()) // should be empty assert.Equal(t, uint64(1), pool.accounts.get(addr1).promoted.length()) - _, exists = pool.index.get(tx2.Hash) + _, exists = pool.index.get(tx2.Hash()) assert.True(t, exists) assert.Equal(t, len(pool.index.all), int(1)) @@ -1030,7 +1030,7 @@ func TestAddTx(t *testing.T) { assert.Equal(t, uint64(1), pool.accounts.get(addr1).promoted.length()) // because the *tx1 and *tx2 now contain the same hash we only need to check for *tx2 existence - _, exists = pool.index.get(tx2.Hash) + _, exists = pool.index.get(tx2.Hash()) assert.True(t, exists) assert.Equal(t, len(pool.index.all), int(1)) @@ -1061,7 +1061,7 @@ func TestAddTx(t *testing.T) { slots uint64, ) *types.Transaction { tx := newTx(addr, nonce, slots) - tx.GasPrice.SetUint64(gasPrice) + tx.GasPrice().SetUint64(gasPrice) return tx } @@ -1080,13 +1080,13 @@ func TestAddTx(t *testing.T) { acc := pool.accounts.get(addr1) assert.NotNil(t, acc) - _, exists := pool.index.get(tx1.Hash) + _, exists := pool.index.get(tx1.Hash()) assert.False(t, exists) - _, exists = pool.index.get(tx2.Hash) + _, exists = pool.index.get(tx2.Hash()) assert.True(t, exists) - maptx2 := acc.nonceToTx.get(tx2.Nonce) + maptx2 := acc.nonceToTx.get(tx2.Nonce()) nonceMapLength := len(acc.nonceToTx.mapping) assert.NotNil(t, maptx2) @@ -1120,7 +1120,7 @@ func TestAddTx(t *testing.T) { slots uint64, ) *types.Transaction { tx := newTx(addr, nonce, slots) - tx.GasPrice.SetUint64(gasPrice) + tx.GasPrice().SetUint64(gasPrice) return tx } @@ -1153,25 +1153,25 @@ func TestAddTx(t *testing.T) { assert.Equal(t, uint64(0), pool.accounts.get(addr1).enqueued.length()) assert.Equal(t, uint64(1), pool.accounts.get(addr1).promoted.length()) - _, exists := pool.index.get(tx1.Hash) + _, exists := pool.index.get(tx1.Hash()) assert.True(t, exists) - _, exists = pool.index.get(tx2.Hash) + _, exists = pool.index.get(tx2.Hash()) assert.False(t, exists) assert.NoError(t, pool.addTx(local, tx2)) - maptx2 := acc.nonceToTx.get(tx2.Nonce) + maptx2 := acc.nonceToTx.get(tx2.Nonce()) nonceMapLength := len(acc.nonceToTx.mapping) assert.NotNil(t, maptx2) assert.Equal(t, tx2, maptx2) assert.Equal(t, int(1), nonceMapLength) - _, exists = pool.index.get(tx1.Hash) + _, exists = pool.index.get(tx1.Hash()) assert.False(t, exists) - _, exists = pool.index.get(tx2.Hash) + _, exists = pool.index.get(tx2.Hash()) assert.True(t, exists) assert.Equal(t, len(pool.index.all), int(1)) @@ -1226,7 +1226,7 @@ func TestPromoteHandler(t *testing.T) { assert.Equal(t, uint64(0), pool.accounts.get(addr1).promoted.length()) mapLen := len(acc.nonceToTx.mapping) - maptx := acc.nonceToTx.get(tx.Nonce) + maptx := acc.nonceToTx.get(tx.Nonce()) assert.Equal(t, int(1), mapLen) assert.Equal(t, tx, maptx) @@ -1238,7 +1238,7 @@ func TestPromoteHandler(t *testing.T) { assert.Equal(t, uint64(0), pool.accounts.get(addr1).promoted.length()) mapLen = len(acc.nonceToTx.mapping) - maptx = acc.nonceToTx.get(tx.Nonce) + maptx = acc.nonceToTx.get(tx.Nonce()) assert.Equal(t, int(1), mapLen) assert.Equal(t, tx, maptx) @@ -1259,7 +1259,7 @@ func TestPromoteHandler(t *testing.T) { assert.NotNil(t, acc) mapLen := len(acc.nonceToTx.mapping) - maptx := acc.nonceToTx.get(tx.Nonce) + maptx := acc.nonceToTx.get(tx.Nonce()) assert.Equal(t, int(1), mapLen) assert.Equal(t, tx, maptx) @@ -1274,7 +1274,7 @@ func TestPromoteHandler(t *testing.T) { assert.Equal(t, uint64(1), pool.accounts.get(addr1).promoted.length()) mapLen = len(acc.nonceToTx.mapping) - maptx = acc.nonceToTx.get(tx.Nonce) + maptx = acc.nonceToTx.get(tx.Nonce()) assert.Equal(t, int(1), mapLen) assert.Equal(t, tx, maptx) @@ -1313,7 +1313,7 @@ func TestPromoteHandler(t *testing.T) { acc := pool.accounts.get(addr1) for _, tx := range txs { - maptx := acc.nonceToTx.get(tx.Nonce) + maptx := acc.nonceToTx.get(tx.Nonce()) assert.Equal(t, tx, maptx) } @@ -1333,7 +1333,7 @@ func TestPromoteHandler(t *testing.T) { assert.Equal(t, uint64(10), pool.accounts.get(addr1).promoted.length()) for _, tx := range txs { - maptx := acc.nonceToTx.get(tx.Nonce) + maptx := acc.nonceToTx.get(tx.Nonce()) assert.Equal(t, tx, maptx) } @@ -1364,7 +1364,7 @@ func TestPromoteHandler(t *testing.T) { acc := pool.accounts.get(addr1) for _, tx := range txs { - maptx := acc.nonceToTx.get(tx.Nonce) + maptx := acc.nonceToTx.get(tx.Nonce()) assert.Equal(t, tx, maptx) } @@ -1455,7 +1455,7 @@ func TestResetAccount(t *testing.T) { // setup prestate acc := pool.getOrCreateAccount(addr1) - acc.setNonce(test.txs[0].Nonce) + acc.setNonce(test.txs[0].Nonce()) err = pool.addTx(local, test.txs[0]) assert.NoError(t, err) @@ -1751,7 +1751,7 @@ func TestResetAccount(t *testing.T) { // setup prestate acc := pool.getOrCreateAccount(addr1) - acc.setNonce(test.txs[0].Nonce) + acc.setNonce(test.txs[0].Nonce()) err = pool.addTx(local, test.txs[0]) assert.NoError(t, err) @@ -1818,7 +1818,7 @@ func TestPop(t *testing.T) { acc := pool.accounts.get(addr1) assert.Equal(t, int(1), len(acc.nonceToTx.mapping)) - assert.Equal(t, tx1, acc.nonceToTx.get(tx1.Nonce)) + assert.Equal(t, tx1, acc.nonceToTx.get(tx1.Nonce())) pool.handlePromoteRequest(<-pool.promoteReqCh) @@ -1836,7 +1836,7 @@ func TestPop(t *testing.T) { assert.Equal(t, uint64(0), pool.accounts.get(addr1).promoted.length()) assert.Equal(t, int(0), len(acc.nonceToTx.mapping)) - assert.Equal(t, (*types.Transaction)(nil), acc.nonceToTx.get(tx1.Nonce)) + assert.Equal(t, (*types.Transaction)(nil), acc.nonceToTx.get(tx1.Nonce())) } func TestDrop(t *testing.T) { @@ -1870,7 +1870,7 @@ func TestDrop(t *testing.T) { assert.Equal(t, uint64(0), pool.accounts.get(addr1).promoted.length()) assert.Equal(t, int(0), len(acc.nonceToTx.mapping)) - assert.Equal(t, (*types.Transaction)(nil), acc.nonceToTx.get(tx1.Nonce)) + assert.Equal(t, (*types.Transaction)(nil), acc.nonceToTx.get(tx1.Nonce())) } func TestDemote(t *testing.T) { @@ -1988,7 +1988,7 @@ func Test_updateAccountSkipsCounts(t *testing.T) { assert.Equal(t, uint64(1), accountMap.promoted.length()) assert.Zero(t, accountMap.skips) assert.Equal(t, slotsRequired(tx), pool.gauge.read()) - checkTxExistence(t, pool, tx.Hash, true) + checkTxExistence(t, pool, tx.Hash(), true) // set 9 to skips in order to drop transaction next accountMap.skips = 9 @@ -2002,7 +2002,7 @@ func Test_updateAccountSkipsCounts(t *testing.T) { assert.Zero(t, accountMap.promoted.length()) assert.Zero(t, accountMap.skips) assert.Zero(t, pool.gauge.read()) - checkTxExistence(t, pool, tx.Hash, false) + checkTxExistence(t, pool, tx.Hash(), false) }) t.Run("should drop the first transaction from enqueued queue", func(t *testing.T) { @@ -2023,7 +2023,7 @@ func Test_updateAccountSkipsCounts(t *testing.T) { assert.Zero(t, accountMap.promoted.length()) assert.Zero(t, accountMap.skips) assert.Equal(t, slotsRequired(tx), pool.gauge.read()) - checkTxExistence(t, pool, tx.Hash, true) + checkTxExistence(t, pool, tx.Hash(), true) // set 9 to skips in order to drop transaction next accountMap.skips = 9 @@ -2037,7 +2037,7 @@ func Test_updateAccountSkipsCounts(t *testing.T) { assert.Zero(t, accountMap.promoted.length()) assert.Zero(t, accountMap.skips) assert.Zero(t, pool.gauge.read()) - checkTxExistence(t, pool, tx.Hash, false) + checkTxExistence(t, pool, tx.Hash(), false) }) t.Run("should not drop a transaction", func(t *testing.T) { @@ -2058,7 +2058,7 @@ func Test_updateAccountSkipsCounts(t *testing.T) { assert.Equal(t, uint64(1), accountMap.promoted.length()) assert.Zero(t, accountMap.skips) assert.Equal(t, slotsRequired(tx), pool.gauge.read()) - checkTxExistence(t, pool, tx.Hash, true) + checkTxExistence(t, pool, tx.Hash(), true) // set 9 to skips in order to drop transaction next accountMap.skips = 5 @@ -2072,7 +2072,7 @@ func Test_updateAccountSkipsCounts(t *testing.T) { assert.Equal(t, uint64(1), accountMap.promoted.length()) assert.Equal(t, uint64(0), accountMap.skips) assert.Equal(t, slotsRequired(tx), pool.gauge.read()) - checkTxExistence(t, pool, tx.Hash, true) + checkTxExistence(t, pool, tx.Hash(), true) }) t.Run("drop should set nonce to current account nonce from store", func(t *testing.T) { @@ -2093,20 +2093,28 @@ func Test_updateAccountSkipsCounts(t *testing.T) { accountMap := pool.accounts.initOnce(addr1, storeNonce) accountMap.enqueued.push(&types.Transaction{ - Nonce: storeNonce + 2, - Hash: types.StringToHash("0xffa"), + Inner: &types.MixedTxn{ + Nonce: storeNonce + 2, + Hash: types.StringToHash("0xffa"), + }, }) accountMap.enqueued.push(&types.Transaction{ - Nonce: storeNonce + 4, - Hash: types.StringToHash("0xff1"), + Inner: &types.MixedTxn{ + Nonce: storeNonce + 4, + Hash: types.StringToHash("0xff1"), + }, }) accountMap.promoted.push(&types.Transaction{ - Nonce: storeNonce, - Hash: types.StringToHash("0xff2"), + Inner: &types.MixedTxn{ + Nonce: storeNonce, + Hash: types.StringToHash("0xff2"), + }, }) accountMap.promoted.push(&types.Transaction{ - Nonce: storeNonce + 1, - Hash: types.StringToHash("0xff3"), + Inner: &types.MixedTxn{ + Nonce: storeNonce + 1, + Hash: types.StringToHash("0xff3"), + }, }) accountMap.setNonce(storeNonce + 3) accountMap.skips = maxAccountSkips - 1 @@ -2162,8 +2170,8 @@ func Test_TxPool_validateTx(t *testing.T) { require.NoError(t, err) tx := newTx(defaultAddr, 0, 1) - tx.To = nil - tx.Input = input + tx.SetTo(nil) + tx.SetInput(input) assert.ErrorIs(t, pool.validateTx(signTx(tx)), @@ -2181,9 +2189,9 @@ func Test_TxPool_validateTx(t *testing.T) { require.NoError(t, err) tx := newTx(defaultAddr, 0, 1) - tx.To = nil - tx.Input = input - tx.GasPrice = new(big.Int).SetUint64(pool.GetBaseFee()) + tx.SetTo(nil) + tx.SetInput(input) + tx.SetGasPrice(new(big.Int).SetUint64(pool.GetBaseFee())) assert.NoError(t, pool.validateTx(signTx(tx)), @@ -2197,9 +2205,9 @@ func Test_TxPool_validateTx(t *testing.T) { pool := setupPool() tx := newTx(defaultAddr, 0, 1) - tx.Type = types.DynamicFeeTx - tx.GasFeeCap = big.NewInt(1100) - tx.GasTipCap = big.NewInt(10) + tx.SetTransactionType(types.DynamicFeeTx) + tx.SetGasFeeCap(big.NewInt(1100)) + tx.SetGasTipCap(big.NewInt(10)) assert.NoError(t, pool.validateTx(signTx(tx))) }) @@ -2210,9 +2218,9 @@ func Test_TxPool_validateTx(t *testing.T) { pool := setupPool() tx := newTx(defaultAddr, 0, 1) - tx.Type = types.DynamicFeeTx - tx.GasFeeCap = big.NewInt(100) - tx.GasTipCap = big.NewInt(10) + tx.SetTransactionType(types.DynamicFeeTx) + tx.SetGasFeeCap(big.NewInt(100)) + tx.SetGasTipCap(big.NewInt(10)) assert.ErrorIs(t, pool.validateTx(signTx(tx)), @@ -2226,9 +2234,9 @@ func Test_TxPool_validateTx(t *testing.T) { pool := setupPool() tx := newTx(defaultAddr, 0, 1) - tx.Type = types.DynamicFeeTx - tx.GasFeeCap = big.NewInt(10000) - tx.GasTipCap = big.NewInt(100000) + tx.SetTransactionType(types.DynamicFeeTx) + tx.SetGasFeeCap(big.NewInt(10000)) + tx.SetGasTipCap(big.NewInt(100000)) assert.ErrorIs(t, pool.validateTx(signTx(tx)), @@ -2243,11 +2251,11 @@ func Test_TxPool_validateTx(t *testing.T) { // undefined gas tip cap tx := newTx(defaultAddr, 0, 1) - tx.Type = types.DynamicFeeTx - tx.GasFeeCap = big.NewInt(10000) + tx.SetTransactionType(types.DynamicFeeTx) + tx.SetGasFeeCap(big.NewInt(10000)) signedTx := signTx(tx) - signedTx.GasTipCap = nil + signedTx.SetGasTipCap(nil) assert.ErrorIs(t, pool.validateTx(signedTx), @@ -2256,10 +2264,11 @@ func Test_TxPool_validateTx(t *testing.T) { // undefined gas fee cap tx = newTx(defaultAddr, 1, 1) - tx.Type = types.DynamicFeeTx - tx.GasTipCap = big.NewInt(1000) + tx.SetTransactionType(types.DynamicFeeTx) + tx.SetGasTipCap(big.NewInt(1000)) + signedTx = signTx(tx) - signedTx.GasFeeCap = nil + signedTx.SetGasFeeCap(nil) assert.ErrorIs(t, pool.validateTx(signedTx), @@ -2276,8 +2285,9 @@ func Test_TxPool_validateTx(t *testing.T) { // very high gas fee cap tx := newTx(defaultAddr, 0, 1) - tx.Type = types.DynamicFeeTx - tx.GasFeeCap = new(big.Int).SetBit(new(big.Int), bitLength, 1) + tx.SetTransactionType(types.DynamicFeeTx) + tx.SetGasFeeCap(new(big.Int).SetBit(new(big.Int), bitLength, 1)) + tx.SetGasTipCap(new(big.Int)) assert.ErrorIs(t, pool.validateTx(signTx(tx)), @@ -2286,8 +2296,9 @@ func Test_TxPool_validateTx(t *testing.T) { // very high gas tip cap tx = newTx(defaultAddr, 1, 1) - tx.Type = types.DynamicFeeTx - tx.GasTipCap = new(big.Int).SetBit(new(big.Int), bitLength, 1) + tx.SetTransactionType(types.DynamicFeeTx) + tx.SetGasTipCap(new(big.Int).SetBit(new(big.Int), bitLength, 1)) + tx.SetGasFeeCap(new(big.Int)) assert.ErrorIs(t, pool.validateTx(signTx(tx)), @@ -2302,9 +2313,9 @@ func Test_TxPool_validateTx(t *testing.T) { pool.forks.RemoveFork(chain.London) tx := newTx(defaultAddr, 0, 1) - tx.Type = types.DynamicFeeTx - tx.GasFeeCap = big.NewInt(10000) - tx.GasTipCap = big.NewInt(100000) + tx.SetTransactionType(types.DynamicFeeTx) + tx.SetGasFeeCap(big.NewInt(10000)) + tx.SetGasTipCap(big.NewInt(100000)) assert.ErrorIs(t, pool.validateTx(signTx(tx)), @@ -2718,16 +2729,16 @@ func TestExecutablesOrder(t *testing.T) { newPricedTx := func( addr types.Address, nonce, gasPrice uint64, gasFeeCap uint64, value uint64) *types.Transaction { tx := newTx(addr, nonce, 1) - tx.Value = new(big.Int).SetUint64(value) + tx.SetValue(new(big.Int).SetUint64(value)) if gasPrice == 0 { - tx.Type = types.DynamicFeeTx - tx.GasFeeCap = new(big.Int).SetUint64(gasFeeCap) - tx.GasTipCap = new(big.Int).SetUint64(2) - tx.GasPrice = big.NewInt(0) + tx.SetTransactionType(types.DynamicFeeTx) + tx.SetGasFeeCap(new(big.Int).SetUint64(gasFeeCap)) + tx.SetGasTipCap(new(big.Int).SetUint64(2)) + tx.SetGasPrice(big.NewInt(0)) } else { - tx.Type = types.LegacyTx - tx.GasPrice = new(big.Int).SetUint64(gasPrice) + tx.SetTransactionType(types.LegacyTx) + tx.SetGasPrice(new(big.Int).SetUint64(gasPrice)) } return tx @@ -2904,8 +2915,8 @@ func TestExecutablesOrder(t *testing.T) { // verify the highest priced transactions // were processed first for i, tx := range successful { - require.Equal(t, test.expectedPriceOrder[i][0], tx.GasPrice.Uint64()) - require.Equal(t, test.expectedPriceOrder[i][1], tx.Value.Uint64()) + require.Equal(t, test.expectedPriceOrder[i][0], tx.GasPrice().Uint64()) + require.Equal(t, test.expectedPriceOrder[i][1], tx.Value().Uint64()) } }) } @@ -3043,9 +3054,9 @@ func TestRecovery(t *testing.T) { // helper callback for transition errors status := func(tx *types.Transaction) (s status) { - txs := test.allTxs[tx.From] + txs := test.allTxs[tx.From()] for _, sTx := range txs { - if tx.Nonce == sTx.tx.Nonce { + if tx.Nonce() == sTx.tx.Nonce() { s = sTx.status } } @@ -3071,7 +3082,7 @@ func TestRecovery(t *testing.T) { for addr, txs := range test.allTxs { // preset nonce so promotions can happen acc := pool.getOrCreateAccount(addr) - acc.setNonce(txs[0].tx.Nonce) + acc.setNonce(txs[0].tx.Nonce()) expectedEnqueued += test.expected.accounts[addr].enqueued @@ -3258,8 +3269,8 @@ func TestGetTxs(t *testing.T) { tx *types.Transaction, all map[types.Address][]*types.Transaction, ) bool { - for _, txx := range all[tx.From] { - if tx.Nonce == txx.Nonce { + for _, txx := range all[tx.From()] { + if tx.Nonce() == txx.Nonce() { return true } } @@ -3293,7 +3304,7 @@ func TestGetTxs(t *testing.T) { promotable := uint64(0) for _, tx := range txs { // send all txs - if tx.Nonce == nonce+promotable { + if tx.Nonce() == nonce+promotable { promotable++ } @@ -3433,7 +3444,7 @@ func TestBatchTx_SingleAccount(t *testing.T) { // add transaction hash to map mux.Lock() - txHashMap[tx.Hash] = struct{}{} + txHashMap[tx.Hash()] = struct{}{} mux.Unlock() // submit transaction to pool @@ -3451,10 +3462,10 @@ func TestBatchTx_SingleAccount(t *testing.T) { for { select { case ev = <-subscription.subscriptionChannel: - case <-time.After(time.Second * 10): - t.Fatal(fmt.Sprintf("timeout. processed: %d/%d and %d/%d. Added: %d", + case <-time.After(time.Second * 3): + t.Fatalf("timeout. processed: %d/%d and %d/%d. Added: %d", enqueuedCount, defaultMaxAccountEnqueued, promotedCount, defaultMaxAccountEnqueued, - atomic.LoadUint64(&counter))) + atomic.LoadUint64(&counter)) } // check if valid transaction hash @@ -3633,11 +3644,11 @@ func TestAddTx_TxReplacement(t *testing.T) { t.Helper() tx := newTx(addr, nonce, 1) - tx.Type = types.DynamicFeeTx - tx.Input = nil - tx.GasPrice = nil - tx.GasTipCap = new(big.Int).SetUint64(gasTipCap) - tx.GasFeeCap = new(big.Int).SetUint64(gasFeeCap) + tx.SetTransactionType(types.DynamicFeeTx) + tx.SetInput(nil) + tx.SetGasPrice(nil) + tx.SetGasTipCap(new(big.Int).SetUint64(gasTipCap)) + tx.SetGasFeeCap(new(big.Int).SetUint64(gasFeeCap)) singedTx, err := poolSigner.SignTx(tx, key) require.NoError(t, err) @@ -3653,8 +3664,8 @@ func TestAddTx_TxReplacement(t *testing.T) { t.Helper() tx := newTx(addr, nonce, 1) - tx.Input = nil - tx.GasPrice = new(big.Int).SetUint64(gasPrice) + tx.SetInput(nil) + tx.SetGasPrice(new(big.Int).SetUint64(gasPrice)) singedTx, err := poolSigner.SignTx(tx, key) require.NoError(t, err) @@ -3688,9 +3699,9 @@ func TestAddTx_TxReplacement(t *testing.T) { // ErrAlreadyKnown tx1 := createLegacyTx(t, - firstAccountNonce, txLegacy.GasPrice.Uint64(), firstKey, firstKeyAddr) + firstAccountNonce, txLegacy.GasPrice().Uint64(), firstKey, firstKeyAddr) tx2 := createDynamicTx(t, - secondAccountNonce, txDynamic.GasFeeCap.Uint64(), txDynamic.GasTipCap.Uint64(), secondKey, secondKeyAddr) + secondAccountNonce, txDynamic.GasFeeCap().Uint64(), txDynamic.GasTipCap().Uint64(), secondKey, secondKeyAddr) assert.ErrorIs(t, pool.addTx(local, tx1), ErrAlreadyKnown) assert.ErrorIs(t, pool.addTx(local, tx2), ErrAlreadyKnown) diff --git a/types/access_list_tx.go b/types/access_list_tx.go new file mode 100644 index 0000000000..f4c8687074 --- /dev/null +++ b/types/access_list_tx.go @@ -0,0 +1,135 @@ +package types + +import "math/big" + +type TxAccessList []AccessTuple + +type AccessTuple struct { + Address Address + StorageKeys []Hash +} + +// StorageKeys returns the total number of storage keys in the access list. +func (al TxAccessList) StorageKeys() int { + sum := 0 + for _, tuple := range al { + sum += len(tuple.StorageKeys) + } + + return sum +} + +// Copy makes a deep copy of the access list. +func (al TxAccessList) Copy() TxAccessList { + if al == nil { + return nil + } + + newAccessList := make(TxAccessList, len(al)) + + for i, item := range al { + var copiedAddress Address + + copy(copiedAddress[:], item.Address[:]) + newAccessList[i] = AccessTuple{ + Address: copiedAddress, + StorageKeys: append([]Hash{}, item.StorageKeys...), + } + } + + return newAccessList +} + +type AccessListTxn struct { + Nonce uint64 + GasPrice *big.Int + Gas uint64 + To *Address + Value *big.Int + Input []byte + V, R, S *big.Int + Hash Hash + From Address + + ChainID *big.Int + AccessList TxAccessList +} + +func (tx *AccessListTxn) transactionType() TxType { return AccessListTx } +func (tx *AccessListTxn) chainID() *big.Int { return tx.ChainID } +func (tx *AccessListTxn) input() []byte { return tx.Input } +func (tx *AccessListTxn) gas() uint64 { return tx.Gas } +func (tx *AccessListTxn) gasPrice() *big.Int { return tx.GasPrice } +func (tx *AccessListTxn) gasTipCap() *big.Int { return tx.GasPrice } +func (tx *AccessListTxn) gasFeeCap() *big.Int { return tx.GasPrice } +func (tx *AccessListTxn) value() *big.Int { return tx.Value } +func (tx *AccessListTxn) nonce() uint64 { return tx.Nonce } +func (tx *AccessListTxn) to() *Address { return tx.To } +func (tx *AccessListTxn) from() Address { return tx.From } + +func (tx *AccessListTxn) hash() Hash { return tx.Hash } + +func (tx *AccessListTxn) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *AccessListTxn) accessList() TxAccessList { + return tx.AccessList +} + +// set methods for transaction fields +func (tx *AccessListTxn) setSignatureValues(v, r, s *big.Int) { + tx.V, tx.R, tx.S = v, r, s +} + +func (tx *AccessListTxn) setFrom(addr Address) { + tx.From = addr +} + +func (tx *AccessListTxn) setGas(gas uint64) { + tx.Gas = gas +} + +func (tx *AccessListTxn) setChainID(id *big.Int) { + tx.ChainID = id +} + +func (tx *AccessListTxn) setGasPrice(gas *big.Int) { + tx.GasPrice = gas +} + +func (tx *AccessListTxn) setGasFeeCap(gas *big.Int) { + tx.GasPrice = gas +} + +func (tx *AccessListTxn) setGasTipCap(gas *big.Int) { + tx.GasPrice = gas +} + +func (tx *AccessListTxn) setTransactionType(t TxType) { + // no need to set a transaction type for access list type of transaction +} + +func (tx *AccessListTxn) setValue(value *big.Int) { + tx.Value = value +} + +func (tx *AccessListTxn) setInput(input []byte) { + tx.Input = input +} + +func (tx *AccessListTxn) setTo(addeess *Address) { + tx.To = addeess +} + +func (tx *AccessListTxn) setNonce(nonce uint64) { + tx.Nonce = nonce +} + +func (tx *AccessListTxn) setAccessList(accessList TxAccessList) { + tx.AccessList = accessList +} + +func (tx *AccessListTxn) setHash(h Hash) { + tx.Hash = h +} diff --git a/types/mixed_tx.go b/types/mixed_tx.go new file mode 100644 index 0000000000..da4094d159 --- /dev/null +++ b/types/mixed_tx.go @@ -0,0 +1,105 @@ +package types + +import "math/big" + +type MixedTxn struct { + Nonce uint64 + GasPrice *big.Int + GasTipCap *big.Int + GasFeeCap *big.Int + Gas uint64 + To *Address + Value *big.Int + Input []byte + V, R, S *big.Int + Hash Hash + From Address + + Type TxType + + ChainID *big.Int + AccessList TxAccessList +} + +func (tx *MixedTxn) transactionType() TxType { return tx.Type } +func (tx *MixedTxn) chainID() *big.Int { return tx.ChainID } +func (tx *MixedTxn) input() []byte { return tx.Input } +func (tx *MixedTxn) gas() uint64 { return tx.Gas } +func (tx *MixedTxn) gasPrice() *big.Int { return tx.GasPrice } +func (tx *MixedTxn) gasTipCap() *big.Int { return tx.GasTipCap } +func (tx *MixedTxn) gasFeeCap() *big.Int { return tx.GasFeeCap } +func (tx *MixedTxn) value() *big.Int { return tx.Value } +func (tx *MixedTxn) nonce() uint64 { return tx.Nonce } +func (tx *MixedTxn) to() *Address { return tx.To } +func (tx *MixedTxn) from() Address { return tx.From } + +func (tx *MixedTxn) hash() Hash { return tx.Hash } + +func (tx *MixedTxn) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *MixedTxn) accessList() TxAccessList { + if tx.transactionType() == DynamicFeeTx { + return tx.AccessList + } + + return nil +} + +// set methods for transaction fields +func (tx *MixedTxn) setSignatureValues(v, r, s *big.Int) { + tx.V, tx.R, tx.S = v, r, s +} + +func (tx *MixedTxn) setFrom(addr Address) { + tx.From = addr +} + +func (tx *MixedTxn) setGas(gas uint64) { + tx.Gas = gas +} + +func (tx *MixedTxn) setChainID(id *big.Int) { + tx.ChainID = id +} + +func (tx *MixedTxn) setGasPrice(gas *big.Int) { + tx.GasPrice = gas +} + +func (tx *MixedTxn) setGasFeeCap(gas *big.Int) { + tx.GasFeeCap = gas +} + +func (tx *MixedTxn) setGasTipCap(gas *big.Int) { + tx.GasTipCap = gas +} + +func (tx *MixedTxn) setTransactionType(t TxType) { + tx.Type = t +} + +func (tx *MixedTxn) setValue(value *big.Int) { + tx.Value = value +} + +func (tx *MixedTxn) setInput(input []byte) { + tx.Input = input +} + +func (tx *MixedTxn) setTo(addeess *Address) { + tx.To = addeess +} + +func (tx *MixedTxn) setNonce(nonce uint64) { + tx.Nonce = nonce +} + +func (tx *MixedTxn) setAccessList(accessList TxAccessList) { + tx.AccessList = accessList +} + +func (tx *MixedTxn) setHash(h Hash) { + tx.Hash = h +} diff --git a/types/rlp_encoding_test.go b/types/rlp_encoding_test.go index f9be7b9304..1f5c1ae3f5 100644 --- a/types/rlp_encoding_test.go +++ b/types/rlp_encoding_test.go @@ -43,7 +43,7 @@ func TestRLPEncoding(t *testing.T) { func TestRLPMarshall_And_Unmarshall_Transaction(t *testing.T) { addrTo := StringToAddress("11") - txn := &Transaction{ + txn := NewTx(&MixedTxn{ Nonce: 0, GasPrice: big.NewInt(11), Gas: 11, @@ -53,11 +53,11 @@ func TestRLPMarshall_And_Unmarshall_Transaction(t *testing.T) { V: big.NewInt(25), S: big.NewInt(26), R: big.NewInt(27), - } + }) txn.ComputeHash() - unmarshalledTxn := new(Transaction) + unmarshalledTxn := NewTx(&MixedTxn{}) marshaledRlp := txn.MarshalRLP() if err := unmarshalledTxn.UnmarshalRLP(marshaledRlp); err != nil { @@ -66,7 +66,7 @@ func TestRLPMarshall_And_Unmarshall_Transaction(t *testing.T) { unmarshalledTxn.ComputeHash() - assert.Equal(t, txn, unmarshalledTxn, "[ERROR] Unmarshalled transaction not equal to base transaction") + assert.Equal(t, txn.Inner, unmarshalledTxn.Inner, "[ERROR] Unmarshalled transaction not equal to base transaction") } func TestRLPStorage_Marshall_And_Unmarshall_Receipt(t *testing.T) { @@ -152,7 +152,7 @@ func TestRLPUnmarshal_Header_ComputeHash(t *testing.T) { func TestRLPMarshall_And_Unmarshall_TypedTransaction(t *testing.T) { addrTo := StringToAddress("11") addrFrom := StringToAddress("22") - originalTx := &Transaction{ + originalTx := NewTx(&MixedTxn{ Nonce: 0, GasPrice: big.NewInt(11), GasFeeCap: big.NewInt(12), @@ -165,7 +165,7 @@ func TestRLPMarshall_And_Unmarshall_TypedTransaction(t *testing.T) { V: big.NewInt(25), S: big.NewInt(26), R: big.NewInt(27), - } + }) txTypes := []TxType{ StateTx, @@ -175,17 +175,17 @@ func TestRLPMarshall_And_Unmarshall_TypedTransaction(t *testing.T) { for _, v := range txTypes { t.Run(v.String(), func(t *testing.T) { - originalTx.Type = v + originalTx.SetTransactionType(v) originalTx.ComputeHash() txRLP := originalTx.MarshalRLP() - unmarshalledTx := new(Transaction) + unmarshalledTx := NewTx(&MixedTxn{}) assert.NoError(t, unmarshalledTx.UnmarshalRLP(txRLP)) unmarshalledTx.ComputeHash() - assert.Equal(t, originalTx.Type, unmarshalledTx.Type) - assert.Equal(t, originalTx.Hash, unmarshalledTx.Hash) + assert.Equal(t, originalTx.Type(), unmarshalledTx.Type()) + assert.Equal(t, originalTx.Hash(), unmarshalledTx.Hash()) }) } } @@ -255,13 +255,15 @@ func TestRLPMarshall_Unmarshall_Missing_Data(t *testing.T) { v, err := parser.Parse(testData) assert.Nil(t, err) - unmarshalledTx := &Transaction{Type: txType} + unmarshalledTx := NewTx(&MixedTxn{ + Type: txType, + }) if tt.expectedErr { assert.Error(t, unmarshalledTx.unmarshalRLPFrom(parser, v), tt.name) } else { assert.NoError(t, unmarshalledTx.unmarshalRLPFrom(parser, v), tt.name) - assert.Equal(t, tt.fromAddrSet, len(unmarshalledTx.From) != 0 && unmarshalledTx.From != ZeroAddress, unmarshalledTx.Type.String(), unmarshalledTx.From) + assert.Equal(t, tt.fromAddrSet, len(unmarshalledTx.From()) != 0 && unmarshalledTx.From() != ZeroAddress, unmarshalledTx.Type().String(), unmarshalledTx.From()) } fastrlp.DefaultParserPool.Put(parser) @@ -314,59 +316,59 @@ func TestRLPMarshall_And_Unmarshall_TxType(t *testing.T) { func testRLPData(arena *fastrlp.Arena, omitValues map[string]bool) []byte { vv := arena.NewArray() - if omit, _ := omitValues["ChainID"]; !omit { + if omit := omitValues["ChainID"]; !omit { vv.Set(arena.NewBigInt(big.NewInt(0))) } - if omit, _ := omitValues["Nonce"]; !omit { + if omit := omitValues["Nonce"]; !omit { vv.Set(arena.NewUint(10)) } - if omit, _ := omitValues["GasTipCap"]; !omit { + if omit := omitValues["GasTipCap"]; !omit { vv.Set(arena.NewBigInt(big.NewInt(11))) } - if omit, _ := omitValues["GasFeeCap"]; !omit { + if omit := omitValues["GasFeeCap"]; !omit { vv.Set(arena.NewBigInt(big.NewInt(11))) } - if omit, _ := omitValues["GasPrice"]; !omit { + if omit := omitValues["GasPrice"]; !omit { vv.Set(arena.NewBigInt(big.NewInt(11))) } - if omit, _ := omitValues["Gas"]; !omit { + if omit := omitValues["Gas"]; !omit { vv.Set(arena.NewUint(12)) } - if omit, _ := omitValues["To"]; !omit { + if omit := omitValues["To"]; !omit { vv.Set(arena.NewBytes((StringToAddress("13")).Bytes())) } - if omit, _ := omitValues["Value"]; !omit { + if omit := omitValues["Value"]; !omit { vv.Set(arena.NewBigInt(big.NewInt(14))) } - if omit, _ := omitValues["Input"]; !omit { + if omit := omitValues["Input"]; !omit { vv.Set(arena.NewCopyBytes([]byte{1, 2})) } - if omit, _ := omitValues["AccessList"]; !omit { + if omit := omitValues["AccessList"]; !omit { vv.Set(arena.NewArray()) } - if omit, _ := omitValues["V"]; !omit { + if omit := omitValues["V"]; !omit { vv.Set(arena.NewBigInt(big.NewInt(15))) } - if omit, _ := omitValues["R"]; !omit { + if omit := omitValues["R"]; !omit { vv.Set(arena.NewBigInt(big.NewInt(16))) } - if omit, _ := omitValues["S"]; !omit { + if omit := omitValues["S"]; !omit { vv.Set(arena.NewBigInt(big.NewInt(17))) } - if omit, _ := omitValues["From"]; !omit { + if omit := omitValues["From"]; !omit { vv.Set(arena.NewBytes((StringToAddress("18")).Bytes())) } diff --git a/types/rlp_marshal.go b/types/rlp_marshal.go index 6bc9893f74..7cb8b143b2 100644 --- a/types/rlp_marshal.go +++ b/types/rlp_marshal.go @@ -40,8 +40,8 @@ func (b *Block) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { v0 := ar.NewArray() for _, tx := range b.Transactions { - if tx.Type != LegacyTx { - v0.Set(ar.NewCopyBytes([]byte{byte(tx.Type)})) + if tx.Type() != LegacyTx { + v0.Set(ar.NewCopyBytes([]byte{byte(tx.Type())})) } v0.Set(tx.MarshalRLPWith(ar)) @@ -183,8 +183,8 @@ func (t *Transaction) MarshalRLP() []byte { } func (t *Transaction) MarshalRLPTo(dst []byte) []byte { - if t.Type != LegacyTx { - dst = append(dst, byte(t.Type)) + if t.Type() != LegacyTx { + dst = append(dst, byte(t.Type())) } return MarshalRLPTo(t.MarshalRLPWith, dst) @@ -196,49 +196,104 @@ func (t *Transaction) MarshalRLPTo(dst []byte) []byte { func (t *Transaction) MarshalRLPWith(arena *fastrlp.Arena) *fastrlp.Value { vv := arena.NewArray() - // Check Transaction1559Payload there https://eips.ethereum.org/EIPS/eip-1559#specification - if t.Type == DynamicFeeTx { - vv.Set(arena.NewBigInt(t.ChainID)) - } + switch t.Inner.(type) { + case *MixedTxn: + // Check Transaction1559Payload there https://eips.ethereum.org/EIPS/eip-1559#specification + if t.Type() == DynamicFeeTx { + vv.Set(arena.NewBigInt(t.ChainID())) + } - vv.Set(arena.NewUint(t.Nonce)) + vv.Set(arena.NewUint(t.Nonce())) - if t.Type == DynamicFeeTx { - // Add EIP-1559 related fields. - // For non-dynamic-fee-tx gas price is used. - vv.Set(arena.NewBigInt(t.GasTipCap)) - vv.Set(arena.NewBigInt(t.GasFeeCap)) - } else { - vv.Set(arena.NewBigInt(t.GasPrice)) - } + if t.Type() == DynamicFeeTx { + // Add EIP-1559 related fields. + // For non-dynamic-fee-tx gas price is used. + vv.Set(arena.NewBigInt(t.GasTipCap())) + vv.Set(arena.NewBigInt(t.GasFeeCap())) + } else { + vv.Set(arena.NewBigInt(t.GasPrice())) + } - vv.Set(arena.NewUint(t.Gas)) + vv.Set(arena.NewUint(t.Gas())) - // Address may be empty - if t.To != nil { - vv.Set(arena.NewCopyBytes(t.To.Bytes())) - } else { - vv.Set(arena.NewNull()) - } + // Address may be empty + if t.To() != nil { + vv.Set(arena.NewCopyBytes(t.To().Bytes())) + } else { + vv.Set(arena.NewNull()) + } - vv.Set(arena.NewBigInt(t.Value)) - vv.Set(arena.NewCopyBytes(t.Input)) + vv.Set(arena.NewBigInt(t.Value())) + vv.Set(arena.NewCopyBytes(t.Input())) - // Specify access list as per spec. - // This is needed to have the same format as other EVM chains do. - // There is no access list feature here, so it is always empty just to be compatible. - // Check Transaction1559Payload there https://eips.ethereum.org/EIPS/eip-1559#specification - if t.Type == DynamicFeeTx { - vv.Set(arena.NewArray()) - } + // Specify access list as per spec. + if t.Type() == DynamicFeeTx { + // Convert TxAccessList to RLP format and add it to the vv array. + accessListVV := arena.NewArray() + + for _, accessTuple := range t.AccessList() { + accessTupleVV := arena.NewArray() + accessTupleVV.Set(arena.NewCopyBytes(accessTuple.Address.Bytes())) + + storageKeysVV := arena.NewArray() + for _, storageKey := range accessTuple.StorageKeys { + storageKeysVV.Set(arena.NewCopyBytes(storageKey.Bytes())) + } + + accessTupleVV.Set(storageKeysVV) + accessListVV.Set(accessTupleVV) + } + + vv.Set(accessListVV) + } + + // signature values + v, r, s := t.RawSignatureValues() + vv.Set(arena.NewBigInt(v)) + vv.Set(arena.NewBigInt(r)) + vv.Set(arena.NewBigInt(s)) + + if t.Type() == StateTx { + vv.Set(arena.NewCopyBytes(t.From().Bytes())) + } + case *AccessListTxn: + vv.Set(arena.NewBigInt(t.ChainID())) + vv.Set(arena.NewUint(t.Nonce())) + vv.Set(arena.NewBigInt(t.GasPrice())) + vv.Set(arena.NewUint(t.Gas())) + + // Address may be empty + if t.To() != nil { + vv.Set(arena.NewCopyBytes(t.To().Bytes())) + } else { + vv.Set(arena.NewNull()) + } + + vv.Set(arena.NewBigInt(t.Value())) + vv.Set(arena.NewCopyBytes(t.Input())) + + // add accessList + accessListVV := arena.NewArray() + + for _, accessTuple := range t.AccessList() { + accessTupleVV := arena.NewArray() + accessTupleVV.Set(arena.NewCopyBytes(accessTuple.Address.Bytes())) + + storageKeysVV := arena.NewArray() + for _, storageKey := range accessTuple.StorageKeys { + storageKeysVV.Set(arena.NewCopyBytes(storageKey.Bytes())) + } + + accessTupleVV.Set(storageKeysVV) + accessListVV.Set(accessTupleVV) + } - // signature values - vv.Set(arena.NewBigInt(t.V)) - vv.Set(arena.NewBigInt(t.R)) - vv.Set(arena.NewBigInt(t.S)) + vv.Set(accessListVV) - if t.Type == StateTx { - vv.Set(arena.NewCopyBytes(t.From.Bytes())) + v, r, s := t.RawSignatureValues() + vv.Set(arena.NewBigInt(v)) + vv.Set(arena.NewBigInt(r)) + vv.Set(arena.NewBigInt(s)) } return vv diff --git a/types/rlp_marshal_storage.go b/types/rlp_marshal_storage.go index dcef5edaa3..82cfbafe86 100644 --- a/types/rlp_marshal_storage.go +++ b/types/rlp_marshal_storage.go @@ -44,14 +44,14 @@ func (t *Transaction) MarshalStoreRLPTo(dst []byte) []byte { func (t *Transaction) marshalStoreRLPWith(a *fastrlp.Arena) *fastrlp.Value { vv := a.NewArray() - if t.Type != LegacyTx { - vv.Set(a.NewBytes([]byte{byte(t.Type)})) + if t.Type() != LegacyTx { + vv.Set(a.NewBytes([]byte{byte(t.Type())})) } // consensus part vv.Set(t.MarshalRLPWith(a)) // context part - vv.Set(a.NewBytes(t.From.Bytes())) + vv.Set(a.NewBytes(t.From().Bytes())) return vv } diff --git a/types/rlp_unmarshal.go b/types/rlp_unmarshal.go index 10ea749a0a..981d789c7f 100644 --- a/types/rlp_unmarshal.go +++ b/types/rlp_unmarshal.go @@ -105,8 +105,14 @@ func (b *Block) unmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { // transactions if err = unmarshalRLPFrom(p, elems[1], func(txType TxType, p *fastrlp.Parser, v *fastrlp.Value) error { - bTxn := &Transaction{ - Type: txType, + var bTxn *Transaction + switch txType { + case AccessListTx: + bTxn = NewTx(&AccessListTxn{}) + case DynamicFeeTx, LegacyTx, StateTx: + bTxn = NewTx(&MixedTxn{ + Type: txType, + }) } if err = bTxn.unmarshalRLPFrom(p, v); err != nil { @@ -364,18 +370,22 @@ func (l *Log) unmarshalRLPFrom(_ *fastrlp.Parser, v *fastrlp.Value) error { // UnmarshalRLP unmarshals transaction from byte slice // Caution: Hash calculation should be done from the outside! func (t *Transaction) UnmarshalRLP(input []byte) error { - t.Type = LegacyTx + txType := LegacyTx offset := 0 if len(input) > 0 && input[0] <= RLPSingleByteUpperLimit { - var err error - if t.Type, err = txTypeFromByte(input[0]); err != nil { + tType, err := txTypeFromByte(input[0]) + if err != nil { return err } + txType = tType + offset = 1 } + t.InitInnerData(txType) + if err := UnmarshalRlp(t.unmarshalRLPFrom, input[offset:]); err != nil { return err } @@ -402,114 +412,313 @@ func (t *Transaction) unmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) erro var num int - switch t.Type { + switch t.Type() { case LegacyTx: num = 9 case StateTx: num = 10 case DynamicFeeTx: num = 12 + case AccessListTx: + num = 11 default: - return fmt.Errorf("transaction type %d not found", t.Type) + return fmt.Errorf("transaction type %d not found", t.Type()) } if numElems := len(elems); numElems != num { return fmt.Errorf("incorrect number of transaction elements, expected %d but found %d", num, numElems) } - // Load Chain ID for dynamic transactions - if t.Type == DynamicFeeTx { - t.ChainID = new(big.Int) - if err = getElem().GetBigInt(t.ChainID); err != nil { + switch t.Inner.(type) { + case *MixedTxn: + // Load Chain ID for dynamic transactions + if t.Type() == DynamicFeeTx { + txChainID := new(big.Int) + if err = getElem().GetBigInt(txChainID); err != nil { + return err + } + + t.SetChainID(txChainID) + } + + // nonce + txNonce, err := getElem().GetUint64() + if err != nil { return err } - } - // nonce - if t.Nonce, err = getElem().GetUint64(); err != nil { - return err - } + t.SetNonce(txNonce) + + if t.Type() == DynamicFeeTx { + // gasTipCap + txGasTipCap := new(big.Int) + if err = getElem().GetBigInt(txGasTipCap); err != nil { + return err + } + + t.SetGasTipCap(txGasTipCap) - if t.Type == DynamicFeeTx { - // gasTipCap - t.GasTipCap = new(big.Int) - if err = getElem().GetBigInt(t.GasTipCap); err != nil { + // gasFeeCap + txGasFeeCap := new(big.Int) + if err = getElem().GetBigInt(txGasFeeCap); err != nil { + return err + } + + t.SetGasFeeCap(txGasFeeCap) + } else { + // gasPrice + txGasPrice := new(big.Int) + if err = getElem().GetBigInt(txGasPrice); err != nil { + return err + } + + t.SetGasPrice(txGasPrice) + } + + // gas + txGas, err := getElem().GetUint64() + if err != nil { return err } - // gasFeeCap - t.GasFeeCap = new(big.Int) - if err = getElem().GetBigInt(t.GasFeeCap); err != nil { + t.SetGas(txGas) + + // to + if vv, _ := getElem().Bytes(); len(vv) == 20 { + // address + addr := BytesToAddress(vv) + t.SetTo(&addr) + } else { + // reset To + t.SetTo(nil) + } + + // value + txValue := new(big.Int) + if err = getElem().GetBigInt(txValue); err != nil { return err } - } else { - // gasPrice - t.GasPrice = new(big.Int) - if err = getElem().GetBigInt(t.GasPrice); err != nil { + + t.SetValue(txValue) + + // input + var txInput []byte + + txInput, err = getElem().GetBytes(txInput) + if err != nil { return err } - } - // gas - if t.Gas, err = getElem().GetUint64(); err != nil { - return err - } + t.SetInput(txInput) - // to - if vv, _ := getElem().Bytes(); len(vv) == 20 { - // address - addr := BytesToAddress(vv) - t.To = &addr - } else { - // reset To - t.To = nil - } + if t.Type() == DynamicFeeTx { + accessListVV, err := getElem().GetElems() + if err != nil { + return err + } - // value - t.Value = new(big.Int) - if err = getElem().GetBigInt(t.Value); err != nil { - return err - } + var txAccessList TxAccessList + if len(accessListVV) != 0 { + txAccessList = make(TxAccessList, len(accessListVV)) + } - // input - if t.Input, err = getElem().GetBytes(t.Input[:0]); err != nil { - return err - } + for i, accessTupleVV := range accessListVV { + accessTupleElems, err := accessTupleVV.GetElems() + if err != nil { + return err + } - // Skipping Access List field since we don't support it. - // This is needed to be compatible with other EVM chains and have the same format. - // Since we don't have access list, just skip it here. - if t.Type == DynamicFeeTx { - _ = getElem() - } + // Read the address + addressVV := accessTupleElems[0] - // V - t.V = new(big.Int) - if err = getElem().GetBigInt(t.V); err != nil { - return err - } + addressBytes, err := addressVV.Bytes() + if err != nil { + return err + } - // R - t.R = new(big.Int) - if err = getElem().GetBigInt(t.R); err != nil { - return err - } + txAccessList[i].Address = BytesToAddress(addressBytes) - // S - t.S = new(big.Int) - if err = getElem().GetBigInt(t.S); err != nil { - return err - } + // Read the storage keys + storageKeysArrayVV := accessTupleElems[1] + + storageKeysElems, err := storageKeysArrayVV.GetElems() + if err != nil { + return err + } + + txAccessList[i].StorageKeys = make([]Hash, len(storageKeysElems)) + + for j, storageKeyVV := range storageKeysElems { + storageKeyBytes, err := storageKeyVV.Bytes() + if err != nil { + return err + } + + txAccessList[i].StorageKeys[j] = BytesToHash(storageKeyBytes) + } + } + + t.SetAccessList(txAccessList) + } + + // V + txV := new(big.Int) + if err = getElem().GetBigInt(txV); err != nil { + return err + } + + // R + txR := new(big.Int) + if err = getElem().GetBigInt(txR); err != nil { + return err + } + + // S + txS := new(big.Int) + if err = getElem().GetBigInt(txS); err != nil { + return err + } + + t.SetSignatureValues(txV, txR, txS) + + if t.Type() == StateTx { + t.SetFrom(ZeroAddress) + + // We need to set From field for state transaction, + // because we are using unique, predefined address, for sending such transactions + if vv, err := getElem().Bytes(); err == nil && len(vv) == AddressLength { + // address + t.SetFrom(BytesToAddress(vv)) + } + } + case *AccessListTxn: + txChainID := new(big.Int) + if err = getElem().GetBigInt(txChainID); err != nil { + return err + } + + t.SetChainID(txChainID) + + // nonce + txNonce, err := getElem().GetUint64() + if err != nil { + return err + } + + t.SetNonce(txNonce) + + // gasPrice + txGasPrice := new(big.Int) + if err = getElem().GetBigInt(txGasPrice); err != nil { + return err + } + + t.SetGasPrice(txGasPrice) + + // gas + txGas, err := getElem().GetUint64() + if err != nil { + return err + } - if t.Type == StateTx { - t.From = ZeroAddress + t.SetGas(txGas) - // We need to set From field for state transaction, - // because we are using unique, predefined address, for sending such transactions - if vv, err := getElem().Bytes(); err == nil && len(vv) == AddressLength { + // to + if vv, _ := getElem().Bytes(); len(vv) == 20 { // address - t.From = BytesToAddress(vv) + addr := BytesToAddress(vv) + t.SetTo(&addr) + } else { + // reset To + t.SetTo(nil) + } + + // value + txValue := new(big.Int) + if err = getElem().GetBigInt(txValue); err != nil { + return err + } + + t.SetValue(txValue) + + // input + var txInput []byte + + txInput, err = getElem().GetBytes(txInput) + if err != nil { + return err + } + + t.SetInput(txInput) + + //accessList + accessListVV, err := getElem().GetElems() + if err != nil { + return err + } + + var txAccessList TxAccessList + if len(accessListVV) != 0 { + txAccessList = make(TxAccessList, len(accessListVV)) + } + + for i, accessTupleVV := range accessListVV { + accessTupleElems, err := accessTupleVV.GetElems() + if err != nil { + return err + } + + // Read the address + addressVV := accessTupleElems[0] + + addressBytes, err := addressVV.Bytes() + if err != nil { + return err + } + + txAccessList[i].Address = BytesToAddress(addressBytes) + + // Read the storage keys + storageKeysArrayVV := accessTupleElems[1] + + storageKeysElems, err := storageKeysArrayVV.GetElems() + if err != nil { + return err + } + + txAccessList[i].StorageKeys = make([]Hash, len(storageKeysElems)) + + for j, storageKeyVV := range storageKeysElems { + storageKeyBytes, err := storageKeyVV.Bytes() + if err != nil { + return err + } + + txAccessList[i].StorageKeys[j] = BytesToHash(storageKeyBytes) + } + } + + t.SetAccessList(txAccessList) + + // V + txV := new(big.Int) + if err = getElem().GetBigInt(txV); err != nil { + return err + } + + // R + txR := new(big.Int) + if err = getElem().GetBigInt(txR); err != nil { + return err } + + // S + txS := new(big.Int) + if err = getElem().GetBigInt(txS); err != nil { + return err + } + + t.SetSignatureValues(txV, txR, txS) } return nil diff --git a/types/rlp_unmarshal_storage.go b/types/rlp_unmarshal_storage.go index 31b2fd385a..f83a160ed9 100644 --- a/types/rlp_unmarshal_storage.go +++ b/types/rlp_unmarshal_storage.go @@ -29,8 +29,14 @@ func (b *Body) unmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { // transactions if err = unmarshalRLPFrom(p, tuple[0], func(txType TxType, p *fastrlp.Parser, v *fastrlp.Value) error { - bTxn := &Transaction{ - Type: txType, + var bTxn *Transaction + switch txType { + case AccessListTx: + bTxn = NewTx(&AccessListTxn{}) + case DynamicFeeTx, StateTx, LegacyTx: + bTxn = NewTx(&MixedTxn{ + Type: txType, + }) } if err = bTxn.unmarshalStoreRLPFrom(p, v); err != nil { @@ -64,16 +70,22 @@ func (b *Body) unmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { // UnmarshalStoreRLP unmarshals transaction from byte slice. Hash must be computed manually after! func (t *Transaction) UnmarshalStoreRLP(input []byte) error { - t.Type = LegacyTx + t.SetTransactionType(LegacyTx) + + offset := 0 if len(input) > 0 && input[0] <= RLPSingleByteUpperLimit { - var err error - if t.Type, err = txTypeFromByte(input[0]); err != nil { + tType, err := txTypeFromByte(input[0]) + if err != nil { return err } + + t.SetTransactionType(tType) + + offset = 1 } - return UnmarshalRlp(t.unmarshalStoreRLPFrom, input) + return UnmarshalRlp(t.unmarshalStoreRLPFrom, input[offset:]) } func (t *Transaction) unmarshalStoreRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { @@ -88,10 +100,13 @@ func (t *Transaction) unmarshalStoreRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) } if len(elems) == 3 { - if err = t.Type.unmarshalRLPFrom(p, elems[0]); err != nil { + tType := t.Type() + if err = tType.unmarshalRLPFrom(p, elems[0]); err != nil { return err } + t.SetTransactionType(tType) + elems = elems[1:] } @@ -101,7 +116,7 @@ func (t *Transaction) unmarshalStoreRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) } // context part - if err = elems[1].GetAddr(t.From[:]); err != nil { + if err = elems[1].GetAddr(t.From().Bytes()); err != nil { return err } @@ -130,15 +145,18 @@ func (r *Receipts) unmarshalStoreRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) er func (r *Receipt) UnmarshalStoreRLP(input []byte) error { r.TransactionType = LegacyTx + offset := 0 if len(input) > 0 && input[0] <= RLPSingleByteUpperLimit { var err error if r.TransactionType, err = txTypeFromByte(input[0]); err != nil { return err } + + offset = 1 } - return UnmarshalRlp(r.unmarshalStoreRLPFrom, input) + return UnmarshalRlp(r.unmarshalStoreRLPFrom, input[offset:]) } func (r *Receipt) unmarshalStoreRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { diff --git a/types/transaction.go b/types/transaction.go index c31c11d774..64c508c323 100644 --- a/types/transaction.go +++ b/types/transaction.go @@ -20,15 +20,16 @@ type TxType byte // List of supported transaction types const ( LegacyTx TxType = 0x0 - StateTx TxType = 0x7f + AccessListTx TxType = 0x01 DynamicFeeTx TxType = 0x02 + StateTx TxType = 0x7f ) func txTypeFromByte(b byte) (TxType, error) { tt := TxType(b) switch tt { - case LegacyTx, StateTx, DynamicFeeTx: + case LegacyTx, StateTx, DynamicFeeTx, AccessListTx: return tt, nil default: return tt, fmt.Errorf("unknown transaction type: %d", b) @@ -44,121 +45,327 @@ func (t TxType) String() (s string) { return "StateTx" case DynamicFeeTx: return "DynamicFeeTx" + case AccessListTx: + return "AccessListTx" } return } type Transaction struct { - Nonce uint64 - GasPrice *big.Int - GasTipCap *big.Int - GasFeeCap *big.Int - Gas uint64 - To *Address - Value *big.Int - Input []byte - V, R, S *big.Int - Hash Hash - From Address - - Type TxType - - ChainID *big.Int + Inner TxData // Cache size atomic.Pointer[uint64] } +// NewTx creates a new transaction. +func NewTx(inner TxData) *Transaction { + t := new(Transaction) + t.Inner = inner + + return t +} + +// InitInnerData initializes the inner data of a Transaction based on the given transaction type. +// It sets the Inner field of the Transaction to either an AccessListStruct or a MixedTx, +// depending on the value of txType. +func (t *Transaction) InitInnerData(txType TxType) { + switch txType { + case AccessListTx: + t.Inner = &AccessListTxn{} + default: + t.Inner = &MixedTxn{} + } + + t.Inner.setTransactionType(txType) +} + +type TxData interface { + transactionType() TxType + chainID() *big.Int + nonce() uint64 + gasPrice() *big.Int + gasTipCap() *big.Int + gasFeeCap() *big.Int + gas() uint64 + to() *Address + value() *big.Int + input() []byte + accessList() TxAccessList + from() Address + hash() Hash + rawSignatureValues() (v, r, s *big.Int) + + //methods to set transactions fields + setSignatureValues(v, r, s *big.Int) + setFrom(Address) + setGas(uint64) + setChainID(*big.Int) + setGasPrice(*big.Int) + setGasFeeCap(*big.Int) + setGasTipCap(*big.Int) + setTransactionType(TxType) + setValue(*big.Int) + setInput([]byte) + setTo(address *Address) + setNonce(uint64) + setAccessList(TxAccessList) + setHash(Hash) +} + +func (t *Transaction) Type() TxType { + return t.Inner.transactionType() +} + +func (t *Transaction) ChainID() *big.Int { + return t.Inner.chainID() +} + +func (t *Transaction) Nonce() uint64 { + return t.Inner.nonce() +} + +func (t *Transaction) GasPrice() *big.Int { + return t.Inner.gasPrice() +} + +func (t *Transaction) GasTipCap() *big.Int { + return t.Inner.gasTipCap() +} + +func (t *Transaction) GasFeeCap() *big.Int { + return t.Inner.gasFeeCap() +} + +func (t *Transaction) Gas() uint64 { + return t.Inner.gas() +} + +func (t *Transaction) To() *Address { + return t.Inner.to() +} + +func (t *Transaction) Value() *big.Int { + return t.Inner.value() +} + +func (t *Transaction) Input() []byte { + return t.Inner.input() +} + +func (t *Transaction) AccessList() TxAccessList { + return t.Inner.accessList() +} + +func (t *Transaction) From() Address { + return t.Inner.from() +} + +func (t *Transaction) Hash() Hash { + return t.Inner.hash() +} + +func (t *Transaction) RawSignatureValues() (v, r, s *big.Int) { + return t.Inner.rawSignatureValues() +} + +// set methods for transaction fields +func (t *Transaction) SetSignatureValues(v, r, s *big.Int) { + t.Inner.setSignatureValues(v, r, s) +} + +func (t *Transaction) SetFrom(addr Address) { + t.Inner.setFrom(addr) +} + +func (t *Transaction) SetGas(gas uint64) { + t.Inner.setGas(gas) +} + +func (t *Transaction) SetChainID(id *big.Int) { + t.Inner.setChainID(id) +} + +func (t *Transaction) SetGasPrice(gas *big.Int) { + t.Inner.setGasPrice(gas) +} + +func (t *Transaction) SetGasFeeCap(gas *big.Int) { + t.Inner.setGasFeeCap(gas) +} + +func (t *Transaction) SetGasTipCap(gas *big.Int) { + t.Inner.setGasTipCap(gas) +} + +func (t *Transaction) SetTransactionType(tType TxType) { + t.Inner.setTransactionType(tType) +} + +func (t *Transaction) SetValue(value *big.Int) { + t.Inner.setValue(value) +} + +func (t *Transaction) SetInput(input []byte) { + t.Inner.setInput(input) +} + +func (t *Transaction) SetTo(address *Address) { + t.Inner.setTo(address) +} + +func (t *Transaction) SetNonce(nonce uint64) { + t.Inner.setNonce(nonce) +} + +func (t *Transaction) SetAccessList(accessList TxAccessList) { + t.Inner.setAccessList(accessList) +} + +func (t *Transaction) SetHash(h Hash) { + t.Inner.setHash(h) +} + // IsContractCreation checks if tx is contract creation func (t *Transaction) IsContractCreation() bool { - return t.To == nil + return t.To() == nil } // IsValueTransfer checks if tx is a value transfer func (t *Transaction) IsValueTransfer() bool { - return t.Value != nil && - t.Value.Sign() > 0 && - len(t.Input) == 0 && + return t.Value() != nil && + t.Value().Sign() > 0 && + len(t.Input()) == 0 && !t.IsContractCreation() } // ComputeHash computes the hash of the transaction func (t *Transaction) ComputeHash() *Transaction { + var txHash Hash + hash := keccak.DefaultKeccakPool.Get() - hash.WriteFn(t.Hash[:0], t.MarshalRLPTo) + hash.WriteFn(txHash[:0], t.MarshalRLPTo) + t.SetHash(txHash) keccak.DefaultKeccakPool.Put(hash) return t } func (t *Transaction) Copy() *Transaction { - tt := new(Transaction) - tt.Nonce = t.Nonce - tt.From = t.From - tt.Gas = t.Gas - tt.Type = t.Type - tt.Hash = t.Hash - - if t.To != nil { - newAddress := *t.To - tt.To = &newAddress + if t == nil { + return nil + } + + newTx := new(Transaction) + innerCopy := CopyTxData(t.Inner) + newTx.Inner = innerCopy + + return newTx +} + +// CopyTxData creates a deep copy of the provided TxData +func CopyTxData(data TxData) TxData { + if data == nil { + return nil + } + + var copyData TxData + switch data.(type) { + case *MixedTxn: + copyData = &MixedTxn{} + case *AccessListTxn: + copyData = &AccessListTxn{} + } + + if copyData == nil { + return nil } - tt.GasPrice = new(big.Int) - if t.GasPrice != nil { - tt.GasPrice.Set(t.GasPrice) + copyData.setNonce(data.nonce()) + copyData.setFrom(data.from()) + copyData.setTo(data.to()) + copyData.setHash(data.hash()) + copyData.setTransactionType(data.transactionType()) + copyData.setGas(data.gas()) + + if data.chainID() != nil { + chainID := new(big.Int) + chainID.Set(data.chainID()) + + copyData.setChainID(chainID) } - tt.GasTipCap = new(big.Int) - if t.GasTipCap != nil { - tt.GasTipCap.Set(t.GasTipCap) + if data.gasPrice() != nil { + gasPrice := new(big.Int) + gasPrice.Set(data.gasPrice()) + + copyData.setGasPrice(gasPrice) } - tt.GasFeeCap = new(big.Int) - if t.GasFeeCap != nil { - tt.GasFeeCap.Set(t.GasFeeCap) + if data.gasTipCap() != nil { + gasTipCap := new(big.Int) + gasTipCap.Set(data.gasTipCap()) + + copyData.setGasTipCap(gasTipCap) } - tt.Value = new(big.Int) - if t.Value != nil { - tt.Value.Set(t.Value) + if data.gasFeeCap() != nil { + gasFeeCap := new(big.Int) + gasFeeCap.Set(data.gasFeeCap()) + + copyData.setGasFeeCap(gasFeeCap) } - if t.V != nil { - tt.V = new(big.Int).Set(t.V) + if data.value() != nil { + value := new(big.Int) + value.Set(data.value()) + + copyData.setValue(value) } - if t.R != nil { - tt.R = new(big.Int).Set(t.R) + v, r, s := data.rawSignatureValues() + + var vCopy, rCopy, sCopy *big.Int + + if v != nil { + vCopy = new(big.Int) + vCopy.Set(v) } - if t.S != nil { - tt.S = new(big.Int).Set(t.S) + if r != nil { + rCopy = new(big.Int) + rCopy.Set(r) } - if t.ChainID != nil { - tt.ChainID = new(big.Int).Set(t.ChainID) + if s != nil { + sCopy = new(big.Int) + sCopy.Set(s) } - tt.Input = make([]byte, len(t.Input)) - copy(tt.Input[:], t.Input[:]) + copyData.setSignatureValues(vCopy, rCopy, sCopy) + + inputCopy := make([]byte, len(data.input())) + copy(inputCopy, data.input()[:]) + + copyData.setInput(inputCopy) + copyData.setAccessList(data.accessList().Copy()) - return tt + return copyData } // Cost returns gas * gasPrice + value func (t *Transaction) Cost() *big.Int { var factor *big.Int - if t.GasFeeCap != nil && t.GasFeeCap.BitLen() > 0 { - factor = new(big.Int).Set(t.GasFeeCap) + if t.GasFeeCap() != nil && t.GasFeeCap().BitLen() > 0 { + factor = new(big.Int).Set(t.GasFeeCap()) } else { - factor = new(big.Int).Set(t.GasPrice) + factor = new(big.Int).Set(t.GasPrice()) } - total := new(big.Int).Mul(factor, new(big.Int).SetUint64(t.Gas)) - total = total.Add(total, t.Value) + total := new(big.Int).Mul(factor, new(big.Int).SetUint64(t.Gas())) + total = total.Add(total, t.Value()) return total } @@ -170,20 +377,20 @@ func (t *Transaction) Cost() *big.Int { // - use existing gas price if exists // - or calculate a value with formula: min(gasFeeCap, gasTipCap + baseFee); func (t *Transaction) GetGasPrice(baseFee uint64) *big.Int { - if t.GasPrice != nil && t.GasPrice.BitLen() > 0 { - return new(big.Int).Set(t.GasPrice) + if t.GasPrice() != nil && t.GasPrice().BitLen() > 0 { + return new(big.Int).Set(t.GasPrice()) } else if baseFee == 0 { return big.NewInt(0) } gasFeeCap := new(big.Int) - if t.GasFeeCap != nil { - gasFeeCap = gasFeeCap.Set(t.GasFeeCap) + if t.GasFeeCap() != nil { + gasFeeCap = gasFeeCap.Set(t.GasFeeCap()) } gasTipCap := new(big.Int) - if t.GasTipCap != nil { - gasTipCap = gasTipCap.Set(t.GasTipCap) + if t.GasTipCap() != nil { + gasTipCap = gasTipCap.Set(t.GasTipCap()) } if gasFeeCap.BitLen() > 0 || gasTipCap.BitLen() > 0 { @@ -227,29 +434,29 @@ func (t *Transaction) EffectiveGasTip(baseFee *big.Int) *big.Int { // GetGasTipCap gets gas tip cap depending on tx type // Spec: https://eips.ethereum.org/EIPS/eip-1559#specification func (t *Transaction) GetGasTipCap() *big.Int { - switch t.Type { + switch t.Type() { case DynamicFeeTx: - return t.GasTipCap + return t.GasTipCap() default: - return t.GasPrice + return t.GasPrice() } } // GetGasFeeCap gets gas fee cap depending on tx type // Spec: https://eips.ethereum.org/EIPS/eip-1559#specification func (t *Transaction) GetGasFeeCap() *big.Int { - switch t.Type { + switch t.Type() { case DynamicFeeTx: - return t.GasFeeCap + return t.GasFeeCap() default: - return t.GasPrice + return t.GasPrice() } } // FindTxByHash returns transaction and its index from a slice of transactions func FindTxByHash(txs []*Transaction, hash Hash) (*Transaction, int) { for idx, txn := range txs { - if txn.Hash == hash { + if txn.Hash() == hash { return txn, idx } } diff --git a/types/types_test.go b/types/types_test.go index 875816c75a..aae6150e70 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -68,7 +68,7 @@ func TestEIP55(t *testing.T) { func TestTransactionCopy(t *testing.T) { addrTo := StringToAddress("11") - txn := &Transaction{ + txn := NewTx(&MixedTxn{ Nonce: 0, GasTipCap: big.NewInt(11), GasFeeCap: big.NewInt(11), @@ -80,7 +80,7 @@ func TestTransactionCopy(t *testing.T) { V: big.NewInt(25), S: big.NewInt(26), R: big.NewInt(27), - } + }) newTxn := txn.Copy() if !reflect.DeepEqual(txn, newTxn) {