From f777cde7b81c8c1d0d91aa96f98f6f477f6e652a Mon Sep 17 00:00:00 2001 From: pdrobnjak Date: Wed, 7 Sep 2022 11:06:55 +0200 Subject: [PATCH 1/9] Apricot Phase 6 support --- accounts/abi/bind/backends/simulated.go | 3 +- constants/constants.go | 13 ++ core/genesis.go | 38 +++-- core/state_processor.go | 7 +- core/state_transition.go | 16 +- core/tx_pool.go | 7 + core/types/block.go | 1 + core/vm/contracts.go | 70 ++++++++- core/vm/contracts_stateful.go | 90 +++-------- core/vm/errors.go | 15 -- core/vm/evm.go | 188 +++++++++++++++++++---- core/vm/gas.go | 3 +- core/vm/gas_table.go | 51 +++--- core/vm/instructions.go | 43 +++--- core/vm/interpreter.go | 9 +- core/vm/operations_acl.go | 5 +- internal/ethapi/api.go | 3 +- miner/worker.go | 8 +- params/config.go | 125 +++++++++++---- plugin/evm/vm.go | 2 +- precompile/contract.go | 143 +++++++++++++++++ precompile/params.go | 47 ++++++ precompile/stateful_precompile_config.go | 59 +++++++ precompile/utils.go | 34 ++++ utils/fork.go | 24 +++ vmerrs/vmerrs.go | 51 ++++++ 26 files changed, 823 insertions(+), 232 deletions(-) create mode 100644 constants/constants.go create mode 100644 precompile/contract.go create mode 100644 precompile/params.go create mode 100644 precompile/stateful_precompile_config.go create mode 100644 precompile/utils.go create mode 100644 utils/fork.go create mode 100644 vmerrs/vmerrs.go diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 806fb282d3..431fc9d192 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -35,6 +35,7 @@ import ( "time" "github.com/ava-labs/coreth/eth" + "github.com/ava-labs/coreth/vmerrs" "github.com/ava-labs/coreth/accounts/abi" "github.com/ava-labs/coreth/accounts/abi/bind" @@ -609,7 +610,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call interfaces.Call return 0, err } if failed { - if result != nil && result.Err != vm.ErrOutOfGas { + if result != nil && result.Err != vmerrs.ErrOutOfGas { if len(result.Revert()) > 0 { return 0, newRevertError(result) } diff --git a/constants/constants.go b/constants/constants.go new file mode 100644 index 0000000000..ed52ed8c3c --- /dev/null +++ b/constants/constants.go @@ -0,0 +1,13 @@ +// (c) 2021-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package constants + +import "github.com/ethereum/go-ethereum/common" + +var ( + BlackholeAddr = common.Address{ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + } +) diff --git a/core/genesis.go b/core/genesis.go index 2efe64120a..1252e13c5a 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -233,6 +233,24 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { if err != nil { panic(err) } + + head := &types.Header{ + Number: new(big.Int).SetUint64(g.Number), + Nonce: types.EncodeNonce(g.Nonce), + Time: g.Timestamp, + ParentHash: g.ParentHash, + Extra: g.ExtraData, + GasLimit: g.GasLimit, + GasUsed: g.GasUsed, + BaseFee: g.BaseFee, + Difficulty: g.Difficulty, + MixDigest: g.Mixhash, + Coinbase: g.Coinbase, + } + + // Configure any stateful precompiles that should be enabled in the genesis. + g.Config.CheckConfigurePrecompiles(nil, types.NewBlockWithHeader(head), statedb) + for addr, account := range g.Alloc { statedb.AddBalance(addr, account.Balance) statedb.SetCode(addr, account.Code) @@ -247,20 +265,8 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { } } root := statedb.IntermediateRoot(false) - head := &types.Header{ - Number: new(big.Int).SetUint64(g.Number), - Nonce: types.EncodeNonce(g.Nonce), - Time: g.Timestamp, - ParentHash: g.ParentHash, - Extra: g.ExtraData, - GasLimit: g.GasLimit, - GasUsed: g.GasUsed, - BaseFee: g.BaseFee, - Difficulty: g.Difficulty, - MixDigest: g.Mixhash, - Coinbase: g.Coinbase, - Root: root, - } + head.Root = root + if g.GasLimit == 0 { head.GasLimit = params.GenesisGasLimit } @@ -275,7 +281,9 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { } } statedb.Commit(false) - statedb.Database().TrieDB().Commit(root, true, nil) + if err := statedb.Database().TrieDB().Commit(root, true, nil); err != nil { + panic(fmt.Sprintf("unable to commit genesis block: %v", err)) + } return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil), nil, false) } diff --git a/core/state_processor.go b/core/state_processor.go index 636eb20f3b..535a39b642 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -75,7 +75,12 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state blockNumber = block.Number() allLogs []*types.Log gp = new(GasPool).AddGas(block.GasLimit()) + timestamp = new(big.Int).SetUint64(header.Time) ) + + // Configure any stateful precompiles that should go into effect during this block. + p.config.CheckConfigurePrecompiles(new(big.Int).SetUint64(parent.Time), block, statedb) + // Mutate the block and state according to any hard-fork specs if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { misc.ApplyDAOHardFork(statedb) @@ -84,7 +89,7 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) // Iterate over and process the individual transactions for i, tx := range block.Transactions() { - msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number, new(big.Int).SetUint64(header.Time)), header.BaseFee) + msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number, timestamp), header.BaseFee) if err != nil { return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } diff --git a/core/state_transition.go b/core/state_transition.go index 43f86bc1b8..ee6ad0cb75 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -27,6 +27,7 @@ package core import ( + "errors" "fmt" "math" "math/big" @@ -36,6 +37,7 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" ) @@ -118,7 +120,7 @@ func (result *ExecutionResult) Return() []byte { // Revert returns the concrete revert reason if the execution is aborted by `REVERT` // opcode. Note the reason can be nil if no data supplied with revert opcode. func (result *ExecutionResult) Revert() []byte { - if result.Err != vm.ErrExecutionReverted { + if result.Err != vmerrs.ErrExecutionReverted { return nil } return common.CopyBytes(result.ReturnData) @@ -241,8 +243,9 @@ func (st *StateTransition) preCheck() error { return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA, st.msg.From().Hex(), codeHash) } - if st.msg.From() == st.evm.Context.Coinbase { - return fmt.Errorf("%w: address %v", vm.ErrNoSenderBlackhole, st.msg.From()) + // Make sure the sender is not prohibited + if vm.IsProhibited(st.msg.From()) { + return fmt.Errorf("%w: address %v", vmerrs.ErrAddrProhibited, st.msg.From()) } } // Make sure that transaction gasFeeCap is greater than the baseFee (post london) @@ -338,6 +341,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) } + if errors.Is(vmerr, vmerrs.ErrToAddrProhibited) { + return &ExecutionResult{ + UsedGas: st.gasUsed(), + Err: vmerr, + ReturnData: ret, + }, vmerr + } st.refundGas(apricotPhase1) st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) diff --git a/core/tx_pool.go b/core/tx_pool.go index 9521a20e45..c45f13fb7a 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -1064,6 +1064,13 @@ func (pool *TxPool) HasLocal(hash common.Hash) bool { return pool.all.GetLocal(hash) != nil } +func (pool *TxPool) RemoveTx(hash common.Hash) { + pool.mu.Lock() + defer pool.mu.Unlock() + + pool.removeTx(hash, true) +} + // removeTx removes a single transaction from the queue, moving all subsequent // transactions back to the future queue. func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { diff --git a/core/types/block.go b/core/types/block.go index 138ec6c95b..7224122506 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -333,6 +333,7 @@ func (b *Block) GasLimit() uint64 { return b.header.GasLimit } func (b *Block) GasUsed() uint64 { return b.header.GasUsed } func (b *Block) Difficulty() *big.Int { return new(big.Int).Set(b.header.Difficulty) } func (b *Block) Time() uint64 { return b.header.Time } +func (b *Block) Timestamp() *big.Int { return new(big.Int).SetUint64(b.header.Time) } func (b *Block) NumberU64() uint64 { return b.header.Number.Uint64() } func (b *Block) MixDigest() common.Hash { return b.header.MixDigest } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 88009f9f73..76a3dbcf4d 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -30,9 +30,13 @@ import ( "crypto/sha256" "encoding/binary" "errors" + "fmt" "math/big" + "github.com/ava-labs/coreth/constants" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/precompile" + "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" @@ -54,7 +58,7 @@ type PrecompiledContract interface { // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum // contracts used in the Frontier and Homestead releases. -var PrecompiledContractsHomestead = map[common.Address]StatefulPrecompiledContract{ +var PrecompiledContractsHomestead = map[common.Address]precompile.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -63,7 +67,7 @@ var PrecompiledContractsHomestead = map[common.Address]StatefulPrecompiledContra // PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum // contracts used in the Byzantium release. -var PrecompiledContractsByzantium = map[common.Address]StatefulPrecompiledContract{ +var PrecompiledContractsByzantium = map[common.Address]precompile.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -76,7 +80,7 @@ var PrecompiledContractsByzantium = map[common.Address]StatefulPrecompiledContra // PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum // contracts used in the Istanbul release. -var PrecompiledContractsIstanbul = map[common.Address]StatefulPrecompiledContract{ +var PrecompiledContractsIstanbul = map[common.Address]precompile.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -90,7 +94,7 @@ var PrecompiledContractsIstanbul = map[common.Address]StatefulPrecompiledContrac // PrecompiledContractsApricotPhase2 contains the default set of pre-compiled Ethereum // contracts used in the Apricot Phase 2 release. -var PrecompiledContractsApricotPhase2 = map[common.Address]StatefulPrecompiledContract{ +var PrecompiledContractsApricotPhase2 = map[common.Address]precompile.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -105,11 +109,30 @@ var PrecompiledContractsApricotPhase2 = map[common.Address]StatefulPrecompiledCo NativeAssetCallAddr: &nativeAssetCall{gasCost: params.AssetCallApricot}, } +// PrecompiledContractsApricotPhase6 contains the default set of pre-compiled Ethereum +// contracts used in the Apricot Phase 6 release. +var PrecompiledContractsApricotPhase6 = map[common.Address]precompile.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: true}), + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), + common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), + genesisContractAddr: &deprecatedContract{}, + NativeAssetBalanceAddr: &deprecatedContract{}, + NativeAssetCallAddr: &deprecatedContract{}, +} + var ( + PrecompiledAddressesApricotPhase6 []common.Address PrecompiledAddressesApricotPhase2 []common.Address PrecompiledAddressesIstanbul []common.Address PrecompiledAddressesByzantium []common.Address PrecompiledAddressesHomestead []common.Address + PrecompileAllNativeAddresses map[common.Address]struct{} ) func init() { @@ -125,11 +148,48 @@ func init() { for k := range PrecompiledContractsApricotPhase2 { PrecompiledAddressesApricotPhase2 = append(PrecompiledAddressesApricotPhase2, k) } + for k := range PrecompiledContractsApricotPhase6 { + PrecompiledAddressesApricotPhase6 = append(PrecompiledAddressesApricotPhase6, k) + } + // Set of all native precompile addresses that are in use + // Note: this will repeat some addresses, but this is cheap and makes the code clearer. + PrecompileAllNativeAddresses = make(map[common.Address]struct{}) + addrsList := append(PrecompiledAddressesHomestead, PrecompiledAddressesByzantium...) + addrsList = append(addrsList, PrecompiledAddressesIstanbul...) + addrsList = append(addrsList, PrecompiledAddressesApricotPhase2...) + for _, k := range addrsList { + PrecompileAllNativeAddresses[k] = struct{}{} + } + + // Ensure that this package will panic during init if there is a conflict present with the declared + // precompile addresses. + for _, k := range precompile.UsedAddresses { + if _, ok := PrecompileAllNativeAddresses[k]; ok { + panic(fmt.Errorf("precompile address collides with existing native address: %s", k)) + } + if k == constants.BlackholeAddr { + panic(fmt.Errorf("cannot use address %s for stateful precompile - overlaps with blackhole address", k)) + } + + // check that [k] belongs to at least one ReservedRange + found := false + for _, reservedRange := range precompile.ReservedRanges { + if reservedRange.Contains(k) { + found = true + break + } + } + if !found { + panic(fmt.Errorf("address %s used for stateful precompile but not specified in any reserved range", k)) + } + } } // ActivePrecompiles returns the precompiles enabled with the current configuration. func ActivePrecompiles(rules params.Rules) []common.Address { switch { + case rules.IsApricotPhase6: + return PrecompiledAddressesApricotPhase6 case rules.IsApricotPhase2: return PrecompiledAddressesApricotPhase2 case rules.IsIstanbul: @@ -149,7 +209,7 @@ func ActivePrecompiles(rules params.Rules) []common.Address { func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { gasCost := p.RequiredGas(input) if suppliedGas < gasCost { - return nil, 0, ErrOutOfGas + return nil, 0, vmerrs.ErrOutOfGas } suppliedGas -= gasCost output, err := p.Run(input) diff --git a/core/vm/contracts_stateful.go b/core/vm/contracts_stateful.go index f182b2a48a..096026e537 100644 --- a/core/vm/contracts_stateful.go +++ b/core/vm/contracts_stateful.go @@ -7,7 +7,8 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/precompile" + "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" ) @@ -24,31 +25,26 @@ var ( NativeAssetCallAddr = common.HexToAddress("0x0100000000000000000000000000000000000002") ) -// StatefulPrecompiledContract is the interface for executing a precompiled contract -// This wraps the PrecompiledContracts native to Ethereum and allows adding in stateful -// precompiled contracts to support native Avalanche asset transfers. -type StatefulPrecompiledContract interface { - // Run executes a precompiled contract in the current state - // assumes that it has already been verified that [caller] can - // transfer [value]. - Run(evm *EVM, caller ContractRef, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) -} - // wrappedPrecompiledContract implements StatefulPrecompiledContract by wrapping stateless native precompiled contracts // in Ethereum. type wrappedPrecompiledContract struct { p PrecompiledContract } -func newWrappedPrecompiledContract(p PrecompiledContract) StatefulPrecompiledContract { +func newWrappedPrecompiledContract(p PrecompiledContract) precompile.StatefulPrecompiledContract { return &wrappedPrecompiledContract{p: p} } // Run implements the StatefulPrecompiledContract interface -func (w *wrappedPrecompiledContract) Run(evm *EVM, caller ContractRef, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { +func (w *wrappedPrecompiledContract) Run(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { return RunPrecompiledContract(w.p, input, suppliedGas) } +// RunStatefulPrecompiledContract confirms runs [precompile] with the specified parameters. +func RunStatefulPrecompiledContract(precompile precompile.StatefulPrecompiledContract, accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + return precompile.Run(accessibleState, caller, addr, input, suppliedGas, readOnly) +} + // nativeAssetBalance is a precompiled contract used to retrieve the native asset balance type nativeAssetBalance struct { gasCost uint64 @@ -75,21 +71,21 @@ func UnpackNativeAssetBalanceInput(input []byte) (common.Address, common.Hash, e } // Run implements StatefulPrecompiledContract -func (b *nativeAssetBalance) Run(evm *EVM, caller ContractRef, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { +func (b *nativeAssetBalance) Run(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { // input: encodePacked(address 20 bytes, assetID 32 bytes) if suppliedGas < b.gasCost { - return nil, 0, ErrOutOfGas + return nil, 0, vmerrs.ErrOutOfGas } remainingGas = suppliedGas - b.gasCost address, assetID, err := UnpackNativeAssetBalanceInput(input) if err != nil { - return nil, remainingGas, ErrExecutionReverted + return nil, remainingGas, vmerrs.ErrExecutionReverted } - res, overflow := uint256.FromBig(evm.StateDB.GetBalanceMultiCoin(address, assetID)) + res, overflow := uint256.FromBig(accessibleState.GetStateDB().GetBalanceMultiCoin(address, assetID)) if overflow { - return nil, remainingGas, ErrExecutionReverted + return nil, remainingGas, vmerrs.ErrExecutionReverted } return common.LeftPadBytes(res.Bytes(), 32), remainingGas, nil } @@ -125,63 +121,13 @@ func UnpackNativeAssetCallInput(input []byte) (common.Address, common.Hash, *big } // Run implements StatefulPrecompiledContract -func (c *nativeAssetCall) Run(evm *EVM, caller ContractRef, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { +func (c *nativeAssetCall) Run(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { // input: encodePacked(address 20 bytes, assetID 32 bytes, assetAmount 32 bytes, callData variable length bytes) - if suppliedGas < c.gasCost { - return nil, 0, ErrOutOfGas - } - remainingGas = suppliedGas - c.gasCost - - if readOnly { - return nil, remainingGas, ErrExecutionReverted - } - - to, assetID, assetAmount, callData, err := UnpackNativeAssetCallInput(input) - if err != nil { - return nil, remainingGas, ErrExecutionReverted - } - - // Note: it is not possible for a negative assetAmount to be passed in here due to the fact that decoding a - // byte slice into a *big.Int type will always return a positive value. - if assetAmount.Sign() != 0 && !evm.Context.CanTransferMC(evm.StateDB, caller.Address(), to, assetID, assetAmount) { - return nil, remainingGas, ErrInsufficientBalance - } - - snapshot := evm.StateDB.Snapshot() - - if !evm.StateDB.Exist(to) { - if remainingGas < params.CallNewAccountGas { - return nil, 0, ErrOutOfGas - } - remainingGas -= params.CallNewAccountGas - evm.StateDB.CreateAccount(to) - } - - // Increment the call depth which is restricted to 1024 - evm.depth++ - defer func() { evm.depth-- }() - - // Send [assetAmount] of [assetID] to [to] address - evm.Context.TransferMultiCoin(evm.StateDB, caller.Address(), to, assetID, assetAmount) - ret, remainingGas, err = evm.Call(caller, to, callData, remainingGas, big.NewInt(0)) - - // When an error was returned by the EVM or when setting the creation code - // above we revert to the snapshot and consume any gas remaining. Additionally - // when we're in homestead this also counts for code storage gas errors. - if err != nil { - evm.StateDB.RevertToSnapshot(snapshot) - if err != ErrExecutionReverted { - remainingGas = 0 - } - // TODO: consider clearing up unused snapshots: - //} else { - // evm.StateDB.DiscardSnapshot(snapshot) - } - return ret, remainingGas, err + return accessibleState.NativeAssetCall(caller, input, suppliedGas, c.gasCost, readOnly) } type deprecatedContract struct{} -func (*deprecatedContract) Run(evm *EVM, caller ContractRef, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - return nil, suppliedGas, ErrExecutionReverted +func (*deprecatedContract) Run(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + return nil, suppliedGas, vmerrs.ErrExecutionReverted } diff --git a/core/vm/errors.go b/core/vm/errors.go index 5c81bb28fe..683a5651c4 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -33,21 +33,6 @@ import ( // List evm execution errors var ( - ErrOutOfGas = errors.New("out of gas") - ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") - ErrDepth = errors.New("max call depth exceeded") - ErrInsufficientBalance = errors.New("insufficient balance for transfer") - ErrContractAddressCollision = errors.New("contract address collision") - ErrExecutionReverted = errors.New("execution reverted") - ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") - ErrInvalidJump = errors.New("invalid jump destination") - ErrWriteProtection = errors.New("write protection") - ErrReturnDataOutOfBounds = errors.New("return data out of bounds") - ErrGasUintOverflow = errors.New("gas uint64 overflow") - ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") - ErrNonceUintOverflow = errors.New("nonce uint64 overflow") - ErrNoSenderBlackhole = errors.New("blackhole address cannot be used as sender") - // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. errStopToken = errors.New("stop token") diff --git a/core/vm/evm.go b/core/vm/evm.go index 8274a2623c..632811b1d5 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -31,12 +31,41 @@ import ( "sync/atomic" "time" + "github.com/ava-labs/coreth/constants" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/precompile" + "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/holiman/uint256" ) +var ( + _ precompile.PrecompileAccessibleState = &EVM{} + _ precompile.BlockContext = &BlockContext{} +) + +// IsProhibited returns true if [addr] is the blackhole address or is +// with a range reserved for precompiled contracts. +func IsProhibited(addr common.Address) bool { + if addr == constants.BlackholeAddr { + return true + } + for _, reservedRange := range precompile.ReservedRanges { + if reservedRange.Contains(addr) { + return true + } + } + return false +} + +func (evm *EVM) isProhibitedWithTimestamp(addr common.Address) bool { + if !evm.chainRules.IsApricotPhasePre6 || evm.chainRules.IsApricotPhase6 { + return false + } + return addr == NativeAssetCallAddr +} + // emptyCodeHash is used by create to ensure deployment is disallowed to already // deployed contract addresses (relevant after the account abstraction). var emptyCodeHash = crypto.Keccak256Hash(nil) @@ -53,8 +82,8 @@ type ( GetHashFunc func(uint64) common.Hash ) -func (evm *EVM) precompile(addr common.Address) (StatefulPrecompiledContract, bool) { - var precompiles map[common.Address]StatefulPrecompiledContract +func (evm *EVM) precompile(addr common.Address) (precompile.StatefulPrecompiledContract, bool) { + var precompiles map[common.Address]precompile.StatefulPrecompiledContract switch { case evm.chainRules.IsApricotPhase2: precompiles = PrecompiledContractsApricotPhase2 @@ -65,7 +94,15 @@ func (evm *EVM) precompile(addr common.Address) (StatefulPrecompiledContract, bo default: precompiles = PrecompiledContractsHomestead } + + // Check the existing precompiles first p, ok := precompiles[addr] + if ok { + return p, true + } + + // Otherwise, check the chain rules for the additionally configured precompiles. + p, ok = evm.chainRules.Precompiles[addr] return p, ok } @@ -94,6 +131,14 @@ type BlockContext struct { BaseFee *big.Int // Provides information for BASEFEE } +func (b *BlockContext) Number() *big.Int { + return b.BlockNumber +} + +func (b *BlockContext) Timestamp() *big.Int { + return b.Time +} + // TxContext provides the EVM with information about a transaction. // All fields can change between transactions. type TxContext struct { @@ -172,6 +217,16 @@ func (evm *EVM) Cancelled() bool { return atomic.LoadInt32(&evm.abort) == 1 } +// GetStateDB returns the evm's StateDB +func (evm *EVM) GetStateDB() precompile.StateDB { + return evm.StateDB +} + +// GetBlockContext returns the evm's BlockContext +func (evm *EVM) GetBlockContext() precompile.BlockContext { + return &evm.Context +} + // Interpreter returns the current interpreter func (evm *EVM) Interpreter() *EVMInterpreter { return evm.interpreter @@ -182,16 +237,19 @@ func (evm *EVM) Interpreter() *EVMInterpreter { // the necessary steps to create accounts and reverses the state in case of an // execution error or failed value transfer. func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { + if evm.isProhibitedWithTimestamp(addr) { + return nil, gas, vmerrs.ErrToAddrProhibited + } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { - return nil, gas, ErrDepth + return nil, gas, vmerrs.ErrDepth } // Fail if we're trying to transfer more than the available balance // Note: it is not possible for a negative value to be passed in here due to the fact // that [value] will be popped from the stack and decoded to a *big.Int, which will // always yield a positive result. if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { - return nil, gas, ErrInsufficientBalance + return nil, gas, vmerrs.ErrInsufficientBalance } snapshot := evm.StateDB.Snapshot() p, isPrecompile := evm.precompile(addr) @@ -231,7 +289,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } if isPrecompile { - ret, gas, err = p.Run(evm, caller, addr, input, gas, evm.interpreter.readOnly) + ret, gas, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly) } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. @@ -253,7 +311,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // when we're in homestead this also counts for code storage gas errors. if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != ErrExecutionReverted { + if err != vmerrs.ErrExecutionReverted { gas = 0 } // TODO: consider clearing up unused snapshots: @@ -265,9 +323,12 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // This allows the user transfer balance of a specified coinId in addition to a normal Call(). func (evm *EVM) CallExpert(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, coinID common.Hash, value2 *big.Int) (ret []byte, leftOverGas uint64, err error) { + if evm.isProhibitedWithTimestamp(addr) { + return nil, gas, vmerrs.ErrToAddrProhibited + } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { - return nil, gas, ErrDepth + return nil, gas, vmerrs.ErrDepth } // Fail if we're trying to transfer more than the available balance @@ -275,11 +336,11 @@ func (evm *EVM) CallExpert(caller ContractRef, addr common.Address, input []byte // that [value] will be popped from the stack and decoded to a *big.Int, which will // always yield a positive result. if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { - return nil, gas, ErrInsufficientBalance + return nil, gas, vmerrs.ErrInsufficientBalance } if value2.Sign() != 0 && !evm.Context.CanTransferMC(evm.StateDB, caller.Address(), addr, coinID, value2) { - return nil, gas, ErrInsufficientBalance + return nil, gas, vmerrs.ErrInsufficientBalance } snapshot := evm.StateDB.Snapshot() @@ -330,7 +391,7 @@ func (evm *EVM) CallExpert(caller ContractRef, addr common.Address, input []byte // when we're in homestead this also counts for code storage gas errors. if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != ErrExecutionReverted { + if err != vmerrs.ErrExecutionReverted { gas = 0 } // TODO: consider clearing up unused snapshots: @@ -348,9 +409,12 @@ func (evm *EVM) CallExpert(caller ContractRef, addr common.Address, input []byte // CallCode differs from Call in the sense that it executes the given address' // code with the caller as context. func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { + if evm.isProhibitedWithTimestamp(addr) { + return nil, gas, vmerrs.ErrToAddrProhibited + } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { - return nil, gas, ErrDepth + return nil, gas, vmerrs.ErrDepth } // Fail if we're trying to transfer more than the available balance // Note although it's noop to transfer X ether to caller itself. But @@ -360,7 +424,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // that [value] will be popped from the stack and decoded to a *big.Int, which will // always yield a positive result. if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { - return nil, gas, ErrInsufficientBalance + return nil, gas, vmerrs.ErrInsufficientBalance } var snapshot = evm.StateDB.Snapshot() @@ -374,7 +438,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = p.Run(evm, caller, addr, input, gas, evm.interpreter.readOnly) + ret, gas, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly) } else { addrCopy := addr // Initialise a new contract and set the code that is to be used by the EVM. @@ -386,7 +450,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != ErrExecutionReverted { + if err != vmerrs.ErrExecutionReverted { gas = 0 } } @@ -399,9 +463,12 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // DelegateCall differs from CallCode in the sense that it executes the given address' // code with the caller as context and the caller is set to the caller of the caller. func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { + if evm.isProhibitedWithTimestamp(addr) { + return nil, gas, vmerrs.ErrToAddrProhibited + } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { - return nil, gas, ErrDepth + return nil, gas, vmerrs.ErrDepth } var snapshot = evm.StateDB.Snapshot() @@ -415,7 +482,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = p.Run(evm, caller, addr, input, gas, evm.interpreter.readOnly) + ret, gas, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, evm.interpreter.readOnly) } else { addrCopy := addr // Initialise a new contract and make initialise the delegate values @@ -426,7 +493,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != ErrExecutionReverted { + if err != vmerrs.ErrExecutionReverted { gas = 0 } } @@ -438,9 +505,12 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // Opcodes that attempt to perform such modifications will result in exceptions // instead of performing the modifications. func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { + if evm.isProhibitedWithTimestamp(addr) { + return nil, gas, vmerrs.ErrToAddrProhibited + } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { - return nil, gas, ErrDepth + return nil, gas, vmerrs.ErrDepth } // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced @@ -464,7 +534,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte } if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = p.Run(evm, caller, addr, input, gas, true) + ret, gas, err = RunStatefulPrecompiledContract(p, evm, caller.Address(), addr, input, gas, true) } else { // At this point, we use a copy of address. If we don't, the go compiler will // leak the 'contract' to the outer scope, and make allocation for 'contract' @@ -482,7 +552,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != ErrExecutionReverted { + if err != vmerrs.ErrExecutionReverted { gas = 0 } } @@ -506,22 +576,22 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Depth check execution. Fail if we're trying to execute above the // limit. if evm.depth > int(params.CallCreateDepth) { - return nil, common.Address{}, gas, ErrDepth + return nil, common.Address{}, gas, vmerrs.ErrDepth } // Note: it is not possible for a negative value to be passed in here due to the fact // that [value] will be popped from the stack and decoded to a *big.Int, which will // always yield a positive result. if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { - return nil, common.Address{}, gas, ErrInsufficientBalance + return nil, common.Address{}, gas, vmerrs.ErrInsufficientBalance } - // If there is any collision with the Blackhole address, return an error instead + // If there is any collision with a prohibited address, return an error instead // of allowing the contract to be created. - if address == evm.Context.Coinbase { - return nil, common.Address{}, gas, ErrNoSenderBlackhole + if IsProhibited(address) { + return nil, common.Address{}, gas, vmerrs.ErrAddrProhibited } nonce := evm.StateDB.GetNonce(caller.Address()) if nonce+1 < nonce { - return nil, common.Address{}, gas, ErrNonceUintOverflow + return nil, common.Address{}, gas, vmerrs.ErrNonceUintOverflow } evm.StateDB.SetNonce(caller.Address(), nonce+1) // We add this to the access list _before_ taking a snapshot. Even if the creation fails, @@ -532,7 +602,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { - return nil, common.Address{}, 0, ErrContractAddressCollision + return nil, common.Address{}, 0, vmerrs.ErrContractAddressCollision } // Create a new account on the state snapshot := evm.StateDB.Snapshot() @@ -561,12 +631,12 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Check whether the max code size has been exceeded, assign err if the case. if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { - err = ErrMaxCodeSizeExceeded + err = vmerrs.ErrMaxCodeSizeExceeded } // Reject code starting with 0xEF if EIP-3541 is enabled. if err == nil && len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsApricotPhase3 { - err = ErrInvalidCode + err = vmerrs.ErrInvalidCode } // if the contract creation ran successfully and no errors were returned @@ -578,16 +648,16 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if contract.UseGas(createDataGas) { evm.StateDB.SetCode(address, ret) } else { - err = ErrCodeStoreOutOfGas + err = vmerrs.ErrCodeStoreOutOfGas } } // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in homestead this also counts for code storage gas errors. - if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { + if err != nil && (evm.chainRules.IsHomestead || err != vmerrs.ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) - if err != ErrExecutionReverted { + if err != vmerrs.ErrExecutionReverted { contract.UseGas(contract.Gas) } } @@ -620,3 +690,57 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment * // ChainConfig returns the environment's chain configuration func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } + +func (evm *EVM) NativeAssetCall(caller common.Address, input []byte, suppliedGas uint64, gasCost uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if suppliedGas < gasCost { + return nil, 0, vmerrs.ErrOutOfGas + } + remainingGas = suppliedGas - gasCost + + if readOnly { + return nil, remainingGas, vmerrs.ErrExecutionReverted + } + + to, assetID, assetAmount, callData, err := UnpackNativeAssetCallInput(input) + if err != nil { + return nil, remainingGas, vmerrs.ErrExecutionReverted + } + + // Note: it is not possible for a negative assetAmount to be passed in here due to the fact that decoding a + // byte slice into a *big.Int type will always return a positive value. + if assetAmount.Sign() != 0 && !evm.Context.CanTransferMC(evm.StateDB, caller, to, assetID, assetAmount) { + return nil, remainingGas, vmerrs.ErrInsufficientBalance + } + + snapshot := evm.StateDB.Snapshot() + + if !evm.StateDB.Exist(to) { + if remainingGas < params.CallNewAccountGas { + return nil, 0, vmerrs.ErrOutOfGas + } + remainingGas -= params.CallNewAccountGas + evm.StateDB.CreateAccount(to) + } + + // Increment the call depth which is restricted to 1024 + evm.depth++ + defer func() { evm.depth-- }() + + // Send [assetAmount] of [assetID] to [to] address + evm.Context.TransferMultiCoin(evm.StateDB, caller, to, assetID, assetAmount) + ret, remainingGas, err = evm.Call(AccountRef(caller), to, callData, remainingGas, new(big.Int)) + + // When an error was returned by the EVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in homestead this also counts for code storage gas errors. + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != vmerrs.ErrExecutionReverted { + remainingGas = 0 + } + // TODO: consider clearing up unused snapshots: + //} else { + // evm.StateDB.DiscardSnapshot(snapshot) + } + return ret, remainingGas, err +} diff --git a/core/vm/gas.go b/core/vm/gas.go index 1f356bbbbc..1a195acf2a 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -27,6 +27,7 @@ package vm import ( + "github.com/ava-labs/coreth/vmerrs" "github.com/holiman/uint256" ) @@ -56,7 +57,7 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (u } } if !callCost.IsUint64() { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return callCost.Uint64(), nil diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 1cc1b77af0..d361e23d68 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -30,6 +30,7 @@ import ( "errors" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" ) @@ -46,7 +47,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { // overflow. The constant 0x1FFFFFFFE0 is the highest number that can be used // without overflowing the gas calculation. if newMemSize > 0x1FFFFFFFE0 { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } newMemSizeWords := toWordSize(newMemSize) newMemSize = newMemSizeWords * 32 @@ -82,15 +83,15 @@ func memoryCopierGas(stackpos int) gasFunc { // And gas for copying data, charged per word at param.CopyGas words, overflow := stack.Back(stackpos).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, words); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return gas, nil } @@ -266,7 +267,7 @@ func makeGasLog(n uint64) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { requestedSize, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } gas, err := memoryGasCost(mem, memorySize) @@ -275,18 +276,18 @@ func makeGasLog(n uint64) gasFunc { } if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } var memorySizeGas uint64 if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return gas, nil } @@ -299,13 +300,13 @@ func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor } wordGas, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return gas, nil } @@ -333,13 +334,13 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS } wordGas, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return gas, nil } @@ -352,7 +353,7 @@ func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem overflow bool ) if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return gas, nil } @@ -365,7 +366,7 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor overflow bool ) if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return gas, nil } @@ -392,7 +393,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } var overflow bool if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) @@ -400,7 +401,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize return 0, err } if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return gas, nil } @@ -431,7 +432,7 @@ func gasCallExpertAP1(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m } var overflow bool if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) @@ -439,7 +440,7 @@ func gasCallExpertAP1(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m return 0, err } if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return gas, nil } @@ -457,14 +458,14 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory gas += params.CallValueTransferGas } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return gas, nil } @@ -480,7 +481,7 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me } var overflow bool if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return gas, nil } @@ -496,7 +497,7 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo } var overflow bool if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return gas, nil } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 9101ef84aa..5344460cf9 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -32,6 +32,7 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" "golang.org/x/crypto/sha3" @@ -350,14 +351,14 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte offset64, overflow := dataOffset.Uint64WithOverflow() if overflow { - return nil, ErrReturnDataOutOfBounds + return nil, vmerrs.ErrReturnDataOutOfBounds } // we can reuse dataOffset now (aliasing it for clarity) var end = dataOffset end.Add(&dataOffset, &length) end64, overflow := end.Uint64WithOverflow() if overflow || uint64(len(interpreter.returnData)) < end64 { - return nil, ErrReturnDataOutOfBounds + return nil, vmerrs.ErrReturnDataOutOfBounds } scope.Memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[offset64:end64]) return nil, nil @@ -539,7 +540,7 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { if interpreter.readOnly { - return nil, ErrWriteProtection + return nil, vmerrs.ErrWriteProtection } loc := scope.Stack.pop() val := scope.Stack.pop() @@ -554,7 +555,7 @@ func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } pos := scope.Stack.pop() if !scope.Contract.validJumpdest(&pos) { - return nil, ErrInvalidJump + return nil, vmerrs.ErrInvalidJump } *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop return nil, nil @@ -567,7 +568,7 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by pos, cond := scope.Stack.pop(), scope.Stack.pop() if !cond.IsZero() { if !scope.Contract.validJumpdest(&pos) { - return nil, ErrInvalidJump + return nil, vmerrs.ErrInvalidJump } *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop } @@ -595,7 +596,7 @@ func opGas(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { if interpreter.readOnly { - return nil, ErrWriteProtection + return nil, vmerrs.ErrWriteProtection } var ( value = scope.Stack.pop() @@ -621,9 +622,9 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must // ignore this error and pretend the operation was successful. - if interpreter.evm.chainRules.IsHomestead && suberr == ErrCodeStoreOutOfGas { + if interpreter.evm.chainRules.IsHomestead && suberr == vmerrs.ErrCodeStoreOutOfGas { stackvalue.Clear() - } else if suberr != nil && suberr != ErrCodeStoreOutOfGas { + } else if suberr != nil && suberr != vmerrs.ErrCodeStoreOutOfGas { stackvalue.Clear() } else { stackvalue.SetBytes(addr.Bytes()) @@ -631,7 +632,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b scope.Stack.push(&stackvalue) scope.Contract.Gas += returnGas - if suberr == ErrExecutionReverted { + if suberr == vmerrs.ErrExecutionReverted { interpreter.returnData = res // set REVERT data to return data buffer return res, nil } @@ -641,7 +642,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { if interpreter.readOnly { - return nil, ErrWriteProtection + return nil, vmerrs.ErrWriteProtection } var ( endowment = scope.Stack.pop() @@ -672,7 +673,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] scope.Stack.push(&stackvalue) scope.Contract.Gas += returnGas - if suberr == ErrExecutionReverted { + if suberr == vmerrs.ErrExecutionReverted { interpreter.returnData = res // set REVERT data to return data buffer return res, nil } @@ -693,7 +694,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) if interpreter.readOnly && !value.IsZero() { - return nil, ErrWriteProtection + return nil, vmerrs.ErrWriteProtection } var bigVal = big0 //TODO: use uint256.Int instead of converting with toBig() @@ -712,7 +713,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt temp.SetOne() } stack.push(&temp) - if err == nil || err == ErrExecutionReverted { + if err == nil || err == vmerrs.ErrExecutionReverted { ret = common.CopyBytes(ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } @@ -736,7 +737,7 @@ func opCallExpert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) if interpreter.readOnly && !value.IsZero() { - return nil, ErrWriteProtection + return nil, vmerrs.ErrWriteProtection } var bigVal = big0 //TODO: use uint256.Int instead of converting with toBig() @@ -763,7 +764,7 @@ func opCallExpert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) temp.SetOne() } stack.push(&temp) - if err == nil || err == ErrExecutionReverted { + if err == nil || err == vmerrs.ErrExecutionReverted { ret = common.CopyBytes(ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } @@ -798,7 +799,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ temp.SetOne() } stack.push(&temp) - if err == nil || err == ErrExecutionReverted { + if err == nil || err == vmerrs.ErrExecutionReverted { ret = common.CopyBytes(ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } @@ -827,7 +828,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext temp.SetOne() } stack.push(&temp) - if err == nil || err == ErrExecutionReverted { + if err == nil || err == vmerrs.ErrExecutionReverted { ret = common.CopyBytes(ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } @@ -856,7 +857,7 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) temp.SetOne() } stack.push(&temp) - if err == nil || err == ErrExecutionReverted { + if err == nil || err == vmerrs.ErrExecutionReverted { ret = common.CopyBytes(ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } @@ -878,7 +879,7 @@ func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) interpreter.returnData = ret - return ret, ErrExecutionReverted + return ret, vmerrs.ErrExecutionReverted } func opUndefined(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { @@ -891,7 +892,7 @@ func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { if interpreter.readOnly { - return nil, ErrWriteProtection + return nil, vmerrs.ErrWriteProtection } beneficiary := scope.Stack.pop() balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) @@ -910,7 +911,7 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext func makeLog(size int) executionFunc { return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { if interpreter.readOnly { - return nil, ErrWriteProtection + return nil, vmerrs.ErrWriteProtection } topics := make([]common.Hash, size) stack := scope.Stack diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 653d64ca23..1aef682c51 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -29,6 +29,7 @@ package vm import ( "hash" + "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/log" @@ -229,7 +230,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } if !contract.UseGas(cost) { - return nil, ErrOutOfGas + return nil, vmerrs.ErrOutOfGas } if operation.dynamicGas != nil { @@ -242,12 +243,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( if operation.memorySize != nil { memSize, overflow := operation.memorySize(stack) if overflow { - return nil, ErrGasUintOverflow + return nil, vmerrs.ErrGasUintOverflow } // memory is expanded in words of 32 bytes. Gas // is also calculated in words. if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow { - return nil, ErrGasUintOverflow + return nil, vmerrs.ErrGasUintOverflow } } // Consume the gas and return an error if not enough gas is available. @@ -256,7 +257,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) cost += dynamicCost // for tracing if err != nil || !contract.UseGas(dynamicCost) { - return nil, ErrOutOfGas + return nil, vmerrs.ErrOutOfGas } if memorySize > 0 { mem.Resize(memorySize) diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index adfe772941..07e6e07eb6 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -30,6 +30,7 @@ import ( "errors" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" ) @@ -128,7 +129,7 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo var overflow bool // We charge (cold-warm), since 'warm' is already charged as constantGas if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { - return 0, ErrGasUintOverflow + return 0, vmerrs.ErrGasUintOverflow } return gas, nil } @@ -167,7 +168,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { // Charge the remaining difference here already, to correctly calculate available // gas for call if !contract.UseGas(coldCost) { - return 0, ErrOutOfGas + return 0, vmerrs.ErrOutOfGas } } // Now call the old calculator, which takes into account diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index bf093c94bc..1b1640c244 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -45,6 +45,7 @@ import ( "github.com/ava-labs/coreth/eth/tracers/logger" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/rpc" + "github.com/ava-labs/coreth/vmerrs" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -1112,7 +1113,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr return 0, err } if failed { - if result != nil && result.Err != vm.ErrOutOfGas { + if result != nil && result.Err != vmerrs.ErrOutOfGas { if len(result.Revert()) > 0 { return 0, newRevertError(result) } diff --git a/miner/worker.go b/miner/worker.go index eed0549bfe..7de8f7ce9b 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -45,6 +45,7 @@ import ( "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -167,6 +168,8 @@ func (w *worker) commitNewWork() (*types.Block, error) { if w.chainConfig.DAOForkSupport && w.chainConfig.DAOForkBlock != nil && w.chainConfig.DAOForkBlock.Cmp(header.Number) == 0 { misc.ApplyDAOHardFork(env.state) } + // Configure any stateful precompiles that should go into effect during this block. + w.chainConfig.CheckConfigurePrecompiles(new(big.Int).SetUint64(parent.Time()), types.NewBlockWithHeader(header), env.state) // Fill the block with all available pending transactions. pending := w.eth.TxPool().Pending(true) @@ -284,7 +287,10 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP // Pop the unsupported transaction without shifting in the next from the account log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type()) txs.Pop() - + case errors.Is(err, vmerrs.ErrToAddrProhibited): + log.Warn("Tx dropped: failed verification", "tx", tx.Hash(), "sender", from, "data", tx.Data(), "err", err) + w.eth.TxPool().RemoveTx(tx.Hash()) + txs.Pop() default: // Strange error, discard the transaction and get the next in line (note, the // nonce-too-high clause will prevent us from executing in vain). diff --git a/params/config.go b/params/config.go index 4a220fcf90..eee9dc7375 100644 --- a/params/config.go +++ b/params/config.go @@ -32,6 +32,8 @@ import ( "math/big" "time" + "github.com/ava-labs/coreth/precompile" + "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" ) @@ -68,6 +70,8 @@ var ( ApricotPhase3BlockTimestamp: big.NewInt(time.Date(2021, time.August, 24, 14, 0, 0, 0, time.UTC).Unix()), ApricotPhase4BlockTimestamp: big.NewInt(time.Date(2021, time.September, 22, 21, 0, 0, 0, time.UTC).Unix()), ApricotPhase5BlockTimestamp: big.NewInt(time.Date(2021, time.December, 2, 18, 0, 0, 0, time.UTC).Unix()), + ApricotPhasePre6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 5, 1, 30, 0, 0, time.UTC).Unix()), + ApricotPhase6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()), } // AvalancheFujiChainConfig is the configuration for the Fuji Test Network @@ -90,6 +94,8 @@ var ( ApricotPhase3BlockTimestamp: big.NewInt(time.Date(2021, time.August, 16, 19, 0, 0, 0, time.UTC).Unix()), ApricotPhase4BlockTimestamp: big.NewInt(time.Date(2021, time.September, 16, 21, 0, 0, 0, time.UTC).Unix()), ApricotPhase5BlockTimestamp: big.NewInt(time.Date(2021, time.November, 24, 15, 0, 0, 0, time.UTC).Unix()), + ApricotPhasePre6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()), + ApricotPhase6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()), } // AvalancheLocalChainConfig is the configuration for the Avalanche Local Network @@ -112,16 +118,21 @@ var ( ApricotPhase3BlockTimestamp: big.NewInt(0), ApricotPhase4BlockTimestamp: big.NewInt(0), ApricotPhase5BlockTimestamp: big.NewInt(0), + ApricotPhasePre6BlockTimestamp: big.NewInt(0), + ApricotPhase6BlockTimestamp: big.NewInt(0), } - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)} - TestLaunchConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil} - TestApricotPhase1Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil} - TestApricotPhase2Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil} - TestApricotPhase3Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil} - TestApricotPhase4Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil} - TestApricotPhase5Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)} - TestRules = TestChainConfig.AvalancheRules(new(big.Int), new(big.Int)) + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)} + TestLaunchConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil} + TestApricotPhase1Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil} + TestApricotPhase2Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil} + TestApricotPhase3Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil} + TestApricotPhase4Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil} + TestApricotPhase5Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil} + TestApricotPhasePre6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil} + TestApricotPhase6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)} + TestBlueberryChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)} + TestRules = TestChainConfig.AvalancheRules(new(big.Int), new(big.Int)) ) // ChainConfig is the core config which determines the blockchain settings. @@ -161,6 +172,10 @@ type ChainConfig struct { ApricotPhase4BlockTimestamp *big.Int `json:"apricotPhase4BlockTimestamp,omitempty"` // Apricot Phase 5 introduces a batch of atomic transactions with a maximum atomic gas limit per block. (nil = no fork, 0 = already activated) ApricotPhase5BlockTimestamp *big.Int `json:"apricotPhase5BlockTimestamp,omitempty"` + // Apricot Phase Pre-6 deprecates the NativeAssetCall precompile (soft). (nil = no fork, 0 = already activated) + ApricotPhasePre6BlockTimestamp *big.Int `json:"apricotPhasePre6BlockTimestamp,omitempty"` + // Apricot Phase 6 deprecates the NativeAssetBalance and NativeAssetCall precompiles. (nil = no fork, 0 = already activated) + ApricotPhase6BlockTimestamp *big.Int `json:"apricotPhase6BlockTimestamp,omitempty"` } // String implements the fmt.Stringer interface. @@ -188,54 +203,54 @@ func (c *ChainConfig) String() string { // IsHomestead returns whether num is either equal to the homestead block or greater. func (c *ChainConfig) IsHomestead(num *big.Int) bool { - return isForked(c.HomesteadBlock, num) + return utils.IsForked(c.HomesteadBlock, num) } // IsDAOFork returns whether num is either equal to the DAO fork block or greater. func (c *ChainConfig) IsDAOFork(num *big.Int) bool { - return isForked(c.DAOForkBlock, num) + return utils.IsForked(c.DAOForkBlock, num) } // IsEIP150 returns whether num is either equal to the EIP150 fork block or greater. func (c *ChainConfig) IsEIP150(num *big.Int) bool { - return isForked(c.EIP150Block, num) + return utils.IsForked(c.EIP150Block, num) } // IsEIP155 returns whether num is either equal to the EIP155 fork block or greater. func (c *ChainConfig) IsEIP155(num *big.Int) bool { - return isForked(c.EIP155Block, num) + return utils.IsForked(c.EIP155Block, num) } // IsEIP158 returns whether num is either equal to the EIP158 fork block or greater. func (c *ChainConfig) IsEIP158(num *big.Int) bool { - return isForked(c.EIP158Block, num) + return utils.IsForked(c.EIP158Block, num) } // IsByzantium returns whether num is either equal to the Byzantium fork block or greater. func (c *ChainConfig) IsByzantium(num *big.Int) bool { - return isForked(c.ByzantiumBlock, num) + return utils.IsForked(c.ByzantiumBlock, num) } // IsConstantinople returns whether num is either equal to the Constantinople fork block or greater. func (c *ChainConfig) IsConstantinople(num *big.Int) bool { - return isForked(c.ConstantinopleBlock, num) + return utils.IsForked(c.ConstantinopleBlock, num) } // IsMuirGlacier returns whether num is either equal to the Muir Glacier (EIP-2384) fork block or greater. func (c *ChainConfig) IsMuirGlacier(num *big.Int) bool { - return isForked(c.MuirGlacierBlock, num) + return utils.IsForked(c.MuirGlacierBlock, num) } // IsPetersburg returns whether num is either // - equal to or greater than the PetersburgBlock fork block, // - OR is nil, and Constantinople is active func (c *ChainConfig) IsPetersburg(num *big.Int) bool { - return isForked(c.PetersburgBlock, num) || c.PetersburgBlock == nil && isForked(c.ConstantinopleBlock, num) + return utils.IsForked(c.PetersburgBlock, num) || c.PetersburgBlock == nil && utils.IsForked(c.ConstantinopleBlock, num) } // IsIstanbul returns whether num is either equal to the Istanbul fork block or greater. func (c *ChainConfig) IsIstanbul(num *big.Int) bool { - return isForked(c.IstanbulBlock, num) + return utils.IsForked(c.IstanbulBlock, num) } // Avalanche Upgrades: @@ -243,31 +258,43 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool { // IsApricotPhase1 returns whether [blockTimestamp] represents a block // with a timestamp after the Apricot Phase 1 upgrade time. func (c *ChainConfig) IsApricotPhase1(blockTimestamp *big.Int) bool { - return isForked(c.ApricotPhase1BlockTimestamp, blockTimestamp) + return utils.IsForked(c.ApricotPhase1BlockTimestamp, blockTimestamp) } // IsApricotPhase2 returns whether [blockTimestamp] represents a block // with a timestamp after the Apricot Phase 2 upgrade time. func (c *ChainConfig) IsApricotPhase2(blockTimestamp *big.Int) bool { - return isForked(c.ApricotPhase2BlockTimestamp, blockTimestamp) + return utils.IsForked(c.ApricotPhase2BlockTimestamp, blockTimestamp) } // IsApricotPhase3 returns whether [blockTimestamp] represents a block // with a timestamp after the Apricot Phase 3 upgrade time. func (c *ChainConfig) IsApricotPhase3(blockTimestamp *big.Int) bool { - return isForked(c.ApricotPhase3BlockTimestamp, blockTimestamp) + return utils.IsForked(c.ApricotPhase3BlockTimestamp, blockTimestamp) } // IsApricotPhase4 returns whether [blockTimestamp] represents a block // with a timestamp after the Apricot Phase 4 upgrade time. func (c *ChainConfig) IsApricotPhase4(blockTimestamp *big.Int) bool { - return isForked(c.ApricotPhase4BlockTimestamp, blockTimestamp) + return utils.IsForked(c.ApricotPhase4BlockTimestamp, blockTimestamp) } // IsApricotPhase5 returns whether [blockTimestamp] represents a block // with a timestamp after the Apricot Phase 5 upgrade time. func (c *ChainConfig) IsApricotPhase5(blockTimestamp *big.Int) bool { - return isForked(c.ApricotPhase5BlockTimestamp, blockTimestamp) + return utils.IsForked(c.ApricotPhase5BlockTimestamp, blockTimestamp) +} + +// IsApricotPhasePre6 returns whether [blockTimestamp] represents a block +// with a timestamp after the Apricot Phase Pre 6 upgrade time. +func (c *ChainConfig) IsApricotPhasePre6(blockTimestamp *big.Int) bool { + return utils.IsForked(c.ApricotPhasePre6BlockTimestamp, blockTimestamp) +} + +// IsApricotPhase6 returns whether [blockTimestamp] represents a block +// with a timestamp after the Apricot Phase 6 upgrade time. +func (c *ChainConfig) IsApricotPhase6(blockTimestamp *big.Int) bool { + return utils.IsForked(c.ApricotPhase6BlockTimestamp, blockTimestamp) } // CheckCompatible checks whether scheduled fork transitions have been imported @@ -433,15 +460,7 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headHeight *big.Int, // isForkIncompatible returns true if a fork scheduled at s1 cannot be rescheduled to // block s2 because head is already past the fork. func isForkIncompatible(s1, s2, head *big.Int) bool { - return (isForked(s1, head) || isForked(s2, head)) && !configNumEqual(s1, s2) -} - -// isForked returns whether a fork scheduled at block s is active at the given head block. -func isForked(s, head *big.Int) bool { - if s == nil || head == nil { - return false - } - return s.Cmp(head) <= 0 + return (utils.IsForked(s1, head) || utils.IsForked(s2, head)) && !configNumEqual(s1, s2) } func configNumEqual(x, y *big.Int) bool { @@ -497,6 +516,13 @@ type Rules struct { // Rules for Avalanche releases IsApricotPhase1, IsApricotPhase2, IsApricotPhase3, IsApricotPhase4, IsApricotPhase5 bool + IsApricotPhasePre6, IsApricotPhase6 bool + + // Precompiles maps addresses to stateful precompiled contracts that are enabled + // for this rule set. + // Note: none of these addresses should conflict with the address space used by + // any existing precompiles. + Precompiles map[common.Address]precompile.StatefulPrecompiledContract } // Rules ensures c's ChainID is not nil. @@ -528,5 +554,40 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { rules.IsApricotPhase3 = c.IsApricotPhase3(blockTimestamp) rules.IsApricotPhase4 = c.IsApricotPhase4(blockTimestamp) rules.IsApricotPhase5 = c.IsApricotPhase5(blockTimestamp) + rules.IsApricotPhasePre6 = c.IsApricotPhasePre6(blockTimestamp) + rules.IsApricotPhase6 = c.IsApricotPhase6(blockTimestamp) + + // Initialize the stateful precompiles that should be enabled at [blockTimestamp]. + rules.Precompiles = make(map[common.Address]precompile.StatefulPrecompiledContract) + for _, config := range c.enabledStatefulPrecompiles() { + if utils.IsForked(config.Timestamp(), blockTimestamp) { + rules.Precompiles[config.Address()] = config.Contract() + } + } + return rules } + +// enabledStatefulPrecompiles returns a list of stateful precompile configs in the order that they are enabled +// by block timestamp. +// Note: the return value does not include the native precompiles [nativeAssetCall] and [nativeAssetBalance]. +// These are handled in [evm.precompile] directly. +func (c *ChainConfig) enabledStatefulPrecompiles() []precompile.StatefulPrecompileConfig { + statefulPrecompileConfigs := make([]precompile.StatefulPrecompileConfig, 0) + + return statefulPrecompileConfigs +} + +// CheckConfigurePrecompiles checks if any of the precompiles specified in the chain config are enabled by the block +// transition from [parentTimestamp] to the timestamp set in [blockContext]. If this is the case, it calls [Configure] +// to apply the necessary state transitions for the upgrade. +// This function is called: +// - within genesis setup to configure the starting state for precompiles enabled at genesis, +// - during block processing to update the state before processing the given block. +func (c *ChainConfig) CheckConfigurePrecompiles(parentTimestamp *big.Int, blockContext precompile.BlockContext, statedb precompile.StateDB) { + // Iterate the enabled stateful precompiles and configure them if needed + for _, config := range c.enabledStatefulPrecompiles() { + precompile.CheckConfigure(c, parentTimestamp, blockContext, config, statedb) + } +} + diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index a27aef1396..33e14c1bd3 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -1080,7 +1080,7 @@ func (vm *VM) VerifyHeightIndex() error { func (vm *VM) GetBlockIDAtHeight(blkHeight uint64) (ids.ID, error) { ethBlock := vm.chain.GetBlockByNumber(blkHeight) if ethBlock == nil { - return ids.ID{}, fmt.Errorf("could not find block at height: %d", blkHeight) + return ids.ID{}, database.ErrNotFound } return ids.ID(ethBlock.Hash()), nil diff --git a/precompile/contract.go b/precompile/contract.go new file mode 100644 index 0000000000..4782ef89b9 --- /dev/null +++ b/precompile/contract.go @@ -0,0 +1,143 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package precompile + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + selectorLen = 4 +) + +type RunStatefulPrecompileFunc func(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) + +// PrecompileAccessibleState defines the interface exposed to stateful precompile contracts +type PrecompileAccessibleState interface { + GetStateDB() StateDB + GetBlockContext() BlockContext + NativeAssetCall(caller common.Address, input []byte, suppliedGas uint64, gasGost uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) +} + +// BlockContext defines an interface that provides information to a stateful precompile +// about the block that activates the upgrade. The precompile can access this information +// to initialize its state. +type BlockContext interface { + Number() *big.Int + Timestamp() *big.Int +} + +// ChainContext defines an interface that provides information to a stateful precompile +// about the chain configuration. The precompile can access this information to initialize +// its state. +type ChainConfig interface { + // Note: None of the existing stateful precompiles currently access chain config information + // in Configure so this interface is empty. +} + +// StateDB is the interface for accessing EVM state +type StateDB interface { + GetState(common.Address, common.Hash) common.Hash + SetState(common.Address, common.Hash, common.Hash) + + SetCode(common.Address, []byte) + + SetNonce(common.Address, uint64) + GetNonce(common.Address) uint64 + + GetBalance(common.Address) *big.Int + AddBalance(common.Address, *big.Int) + SubBalance(common.Address, *big.Int) + + SubBalanceMultiCoin(common.Address, common.Hash, *big.Int) + AddBalanceMultiCoin(common.Address, common.Hash, *big.Int) + GetBalanceMultiCoin(common.Address, common.Hash) *big.Int + + CreateAccount(common.Address) + Exist(common.Address) bool +} + +// StatefulPrecompiledContract is the interface for executing a precompiled contract +type StatefulPrecompiledContract interface { + // Run executes the precompiled contract. + Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) +} + +// statefulPrecompileFunction defines a function implemented by a stateful precompile +type statefulPrecompileFunction struct { + // selector is the 4 byte function selector for this function + // This should be calculated from the function signature using CalculateFunctionSelector + selector []byte + // execute is performed when this function is selected + execute RunStatefulPrecompileFunc +} + +// newStatefulPrecompileFunction creates a stateful precompile function with the given arguments +//nolint:unused,deadcode +func newStatefulPrecompileFunction(selector []byte, execute RunStatefulPrecompileFunc) *statefulPrecompileFunction { + return &statefulPrecompileFunction{ + selector: selector, + execute: execute, + } +} + +// statefulPrecompileWithFunctionSelectors implements StatefulPrecompiledContract by using 4 byte function selectors to pass +// off responsibilities to internal execution functions. +// Note: because we only ever read from [functions] there no lock is required to make it thread-safe. +type statefulPrecompileWithFunctionSelectors struct { + fallback *statefulPrecompileFunction + functions map[string]*statefulPrecompileFunction +} + +// newStatefulPrecompileWithFunctionSelectors generates new StatefulPrecompile using [functions] as the available functions and [fallback] +// as an optional fallback if there is no input data. Note: the selector of [fallback] will be ignored, so it is required to be left empty. +//nolint:unused,deadcode +func newStatefulPrecompileWithFunctionSelectors(fallback *statefulPrecompileFunction, functions []*statefulPrecompileFunction) StatefulPrecompiledContract { + // Ensure that if a fallback is present, it does not have a mistakenly populated function selector. + if fallback != nil && len(fallback.selector) != 0 { + panic(fmt.Errorf("fallback function cannot specify non-zero length function selector")) + } + + // Construct the contract and populate [functions]. + contract := &statefulPrecompileWithFunctionSelectors{ + fallback: fallback, + functions: make(map[string]*statefulPrecompileFunction), + } + for _, function := range functions { + _, exists := contract.functions[string(function.selector)] + if exists { + panic(fmt.Errorf("cannot create stateful precompile with duplicated function selector: %q", function.selector)) + } + contract.functions[string(function.selector)] = function + } + + return contract +} + +// Run selects the function using the 4 byte function selector at the start of the input and executes the underlying function on the +// given arguments. +func (s *statefulPrecompileWithFunctionSelectors) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + // If there is no input data present, call the fallback function if present. + if len(input) == 0 && s.fallback != nil { + return s.fallback.execute(accessibleState, caller, addr, nil, suppliedGas, readOnly) + } + + // Otherwise, an unexpected input size will result in an error. + if len(input) < selectorLen { + return nil, suppliedGas, fmt.Errorf("missing function selector to precompile - input length (%d)", len(input)) + } + + // Use the function selector to grab the correct function + selector := input[:selectorLen] + functionInput := input[selectorLen:] + function, ok := s.functions[string(selector)] + if !ok { + return nil, suppliedGas, fmt.Errorf("invalid function selector %#x", selector) + } + + return function.execute(accessibleState, caller, addr, functionInput, suppliedGas, readOnly) +} \ No newline at end of file diff --git a/precompile/params.go b/precompile/params.go new file mode 100644 index 0000000000..f332d11c61 --- /dev/null +++ b/precompile/params.go @@ -0,0 +1,47 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package precompile + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" +) + +// Gas costs for stateful precompiles +// can be added here eg. +// const MintGasCost = 30_000 + +// AddressRange represents a continuous range of addresses +type AddressRange struct { + Start common.Address + End common.Address +} + +// Contains returns true iff [addr] is contained within the (inclusive) +func (a *AddressRange) Contains(addr common.Address) bool { + addrBytes := addr.Bytes() + return bytes.Compare(addrBytes, a.Start[:]) >= 0 && bytes.Compare(addrBytes, a.End[:]) <= 0 +} + +// Designated addresses of stateful precompiles +// Note: it is important that none of these addresses conflict with each other or any other precompiles +// in core/vm/contracts.go. +// We start at 0x0100000000000000000000000000000000000000 and will increment by 1 from here to reduce +// the risk of conflicts. +var ( + UsedAddresses = []common.Address{ + // precompile contract addresses can be added here + } + + // ReservedRanges contains addresses ranges that are reserved + // for precompiles and cannot be used as EOA or deployed contracts. + ReservedRanges = []AddressRange{ + { + // reserved for coreth precompiles + common.HexToAddress("0x0100000000000000000000000000000000000000"), + common.HexToAddress("0x01000000000000000000000000000000000000ff"), + }, + } +) \ No newline at end of file diff --git a/precompile/stateful_precompile_config.go b/precompile/stateful_precompile_config.go new file mode 100644 index 0000000000..b262fcdba6 --- /dev/null +++ b/precompile/stateful_precompile_config.go @@ -0,0 +1,59 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package precompile + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ava-labs/coreth/utils" +) + +// StatefulPrecompileConfig defines the interface for a stateful precompile to +type StatefulPrecompileConfig interface { + // Address returns the address where the stateful precompile is accessible. + Address() common.Address + // Timestamp returns the timestamp at which this stateful precompile should be enabled. + // 1) 0 indicates that the precompile should be enabled from genesis. + // 2) n indicates that the precompile should be enabled in the first block with timestamp >= [n]. + // 3) nil indicates that the precompile is never enabled. + Timestamp() *big.Int + // Configure is called on the first block where the stateful precompile should be enabled. + // This allows the stateful precompile to configure its own state via [StateDB] as necessary. + // This function must be deterministic since it will impact the EVM state. If a change to the + // config causes a change to the state modifications made in Configure, then it cannot be safely + // made to the config after the network upgrade has gone into effect. + // + // Configure is called on the first block where the stateful precompile should be enabled. This + // provides the config the ability to set its initial state and should only modify the state within + // its own address space. + Configure(ChainConfig, StateDB, BlockContext) + // Contract returns a thread-safe singleton that can be used as the StatefulPrecompiledContract when + // this config is enabled. + Contract() StatefulPrecompiledContract +} + +// CheckConfigure checks if [config] is activated by the transition from block at [parentTimestamp] to the timestamp +// set in [blockContext]. +// If it does, then it calls Configure on [precompileConfig] to make the necessary state update to enable the StatefulPrecompile. +// Note: this function is called within genesis to configure the starting state if [precompileConfig] specifies that it should be +// configured at genesis, or happens during block processing to update the state before processing the given block. +// TODO: add ability to call Configure at different timestamps, so that developers can easily re-configure by updating the +// stateful precompile config. +// Assumes that [config] is non-nil. +func CheckConfigure(chainConfig ChainConfig, parentTimestamp *big.Int, blockContext BlockContext, precompileConfig StatefulPrecompileConfig, state StateDB) { + forkTimestamp := precompileConfig.Timestamp() + // If the network upgrade goes into effect within this transition, configure the stateful precompile + if utils.IsForkTransition(forkTimestamp, parentTimestamp, blockContext.Timestamp()) { + // Set the nonce of the precompile's address (as is done when a contract is created) to ensure + // that it is marked as non-empty and will not be cleaned up when the statedb is finalized. + state.SetNonce(precompileConfig.Address(), 1) + // Set the code of the precompile's address to a non-zero length byte slice to ensure that the precompile + // can be called from within Solidity contracts. Solidity adds a check before invoking a contract to ensure + // that it does not attempt to invoke a non-existent contract. + state.SetCode(precompileConfig.Address(), []byte{0x1}) + precompileConfig.Configure(chainConfig, state, blockContext) + } +} diff --git a/precompile/utils.go b/precompile/utils.go new file mode 100644 index 0000000000..758b29d5a6 --- /dev/null +++ b/precompile/utils.go @@ -0,0 +1,34 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package precompile + +import ( + "fmt" + "regexp" + + "github.com/ava-labs/coreth/vmerrs" + "github.com/ethereum/go-ethereum/crypto" +) + +var functionSignatureRegex = regexp.MustCompile(`[\w]+\(((([\w]+)?)|((([\w]+),)+([\w]+)))\)`) + +// CalculateFunctionSelector returns the 4 byte function selector that results from [functionSignature] +// Ex. the function setBalance(addr address, balance uint256) should be passed in as the string: +// "setBalance(address,uint256)" +func CalculateFunctionSelector(functionSignature string) []byte { + if !functionSignatureRegex.MatchString(functionSignature) { + panic(fmt.Errorf("invalid function signature: %q", functionSignature)) + } + hash := crypto.Keccak256([]byte(functionSignature)) + return hash[:4] +} + +// deductGas checks if [suppliedGas] is sufficient against [requiredGas] and deducts [requiredGas] from [suppliedGas]. +//nolint:unused,deadcode +func deductGas(suppliedGas uint64, requiredGas uint64) (uint64, error) { + if suppliedGas < requiredGas { + return 0, vmerrs.ErrOutOfGas + } + return suppliedGas - requiredGas, nil +} \ No newline at end of file diff --git a/utils/fork.go b/utils/fork.go new file mode 100644 index 0000000000..4a08b76d1d --- /dev/null +++ b/utils/fork.go @@ -0,0 +1,24 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package utils + +import "math/big" + +// IsForked returns whether a fork scheduled at block s is active at the given head block. +// Note: [s] and [head] can be either a block number or a block timestamp. +func IsForked(s, head *big.Int) bool { + if s == nil || head == nil { + return false + } + return s.Cmp(head) <= 0 +} + +// IsForkTransition returns true if [fork] activates during the transition from [parent] +// to [current]. +// Note: this works for both block number and timestamp activated forks. +func IsForkTransition(fork *big.Int, parent *big.Int, current *big.Int) bool { + parentForked := IsForked(fork, parent) + currentForked := IsForked(fork, current) + return !parentForked && currentForked +} \ No newline at end of file diff --git a/vmerrs/vmerrs.go b/vmerrs/vmerrs.go new file mode 100644 index 0000000000..c47580880b --- /dev/null +++ b/vmerrs/vmerrs.go @@ -0,0 +1,51 @@ +// (c) 2019-2020, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vmerrs + +import ( + "errors" +) + +// List evm execution errors +var ( + ErrOutOfGas = errors.New("out of gas") + ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") + ErrDepth = errors.New("max call depth exceeded") + ErrInsufficientBalance = errors.New("insufficient balance for transfer") + ErrContractAddressCollision = errors.New("contract address collision") + ErrExecutionReverted = errors.New("execution reverted") + ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") + ErrInvalidJump = errors.New("invalid jump destination") + ErrWriteProtection = errors.New("write protection") + ErrReturnDataOutOfBounds = errors.New("return data out of bounds") + ErrGasUintOverflow = errors.New("gas uint64 overflow") + ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") + ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + ErrAddrProhibited = errors.New("prohibited address cannot be sender or created contract address") + ErrToAddrProhibited = errors.New("prohibited address cannot be called") +) + From c7931da28426119a13811ff6de2659f50c916194 Mon Sep 17 00:00:00 2001 From: pdrobnjak Date: Wed, 7 Sep 2022 11:39:08 +0200 Subject: [PATCH 2/9] Apricot Phase Post 6 support --- core/state_transition.go | 2 +- core/vm/contracts.go | 1 + core/vm/evm.go | 41 ++++++++++++++++++++++------------ core/vm/instructions.go | 30 +++++++++++++++++++++++-- miner/worker.go | 2 +- params/config.go | 48 +++++++++++++++++++++++++++++----------- vmerrs/vmerrs.go | 4 ++-- 7 files changed, 95 insertions(+), 33 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index ee6ad0cb75..5af28d9980 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -341,7 +341,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) } - if errors.Is(vmerr, vmerrs.ErrToAddrProhibited) { + if errors.Is(vmerr, vmerrs.ErrToAddrProhibitedSoft) { return &ExecutionResult{ UsedGas: st.gasUsed(), Err: vmerr, diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 76a3dbcf4d..a380d5d8cd 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -157,6 +157,7 @@ func init() { addrsList := append(PrecompiledAddressesHomestead, PrecompiledAddressesByzantium...) addrsList = append(addrsList, PrecompiledAddressesIstanbul...) addrsList = append(addrsList, PrecompiledAddressesApricotPhase2...) + addrsList = append(addrsList, PrecompiledAddressesApricotPhase6...) for _, k := range addrsList { PrecompileAllNativeAddresses[k] = struct{}{} } diff --git a/core/vm/evm.go b/core/vm/evm.go index 632811b1d5..ec1dab6d18 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -59,11 +59,22 @@ func IsProhibited(addr common.Address) bool { return false } -func (evm *EVM) isProhibitedWithTimestamp(addr common.Address) bool { - if !evm.chainRules.IsApricotPhasePre6 || evm.chainRules.IsApricotPhase6 { - return false +func (evm *EVM) isProhibitedWithTimestamp(addr common.Address) error { + if addr != NativeAssetCallAddr { + return nil + } + + // Return error depending on the phase + switch { + case evm.chainRules.IsApricotPhasePost6: // If we are in the soft fork, return the soft error + return vmerrs.ErrToAddrProhibitedSoft + case evm.chainRules.IsApricotPhase6: // If we are in Phase6, return nil + return nil + case evm.chainRules.IsApricotPhasePre6: // If we are in PrePhase6, return Prohibited6 + return vmerrs.ErrToAddrProhibited6 + default: // Prior to Pre6, don't alter behavior at all + return nil } - return addr == NativeAssetCallAddr } // emptyCodeHash is used by create to ensure deployment is disallowed to already @@ -85,6 +96,8 @@ type ( func (evm *EVM) precompile(addr common.Address) (precompile.StatefulPrecompiledContract, bool) { var precompiles map[common.Address]precompile.StatefulPrecompiledContract switch { + case evm.chainRules.IsApricotPhase6: // This stayed the same + precompiles = PrecompiledContractsApricotPhase2 case evm.chainRules.IsApricotPhase2: precompiles = PrecompiledContractsApricotPhase2 case evm.chainRules.IsIstanbul: @@ -237,8 +250,8 @@ func (evm *EVM) Interpreter() *EVMInterpreter { // the necessary steps to create accounts and reverses the state in case of an // execution error or failed value transfer. func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { - if evm.isProhibitedWithTimestamp(addr) { - return nil, gas, vmerrs.ErrToAddrProhibited + if prohibitErr := evm.isProhibitedWithTimestamp(addr); prohibitErr != nil { + return nil, gas, prohibitErr } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { @@ -323,8 +336,8 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // This allows the user transfer balance of a specified coinId in addition to a normal Call(). func (evm *EVM) CallExpert(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, coinID common.Hash, value2 *big.Int) (ret []byte, leftOverGas uint64, err error) { - if evm.isProhibitedWithTimestamp(addr) { - return nil, gas, vmerrs.ErrToAddrProhibited + if prohibitErr := evm.isProhibitedWithTimestamp(addr); prohibitErr != nil { + return nil, gas, prohibitErr } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { @@ -409,8 +422,8 @@ func (evm *EVM) CallExpert(caller ContractRef, addr common.Address, input []byte // CallCode differs from Call in the sense that it executes the given address' // code with the caller as context. func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { - if evm.isProhibitedWithTimestamp(addr) { - return nil, gas, vmerrs.ErrToAddrProhibited + if prohibitErr := evm.isProhibitedWithTimestamp(addr); prohibitErr != nil { + return nil, gas, prohibitErr } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { @@ -463,8 +476,8 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // DelegateCall differs from CallCode in the sense that it executes the given address' // code with the caller as context and the caller is set to the caller of the caller. func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { - if evm.isProhibitedWithTimestamp(addr) { - return nil, gas, vmerrs.ErrToAddrProhibited + if prohibitErr := evm.isProhibitedWithTimestamp(addr); prohibitErr != nil { + return nil, gas, prohibitErr } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { @@ -505,8 +518,8 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // Opcodes that attempt to perform such modifications will result in exceptions // instead of performing the modifications. func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { - if evm.isProhibitedWithTimestamp(addr) { - return nil, gas, vmerrs.ErrToAddrProhibited + if prohibitErr := evm.isProhibitedWithTimestamp(addr); prohibitErr != nil { + return nil, gas, prohibitErr } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 5344460cf9..c2d3d6db26 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -618,6 +618,10 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, bigVal) + // Special case the error in the op code + if errors.Is(suberr, vmerrs.ErrToAddrProhibitedSoft) { + return nil, suberr + } // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must @@ -664,6 +668,10 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] } res, addr, returnGas, suberr := interpreter.evm.Create2(scope.Contract, input, gas, bigEndowment, &salt) + // Special case the error in the op code + if errors.Is(suberr, vmerrs.ErrToAddrProhibitedSoft) { + return nil, suberr + } // Push item on the stack based on the returned error. if suberr != nil { stackvalue.Clear() @@ -706,7 +714,10 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal) - + // Special case the error in the op code + if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) { + return nil, err + } if err != nil { temp.Clear() } else { @@ -757,7 +768,10 @@ func opCallExpert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } ret, returnGas, err := interpreter.evm.CallExpert(scope.Contract, toAddr, args, gas, bigVal, coinID, bigVal2) - + // Special case the error in the op code + if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) { + return nil, err + } if err != nil { temp.Clear() } else { @@ -793,6 +807,10 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ } ret, returnGas, err := interpreter.evm.CallCode(scope.Contract, toAddr, args, gas, bigVal) + // Special case the error in the op code + if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) { + return nil, err + } if err != nil { temp.Clear() } else { @@ -822,6 +840,10 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas) + // Special case the error in the op code + if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) { + return nil, err + } if err != nil { temp.Clear() } else { @@ -851,6 +873,10 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) ret, returnGas, err := interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas) + // Special case the error in the op code + if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) { + return nil, err + } if err != nil { temp.Clear() } else { diff --git a/miner/worker.go b/miner/worker.go index 7de8f7ce9b..c89225adec 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -287,7 +287,7 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP // Pop the unsupported transaction without shifting in the next from the account log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type()) txs.Pop() - case errors.Is(err, vmerrs.ErrToAddrProhibited): + case errors.Is(err, vmerrs.ErrToAddrProhibitedSoft): log.Warn("Tx dropped: failed verification", "tx", tx.Hash(), "sender", from, "data", tx.Data(), "err", err) w.eth.TxPool().RemoveTx(tx.Hash()) txs.Pop() diff --git a/params/config.go b/params/config.go index eee9dc7375..27287e99ef 100644 --- a/params/config.go +++ b/params/config.go @@ -72,6 +72,7 @@ var ( ApricotPhase5BlockTimestamp: big.NewInt(time.Date(2021, time.December, 2, 18, 0, 0, 0, time.UTC).Unix()), ApricotPhasePre6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 5, 1, 30, 0, 0, time.UTC).Unix()), ApricotPhase6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()), + ApricotPhasePost6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 7, 3, 0, 0, 0, time.UTC).Unix()), } // AvalancheFujiChainConfig is the configuration for the Fuji Test Network @@ -96,6 +97,7 @@ var ( ApricotPhase5BlockTimestamp: big.NewInt(time.Date(2021, time.November, 24, 15, 0, 0, 0, time.UTC).Unix()), ApricotPhasePre6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()), ApricotPhase6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()), + ApricotPhasePost6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 7, 6, 0, 0, 0, time.UTC).Unix()), } // AvalancheLocalChainConfig is the configuration for the Avalanche Local Network @@ -120,18 +122,19 @@ var ( ApricotPhase5BlockTimestamp: big.NewInt(0), ApricotPhasePre6BlockTimestamp: big.NewInt(0), ApricotPhase6BlockTimestamp: big.NewInt(0), - } - - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)} - TestLaunchConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil} - TestApricotPhase1Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil} - TestApricotPhase2Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil} - TestApricotPhase3Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil} - TestApricotPhase4Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil} - TestApricotPhase5Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil} - TestApricotPhasePre6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil} - TestApricotPhase6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)} - TestBlueberryChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)} + ApricotPhasePost6BlockTimestamp: big.NewInt(0), + } + + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil} + TestLaunchConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil, nil} + TestApricotPhase1Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil} + TestApricotPhase2Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil} + TestApricotPhase3Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil} + TestApricotPhase4Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil} + TestApricotPhase5Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil} + TestApricotPhasePre6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil} + TestApricotPhase6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil} + TestBlueberryChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil} TestRules = TestChainConfig.AvalancheRules(new(big.Int), new(big.Int)) ) @@ -176,6 +179,8 @@ type ChainConfig struct { ApricotPhasePre6BlockTimestamp *big.Int `json:"apricotPhasePre6BlockTimestamp,omitempty"` // Apricot Phase 6 deprecates the NativeAssetBalance and NativeAssetCall precompiles. (nil = no fork, 0 = already activated) ApricotPhase6BlockTimestamp *big.Int `json:"apricotPhase6BlockTimestamp,omitempty"` + // Apricot Phase Post-6 deprecates the NativeAssetCall precompile (soft). (nil = no fork, 0 = already activated) + ApricotPhasePost6BlockTimestamp *big.Int `json:"apricotPhasePost6BlockTimestamp,omitempty"` } // String implements the fmt.Stringer interface. @@ -297,6 +302,12 @@ func (c *ChainConfig) IsApricotPhase6(blockTimestamp *big.Int) bool { return utils.IsForked(c.ApricotPhase6BlockTimestamp, blockTimestamp) } +// IsApricotPhasePost6 returns whether [blockTimestamp] represents a block +// with a timestamp after the Apricot Phase 6 Post upgrade time. +func (c *ChainConfig) IsApricotPhasePost6(blockTimestamp *big.Int) bool { + return utils.IsForked(c.ApricotPhasePost6BlockTimestamp, blockTimestamp) +} + // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, timestamp uint64) *ConfigCompatError { @@ -371,6 +382,10 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "apricotPhase2BlockTimestamp", block: c.ApricotPhase2BlockTimestamp}, {name: "apricotPhase3BlockTimestamp", block: c.ApricotPhase3BlockTimestamp}, {name: "apricotPhase4BlockTimestamp", block: c.ApricotPhase4BlockTimestamp}, + {name: "apricotPhase5BlockTimestamp", block: c.ApricotPhase5BlockTimestamp}, + {name: "apricotPhasePre6BlockTimestamp", block: c.ApricotPhasePre6BlockTimestamp}, + {name: "apricotPhase6BlockTimestamp", block: c.ApricotPhase6BlockTimestamp}, + {name: "apricotPhasePost6BlockTimestamp", block: c.ApricotPhasePost6BlockTimestamp}, } { if lastFork.name != "" { // Next one must be higher number @@ -453,6 +468,12 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headHeight *big.Int, if isForkIncompatible(c.ApricotPhase5BlockTimestamp, newcfg.ApricotPhase5BlockTimestamp, headTimestamp) { return newCompatError("ApricotPhase5 fork block timestamp", c.ApricotPhase5BlockTimestamp, newcfg.ApricotPhase5BlockTimestamp) } + if isForkIncompatible(c.ApricotPhasePre6BlockTimestamp, newcfg.ApricotPhasePre6BlockTimestamp, headTimestamp) { + return newCompatError("ApricotPhasePre6 fork block timestamp", c.ApricotPhasePre6BlockTimestamp, newcfg.ApricotPhasePre6BlockTimestamp) + } + if isForkIncompatible(c.ApricotPhase6BlockTimestamp, newcfg.ApricotPhase6BlockTimestamp, headTimestamp) { + return newCompatError("ApricotPhase6 fork block timestamp", c.ApricotPhase6BlockTimestamp, newcfg.ApricotPhase6BlockTimestamp) + } return nil } @@ -516,7 +537,7 @@ type Rules struct { // Rules for Avalanche releases IsApricotPhase1, IsApricotPhase2, IsApricotPhase3, IsApricotPhase4, IsApricotPhase5 bool - IsApricotPhasePre6, IsApricotPhase6 bool + IsApricotPhasePre6, IsApricotPhase6, IsApricotPhasePost6 bool // Precompiles maps addresses to stateful precompiled contracts that are enabled // for this rule set. @@ -556,6 +577,7 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { rules.IsApricotPhase5 = c.IsApricotPhase5(blockTimestamp) rules.IsApricotPhasePre6 = c.IsApricotPhasePre6(blockTimestamp) rules.IsApricotPhase6 = c.IsApricotPhase6(blockTimestamp) + rules.IsApricotPhasePost6 = c.IsApricotPhasePost6(blockTimestamp) // Initialize the stateful precompiles that should be enabled at [blockTimestamp]. rules.Precompiles = make(map[common.Address]precompile.StatefulPrecompiledContract) diff --git a/vmerrs/vmerrs.go b/vmerrs/vmerrs.go index c47580880b..dc40911415 100644 --- a/vmerrs/vmerrs.go +++ b/vmerrs/vmerrs.go @@ -46,6 +46,6 @@ var ( ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") ErrAddrProhibited = errors.New("prohibited address cannot be sender or created contract address") - ErrToAddrProhibited = errors.New("prohibited address cannot be called") + ErrToAddrProhibited6 = errors.New("prohibited address cannot be called") + ErrToAddrProhibitedSoft = errors.New("prohibited address cannot be called") ) - From d90c7f148e262557698d5a099144b9bcc759ad31 Mon Sep 17 00:00:00 2001 From: pdrobnjak Date: Wed, 7 Sep 2022 11:52:39 +0200 Subject: [PATCH 3/9] Update module name --- accounts/abi/bind/auth.go | 8 +- accounts/abi/bind/backend.go | 4 +- accounts/abi/bind/backends/simulated.go | 34 ++--- accounts/abi/bind/backends/simulated_test.go | 12 +- accounts/abi/bind/base.go | 8 +- accounts/abi/bind/base_test.go | 10 +- accounts/abi/bind/bind.go | 2 +- accounts/abi/bind/bind_test.go | 142 +++++++++--------- accounts/abi/bind/template.go | 10 +- accounts/abi/bind/util.go | 4 +- accounts/abi/bind/util_test.go | 8 +- accounts/accounts.go | 4 +- accounts/external/backend.go | 10 +- accounts/keystore/account_cache.go | 2 +- accounts/keystore/account_cache_test.go | 2 +- accounts/keystore/key.go | 2 +- accounts/keystore/keystore.go | 4 +- accounts/keystore/keystore_test.go | 2 +- accounts/keystore/passphrase.go | 2 +- accounts/keystore/presale.go | 2 +- accounts/keystore/wallet.go | 6 +- accounts/scwallet/hub.go | 2 +- accounts/scwallet/wallet.go | 6 +- chain/chain_test.go | 20 +-- chain/coreth.go | 16 +- chain/counter_test.go | 2 +- chain/multicoin_test.go | 18 +-- chain/payment_test.go | 2 +- chain/subscribe_accepted_heads_test.go | 4 +- chain/subscribe_block_logs_test.go | 4 +- chain/subscribe_transactions_test.go | 4 +- chain/test_chain.go | 18 +-- cmd/abigen/main.go | 6 +- consensus/consensus.go | 6 +- consensus/dummy/consensus.go | 12 +- consensus/dummy/consensus_test.go | 2 +- consensus/dummy/dynamic_fees.go | 4 +- consensus/dummy/dynamic_fees_test.go | 4 +- consensus/misc/dao.go | 6 +- core/bench_test.go | 12 +- core/block_validator.go | 10 +- core/blockchain.go | 20 +-- core/blockchain_iterator.go | 2 +- core/blockchain_reader.go | 14 +- core/blockchain_repair_test.go | 10 +- core/blockchain_sethead_test.go | 2 +- core/blockchain_snapshot_test.go | 14 +- core/blockchain_test.go | 16 +- core/bloom_indexer.go | 8 +- core/bloombits/generator.go | 2 +- core/bloombits/generator_test.go | 2 +- core/chain_indexer.go | 6 +- core/chain_indexer_test.go | 4 +- core/chain_makers.go | 16 +- core/chain_makers_test.go | 10 +- core/dao_test.go | 8 +- core/error.go | 2 +- core/events.go | 2 +- core/evm.go | 6 +- core/gen_genesis.go | 2 +- core/genesis.go | 12 +- core/genesis_test.go | 10 +- core/headerchain.go | 10 +- core/headerchain_test.go | 12 +- core/mkalloc.go | 2 +- core/rawdb/accessors_chain.go | 6 +- core/rawdb/accessors_chain_test.go | 4 +- core/rawdb/accessors_indexes.go | 6 +- core/rawdb/accessors_indexes_test.go | 4 +- core/rawdb/accessors_metadata.go | 4 +- core/rawdb/accessors_snapshot.go | 2 +- core/rawdb/accessors_state.go | 2 +- core/rawdb/database.go | 6 +- core/rawdb/schema.go | 2 +- core/rawdb/table.go | 2 +- core/rawdb/table_test.go | 2 +- core/rlp_test.go | 8 +- core/state/database.go | 8 +- core/state/dump.go | 4 +- core/state/iterator.go | 4 +- core/state/metrics.go | 2 +- core/state/pruner/bloom.go | 2 +- core/state/pruner/pruner.go | 10 +- core/state/snapshot/conversion.go | 6 +- core/state/snapshot/difflayer_test.go | 2 +- core/state/snapshot/disklayer.go | 6 +- core/state/snapshot/disklayer_test.go | 6 +- core/state/snapshot/generate.go | 6 +- core/state/snapshot/generate_test.go | 6 +- core/state/snapshot/iterator.go | 4 +- core/state/snapshot/iterator_test.go | 2 +- core/state/snapshot/journal.go | 6 +- core/state/snapshot/snapshot.go | 8 +- core/state/snapshot/snapshot_test.go | 2 +- core/state/snapshot/wipe.go | 4 +- core/state/snapshot/wipe_test.go | 4 +- core/state/state_object.go | 4 +- core/state/state_test.go | 4 +- core/state/statedb.go | 10 +- core/state/statedb_test.go | 6 +- core/state/trie_prefetcher.go | 2 +- core/state/trie_prefetcher_test.go | 2 +- core/state_manager.go | 4 +- core/state_manager_test.go | 2 +- core/state_prefetcher.go | 10 +- core/state_processor.go | 12 +- core/state_processor_test.go | 12 +- core/state_transition.go | 8 +- core/test_blockchain.go | 12 +- core/tx_cacher.go | 2 +- core/tx_journal.go | 2 +- core/tx_list.go | 2 +- core/tx_list_test.go | 2 +- core/tx_noncer.go | 2 +- core/tx_pool.go | 10 +- core/tx_pool_test.go | 10 +- core/types.go | 6 +- core/types/block_test.go | 2 +- core/types/hashing_test.go | 4 +- core/types/receipt.go | 2 +- core/types/receipt_test.go | 2 +- core/types/transaction_signing.go | 2 +- core/vm/contracts.go | 8 +- core/vm/contracts_stateful.go | 4 +- core/vm/contracts_stateful_test.go | 6 +- core/vm/eips.go | 2 +- core/vm/evm.go | 8 +- core/vm/gas.go | 2 +- core/vm/gas_table.go | 4 +- core/vm/gas_table_test.go | 6 +- core/vm/instructions.go | 6 +- core/vm/instructions_test.go | 2 +- core/vm/interface.go | 2 +- core/vm/interpreter.go | 2 +- core/vm/interpreter_test.go | 6 +- core/vm/jump_table.go | 2 +- core/vm/operations_acl.go | 4 +- core/vm/runtime/env.go | 4 +- core/vm/runtime/runtime.go | 8 +- core/vm/runtime/runtime_example_test.go | 2 +- core/vm/runtime/runtime_test.go | 22 +-- core/vm/stack_table.go | 2 +- eth/api.go | 14 +- eth/api_backend.go | 26 ++-- eth/backend.go | 40 ++--- eth/bloombits.go | 2 +- eth/ethconfig/config.go | 6 +- eth/filters/api.go | 6 +- eth/filters/api_test.go | 2 +- eth/filters/filter.go | 12 +- eth/filters/filter_system.go | 10 +- eth/gasprice/feehistory.go | 4 +- eth/gasprice/feehistory_test.go | 8 +- eth/gasprice/gasprice.go | 10 +- eth/gasprice/gasprice_test.go | 16 +- eth/state_accessor.go | 10 +- eth/tracers/api.go | 20 +-- eth/tracers/api_test.go | 22 +-- .../internal/tracetest/calltrace_test.go | 16 +- eth/tracers/logger/access_list_tracer.go | 4 +- eth/tracers/logger/gen_structlog.go | 2 +- eth/tracers/logger/logger.go | 6 +- eth/tracers/logger/logger_json.go | 2 +- eth/tracers/logger/logger_test.go | 6 +- eth/tracers/native/4byte.go | 4 +- eth/tracers/native/call.go | 4 +- eth/tracers/native/noop.go | 4 +- eth/tracers/native/prestate.go | 6 +- eth/tracers/native/tracer.go | 2 +- eth/tracers/tracers.go | 2 +- eth/tracers/tracers_test.go | 14 +- ethclient/corethclient/corethclient.go | 8 +- ethclient/ethclient.go | 8 +- ethclient/signer.go | 2 +- ethdb/dbtest/testsuite.go | 2 +- ethdb/leveldb/leveldb.go | 4 +- ethdb/leveldb/leveldb_test.go | 4 +- ethdb/memorydb/memorydb.go | 2 +- ethdb/memorydb/memorydb_test.go | 4 +- go.mod | 2 +- interfaces/interfaces.go | 2 +- internal/ethapi/api.go | 22 +-- internal/ethapi/backend.go | 20 +-- internal/ethapi/transaction_args.go | 4 +- internal/shutdowncheck/shutdown_tracker.go | 4 +- metrics/prometheus/prometheus.go | 2 +- metrics/prometheus/prometheus_test.go | 2 +- miner/miner.go | 8 +- miner/worker.go | 16 +- node/api.go | 4 +- node/config.go | 8 +- node/defaults.go | 2 +- node/node.go | 4 +- params/config.go | 4 +- peer/network.go | 4 +- peer/network_test.go | 2 +- peer/peer_tracker.go | 2 +- peer/stats/stats.go | 2 +- peer/waiting_handler.go | 2 +- plugin/evm/atomic_syncer.go | 4 +- plugin/evm/atomic_syncer_test.go | 12 +- plugin/evm/atomic_trie.go | 6 +- plugin/evm/atomic_trie_iterator.go | 2 +- plugin/evm/block.go | 4 +- plugin/evm/block_builder.go | 4 +- plugin/evm/block_builder_test.go | 2 +- plugin/evm/block_verification.go | 8 +- plugin/evm/config.go | 2 +- plugin/evm/database.go | 2 +- plugin/evm/export_tx.go | 4 +- plugin/evm/export_tx_test.go | 2 +- plugin/evm/gasprice_update.go | 2 +- plugin/evm/gasprice_update_test.go | 2 +- plugin/evm/gossiper.go | 10 +- plugin/evm/gossiper_atomic_gossiping_test.go | 2 +- plugin/evm/gossiper_eth_gossiping_test.go | 8 +- plugin/evm/import_tx.go | 4 +- plugin/evm/import_tx_test.go | 2 +- plugin/evm/mempool_atomic_gossiping_test.go | 2 +- plugin/evm/service.go | 2 +- plugin/evm/static_service.go | 2 +- plugin/evm/syncervm_client.go | 16 +- plugin/evm/syncervm_server.go | 4 +- plugin/evm/syncervm_test.go | 24 +-- plugin/evm/test_tx.go | 4 +- plugin/evm/tx.go | 4 +- plugin/evm/tx_test.go | 2 +- plugin/evm/vm.go | 42 +++--- plugin/evm/vm_test.go | 16 +- plugin/main.go | 2 +- precompile/stateful_precompile_config.go | 2 +- precompile/utils.go | 2 +- rpc/handler.go | 2 +- rpc/metrics.go | 2 +- signer/core/apitypes/types.go | 2 +- sync/client/client.go | 16 +- sync/client/client_test.go | 20 +-- sync/client/leaf_syncer.go | 4 +- sync/client/mock_client.go | 6 +- sync/client/mock_network.go | 2 +- sync/client/stats/stats.go | 4 +- sync/handlers/block_request.go | 6 +- sync/handlers/block_request_test.go | 14 +- sync/handlers/code_request.go | 8 +- sync/handlers/code_request_test.go | 10 +- sync/handlers/handler.go | 10 +- sync/handlers/iterators.go | 4 +- sync/handlers/leafs_request.go | 16 +- sync/handlers/leafs_request_test.go | 16 +- sync/handlers/stats/stats.go | 2 +- sync/handlers/test_providers.go | 4 +- sync/statesync/code_syncer.go | 6 +- sync/statesync/code_syncer_test.go | 12 +- sync/statesync/state_syncer.go | 12 +- sync/statesync/state_syncer_progress.go | 2 +- sync/statesync/sync_helpers.go | 12 +- sync/statesync/sync_test.go | 20 +-- sync/statesync/test_sync.go | 10 +- tests/init.go | 2 +- tests/init_test.go | 2 +- tests/state_test_util.go | 14 +- trie/database.go | 6 +- trie/database_test.go | 2 +- trie/iterator.go | 2 +- trie/iterator_test.go | 4 +- trie/proof.go | 4 +- trie/proof_test.go | 2 +- trie/secure_trie.go | 2 +- trie/secure_trie_test.go | 2 +- trie/stacktrie.go | 2 +- trie/stacktrie_test.go | 2 +- trie/sync_test.go | 2 +- trie/test_trie.go | 4 +- trie/trie.go | 2 +- trie/trie_test.go | 6 +- 275 files changed, 962 insertions(+), 962 deletions(-) diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index 859083c059..943a04c51f 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -34,10 +34,10 @@ import ( "io/ioutil" "math/big" - "github.com/ava-labs/coreth/accounts" - "github.com/ava-labs/coreth/accounts/external" - "github.com/ava-labs/coreth/accounts/keystore" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/accounts" + "github.com/tenderly/coreth/accounts/external" + "github.com/tenderly/coreth/accounts/keystore" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index 29a4b3cb58..76b195b087 100644 --- a/accounts/abi/bind/backend.go +++ b/accounts/abi/bind/backend.go @@ -31,8 +31,8 @@ import ( "errors" "math/big" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/interfaces" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/interfaces" "github.com/ethereum/go-ethereum/common" ) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 431fc9d192..dabedb9ec4 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -34,23 +34,23 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/eth" - "github.com/ava-labs/coreth/vmerrs" - - "github.com/ava-labs/coreth/accounts/abi" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/bloombits" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth/filters" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/interfaces" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/eth" + "github.com/tenderly/coreth/vmerrs" + + "github.com/tenderly/coreth/accounts/abi" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/bloombits" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/filters" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/interfaces" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index dd03d74783..897f3b5cc6 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -37,12 +37,12 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/accounts/abi" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/interfaces" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/accounts/abi" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/interfaces" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 55fbc017a6..5b884281c4 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -34,10 +34,10 @@ import ( "strings" "sync" - "github.com/ava-labs/coreth/accounts/abi" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/interfaces" + "github.com/tenderly/coreth/accounts/abi" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/interfaces" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go index 2c490d5cfa..84216f0e46 100644 --- a/accounts/abi/bind/base_test.go +++ b/accounts/abi/bind/base_test.go @@ -34,11 +34,11 @@ import ( "strings" "testing" - "github.com/ava-labs/coreth/accounts/abi" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/interfaces" + "github.com/tenderly/coreth/accounts/abi" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/interfaces" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index fec7c3216a..be0cb6e3df 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -40,7 +40,7 @@ import ( "text/template" "unicode" - "github.com/ava-labs/coreth/accounts/abi" + "github.com/tenderly/coreth/accounts/abi" "github.com/ethereum/go-ethereum/log" ) diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 42fd0820e1..2fa95de410 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -298,9 +298,9 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -353,9 +353,9 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -399,9 +399,9 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -456,10 +456,10 @@ var bindTests = []struct { "math/big" "reflect" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -505,9 +505,9 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -571,9 +571,9 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -616,10 +616,10 @@ var bindTests = []struct { []string{`6060604052609f8060106000396000f3606060405260e060020a6000350463f97a60058114601a575b005b600060605260c0604052600d60809081527f4920646f6e27742065786973740000000000000000000000000000000000000060a052602060c0908152600d60e081905281906101009060a09080838184600060046012f15050815172ffffffffffffffffffffffffffffffffffffff1916909152505060405161012081900392509050f3`}, []string{`[{"constant":true,"inputs":[],"name":"String","outputs":[{"name":"","type":"string"}],"type":"function"}]`}, ` - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/core" `, ` // Create a simulator and wrap a non-deployed contract @@ -655,10 +655,10 @@ var bindTests = []struct { []string{`6080604052348015600f57600080fd5b5060888061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063d5f6622514602d575b600080fd5b6033604c565b6040805192835260208301919091528051918290030190f35b600a809156fea264697066735822beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef64736f6c6343decafe0033`}, []string{`[{"inputs":[],"name":"Struct","outputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"stateMutability":"pure","type":"function"}]`}, ` - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/core" `, ` // Create a simulator and wrap a non-deployed contract @@ -703,9 +703,9 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -752,10 +752,10 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -828,9 +828,9 @@ var bindTests = []struct { "fmt" "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -921,10 +921,10 @@ var bindTests = []struct { "math/big" "time" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -1112,9 +1112,9 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -1247,9 +1247,9 @@ var bindTests = []struct { "math/big" "reflect" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, @@ -1389,9 +1389,9 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -1455,11 +1455,11 @@ var bindTests = []struct { "math/big" "time" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" `, ` // Initialize test accounts @@ -1566,10 +1566,10 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/crypto" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/core" `, ` // Initialize test accounts @@ -1629,10 +1629,10 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/crypto" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/core" `, ` key, _ := crypto.GenerateKey() @@ -1691,9 +1691,9 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -1752,9 +1752,9 @@ var bindTests = []struct { "bytes" "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -1840,9 +1840,9 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -1909,9 +1909,9 @@ var bindTests = []struct { ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, ` @@ -1960,9 +1960,9 @@ var bindTests = []struct { imports: ` "math/big" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/crypto" `, tester: ` @@ -2066,7 +2066,7 @@ func golangBindings(t *testing.T, overload bool) { t.Fatalf("failed to convert binding test to modules: %v\n%s", err, out) } pwd, _ := os.Getwd() - replacer := exec.Command(gocmd, "mod", "edit", "-x", "-require", "github.com/ava-labs/coreth@v0.0.0", "-replace", "github.com/ava-labs/coreth="+filepath.Join(pwd, "..", "..", "..")) // Repo root + replacer := exec.Command(gocmd, "mod", "edit", "-x", "-require", "github.com/tenderly/coreth@v0.0.0", "-replace", "github.com/tenderly/coreth="+filepath.Join(pwd, "..", "..", "..")) // Repo root replacer.Dir = pkg if out, err := replacer.CombinedOutput(); err != nil { t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out) diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index f66f175c28..98ed4ab86f 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -26,7 +26,7 @@ package bind -import "github.com/ava-labs/coreth/accounts/abi" +import "github.com/tenderly/coreth/accounts/abi" // tmplData is the data structure required to fill the binding template. type tmplData struct { @@ -102,10 +102,10 @@ import ( "strings" "errors" - "github.com/ava-labs/coreth/accounts/abi" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/interfaces" + "github.com/tenderly/coreth/accounts/abi" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/interfaces" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" ) diff --git a/accounts/abi/bind/util.go b/accounts/abi/bind/util.go index 378f7ef877..c9c14c58da 100644 --- a/accounts/abi/bind/util.go +++ b/accounts/abi/bind/util.go @@ -31,8 +31,8 @@ import ( "errors" "time" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/interfaces" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/interfaces" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go index aab4ae8928..d220149b7d 100644 --- a/accounts/abi/bind/util_test.go +++ b/accounts/abi/bind/util_test.go @@ -33,10 +33,10 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/accounts/abi/bind/backends" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/accounts/abi/bind/backends" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/accounts/accounts.go b/accounts/accounts.go index b59fe580ba..d9ea0c2dd4 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -31,8 +31,8 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/interfaces" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/interfaces" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "golang.org/x/crypto/sha3" diff --git a/accounts/external/backend.go b/accounts/external/backend.go index 558700fc70..58e509e302 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -31,11 +31,11 @@ import ( "math/big" "sync" - "github.com/ava-labs/coreth/accounts" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/interfaces" - "github.com/ava-labs/coreth/rpc" - "github.com/ava-labs/coreth/signer/core/apitypes" + "github.com/tenderly/coreth/accounts" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/interfaces" + "github.com/tenderly/coreth/rpc" + "github.com/tenderly/coreth/signer/core/apitypes" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/event" diff --git a/accounts/keystore/account_cache.go b/accounts/keystore/account_cache.go index 4c35aa7421..d05a8a9d78 100644 --- a/accounts/keystore/account_cache.go +++ b/accounts/keystore/account_cache.go @@ -37,7 +37,7 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/accounts" + "github.com/tenderly/coreth/accounts" mapset "github.com/deckarep/golang-set" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" diff --git a/accounts/keystore/account_cache_test.go b/accounts/keystore/account_cache_test.go index 281a8c4ba6..b0dd8ad805 100644 --- a/accounts/keystore/account_cache_test.go +++ b/accounts/keystore/account_cache_test.go @@ -37,7 +37,7 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/accounts" + "github.com/tenderly/coreth/accounts" "github.com/cespare/cp" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go index 71402d3629..bfc9620765 100644 --- a/accounts/keystore/key.go +++ b/accounts/keystore/key.go @@ -39,7 +39,7 @@ import ( "strings" "time" - "github.com/ava-labs/coreth/accounts" + "github.com/tenderly/coreth/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/google/uuid" diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index ff82ef88b9..8d20c56c79 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -42,8 +42,8 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/accounts" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/accounts" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go index c744d3a448..66cccf7003 100644 --- a/accounts/keystore/keystore_test.go +++ b/accounts/keystore/keystore_test.go @@ -38,7 +38,7 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/accounts" + "github.com/tenderly/coreth/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go index 5da41f6304..309f3340e0 100644 --- a/accounts/keystore/passphrase.go +++ b/accounts/keystore/passphrase.go @@ -48,7 +48,7 @@ import ( "os" "path/filepath" - "github.com/ava-labs/coreth/accounts" + "github.com/tenderly/coreth/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" diff --git a/accounts/keystore/presale.go b/accounts/keystore/presale.go index 1dfbd9c2a9..b25c6fc246 100644 --- a/accounts/keystore/presale.go +++ b/accounts/keystore/presale.go @@ -35,7 +35,7 @@ import ( "errors" "fmt" - "github.com/ava-labs/coreth/accounts" + "github.com/tenderly/coreth/accounts" "github.com/ethereum/go-ethereum/crypto" "github.com/google/uuid" "golang.org/x/crypto/pbkdf2" diff --git a/accounts/keystore/wallet.go b/accounts/keystore/wallet.go index 7193526399..0ff33b5b94 100644 --- a/accounts/keystore/wallet.go +++ b/accounts/keystore/wallet.go @@ -29,9 +29,9 @@ package keystore import ( "math/big" - "github.com/ava-labs/coreth/accounts" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/interfaces" + "github.com/tenderly/coreth/accounts" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/interfaces" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/accounts/scwallet/hub.go b/accounts/scwallet/hub.go index 7a630facca..7248ad891b 100644 --- a/accounts/scwallet/hub.go +++ b/accounts/scwallet/hub.go @@ -51,7 +51,7 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/accounts" + "github.com/tenderly/coreth/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go index d19cc1ef05..ecfbcc22ec 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -43,9 +43,9 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/accounts" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/interfaces" + "github.com/tenderly/coreth/accounts" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/interfaces" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" diff --git a/chain/chain_test.go b/chain/chain_test.go index 02a9223117..459a93b73b 100644 --- a/chain/chain_test.go +++ b/chain/chain_test.go @@ -10,16 +10,16 @@ import ( "testing" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/coreth/accounts/keystore" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/eth" - "github.com/ava-labs/coreth/eth/ethconfig" - "github.com/ava-labs/coreth/node" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/accounts/keystore" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/eth" + "github.com/tenderly/coreth/eth/ethconfig" + "github.com/tenderly/coreth/node" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" diff --git a/chain/coreth.go b/chain/coreth.go index 5f6da50096..5eec8181bf 100644 --- a/chain/coreth.go +++ b/chain/coreth.go @@ -8,14 +8,14 @@ import ( "time" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/eth" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/node" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/eth" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/node" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" ) diff --git a/chain/counter_test.go b/chain/counter_test.go index 7209ac519e..2178dbe137 100644 --- a/chain/counter_test.go +++ b/chain/counter_test.go @@ -15,7 +15,7 @@ import ( "testing" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" diff --git a/chain/multicoin_test.go b/chain/multicoin_test.go index ad25d01a62..7dd53235d8 100644 --- a/chain/multicoin_test.go +++ b/chain/multicoin_test.go @@ -29,15 +29,15 @@ import ( "testing" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/coreth/accounts/keystore" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth" - "github.com/ava-labs/coreth/eth/ethconfig" - "github.com/ava-labs/coreth/node" + "github.com/tenderly/coreth/accounts/keystore" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth" + "github.com/tenderly/coreth/eth/ethconfig" + "github.com/tenderly/coreth/node" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" diff --git a/chain/payment_test.go b/chain/payment_test.go index 5d9da4d82f..ebe2295636 100644 --- a/chain/payment_test.go +++ b/chain/payment_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/log" ) diff --git a/chain/subscribe_accepted_heads_test.go b/chain/subscribe_accepted_heads_test.go index 4f520451ae..7950c9e41d 100644 --- a/chain/subscribe_accepted_heads_test.go +++ b/chain/subscribe_accepted_heads_test.go @@ -4,8 +4,8 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/chain/subscribe_block_logs_test.go b/chain/subscribe_block_logs_test.go index 6a2c9aaa46..2f47d44292 100644 --- a/chain/subscribe_block_logs_test.go +++ b/chain/subscribe_block_logs_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/eth/filters" + "github.com/tenderly/coreth/eth/filters" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" ) diff --git a/chain/subscribe_transactions_test.go b/chain/subscribe_transactions_test.go index aac6db4acd..a9108487f8 100644 --- a/chain/subscribe_transactions_test.go +++ b/chain/subscribe_transactions_test.go @@ -4,9 +4,9 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/eth/filters" + "github.com/tenderly/coreth/eth/filters" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" ) diff --git a/chain/test_chain.go b/chain/test_chain.go index ae2732e51f..85da35a176 100644 --- a/chain/test_chain.go +++ b/chain/test_chain.go @@ -9,15 +9,15 @@ import ( "testing" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/coreth/accounts/keystore" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/eth" - "github.com/ava-labs/coreth/eth/ethconfig" - "github.com/ava-labs/coreth/node" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/accounts/keystore" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/eth" + "github.com/tenderly/coreth/eth/ethconfig" + "github.com/tenderly/coreth/node" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ) diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index ebb5b5902e..daa2f0080e 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -35,9 +35,9 @@ import ( "regexp" "strings" - "github.com/ava-labs/coreth/accounts/abi" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/internal/flags" + "github.com/tenderly/coreth/accounts/abi" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/internal/flags" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/crypto" diff --git a/consensus/consensus.go b/consensus/consensus.go index d4e247ceaf..2160b58bf9 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -30,9 +30,9 @@ package consensus import ( "math/big" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" ) diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index 77720a0791..8bb91a5aa4 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -10,12 +10,12 @@ import ( "math/big" "time" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/rpc" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rpc" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" ) diff --git a/consensus/dummy/consensus_test.go b/consensus/dummy/consensus_test.go index 64a8439817..702d4efd1b 100644 --- a/consensus/dummy/consensus_test.go +++ b/consensus/dummy/consensus_test.go @@ -8,7 +8,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" ) diff --git a/consensus/dummy/dynamic_fees.go b/consensus/dummy/dynamic_fees.go index aa38fcce5f..f0101ec243 100644 --- a/consensus/dummy/dynamic_fees.go +++ b/consensus/dummy/dynamic_fees.go @@ -9,8 +9,8 @@ import ( "math/big" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" ) diff --git a/consensus/dummy/dynamic_fees_test.go b/consensus/dummy/dynamic_fees_test.go index 9bbfcc12f8..97fa210098 100644 --- a/consensus/dummy/dynamic_fees_test.go +++ b/consensus/dummy/dynamic_fees_test.go @@ -8,8 +8,8 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/assert" diff --git a/consensus/misc/dao.go b/consensus/misc/dao.go index a0ab4029b4..a4e582a9f8 100644 --- a/consensus/misc/dao.go +++ b/consensus/misc/dao.go @@ -31,9 +31,9 @@ import ( "errors" "math/big" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" ) var ( diff --git a/core/bench_test.go b/core/bench_test.go index 5271c351a2..f87e639663 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -33,12 +33,12 @@ import ( "os" "testing" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" diff --git a/core/block_validator.go b/core/block_validator.go index 287a42fb8e..60f558e74d 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -29,11 +29,11 @@ package core import ( "fmt" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/trie" ) // BlockValidator is responsible for validating block headers, uncles and diff --git a/core/blockchain.go b/core/blockchain.go index b966c8c73e..dbcd51bd7b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -38,16 +38,16 @@ import ( "sync/atomic" "time" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/metrics" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/metrics" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" diff --git a/core/blockchain_iterator.go b/core/blockchain_iterator.go index e81d4a5761..782c5f7114 100644 --- a/core/blockchain_iterator.go +++ b/core/blockchain_iterator.go @@ -33,7 +33,7 @@ import ( "fmt" "sync" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" ) type blockAndState struct { diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index 619e58447d..032cb87290 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -27,13 +27,13 @@ package core import ( - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" ) diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index 8b6344a92c..d48d6f6c62 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -36,11 +36,11 @@ import ( "os" "testing" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go index 6695ea0464..b0fb98e532 100644 --- a/core/blockchain_sethead_test.go +++ b/core/blockchain_sethead_test.go @@ -32,7 +32,7 @@ package core import ( "testing" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" ) // verifyNoGaps checks that there are no gaps after the initial set of blocks in diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index b40f92aba0..f080461211 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -38,13 +38,13 @@ import ( "strings" "testing" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index c4bd47090b..327d9b4083 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -8,14 +8,14 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/state/pruner" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/state/pruner" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/core/bloom_indexer.go b/core/bloom_indexer.go index 4d8f58fdfc..d6bac3fa4d 100644 --- a/core/bloom_indexer.go +++ b/core/bloom_indexer.go @@ -20,10 +20,10 @@ import ( "context" "time" - "github.com/ava-labs/coreth/core/bloombits" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/core/bloombits" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/bitutil" ) diff --git a/core/bloombits/generator.go b/core/bloombits/generator.go index c0422caad5..f5e7edd38b 100644 --- a/core/bloombits/generator.go +++ b/core/bloombits/generator.go @@ -29,7 +29,7 @@ package bloombits import ( "errors" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" ) var ( diff --git a/core/bloombits/generator_test.go b/core/bloombits/generator_test.go index 067c1db66c..6f36ade0e1 100644 --- a/core/bloombits/generator_test.go +++ b/core/bloombits/generator_test.go @@ -31,7 +31,7 @@ import ( "math/rand" "testing" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" ) // Tests that batched bloom bits are correctly rotated from the input bloom diff --git a/core/chain_indexer.go b/core/chain_indexer.go index 975f82b37a..8f42923340 100644 --- a/core/chain_indexer.go +++ b/core/chain_indexer.go @@ -34,9 +34,9 @@ import ( "sync/atomic" "time" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" diff --git a/core/chain_indexer_test.go b/core/chain_indexer_test.go index 3edf175d3d..5c9305fe9b 100644 --- a/core/chain_indexer_test.go +++ b/core/chain_indexer_test.go @@ -35,8 +35,8 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/chain_makers.go b/core/chain_makers.go index 8af1ec7c7f..51fb40fd04 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -30,14 +30,14 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/consensus/misc" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/consensus/misc" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index cd003932e7..01a0ee5daa 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -30,11 +30,11 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/core/dao_test.go b/core/dao_test.go index 60c6c507e0..49df2c7251 100644 --- a/core/dao_test.go +++ b/core/dao_test.go @@ -30,10 +30,10 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/error.go b/core/error.go index a90475fe2e..9cc872a16a 100644 --- a/core/error.go +++ b/core/error.go @@ -29,7 +29,7 @@ package core import ( "errors" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" ) var ( diff --git a/core/events.go b/core/events.go index 4898dbc071..dc4c8ff6ba 100644 --- a/core/events.go +++ b/core/events.go @@ -27,7 +27,7 @@ package core import ( - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/evm.go b/core/evm.go index 474c157b4e..e809be0f20 100644 --- a/core/evm.go +++ b/core/evm.go @@ -29,9 +29,9 @@ package core import ( "math/big" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" "github.com/ethereum/go-ethereum/common" //"github.com/ethereum/go-ethereum/log" ) diff --git a/core/gen_genesis.go b/core/gen_genesis.go index a4ec8f54dd..d81cff7173 100644 --- a/core/gen_genesis.go +++ b/core/gen_genesis.go @@ -7,7 +7,7 @@ import ( "errors" "math/big" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" diff --git a/core/genesis.go b/core/genesis.go index 1252e13c5a..7c0a7eb19b 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -34,12 +34,12 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" diff --git a/core/genesis_test.go b/core/genesis_test.go index 54e9ad03d3..ee0090c437 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -32,11 +32,11 @@ import ( "reflect" "testing" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/headerchain.go b/core/headerchain.go index 94b34058a3..434db4eaf6 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -33,11 +33,11 @@ import ( mrand "math/rand" "sync/atomic" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" lru "github.com/hashicorp/golang-lru" ) diff --git a/core/headerchain_test.go b/core/headerchain_test.go index 8c8b598524..e3d2ee987d 100644 --- a/core/headerchain_test.go +++ b/core/headerchain_test.go @@ -32,12 +32,12 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/core/mkalloc.go b/core/mkalloc.go index 76978a547f..2890aaad53 100644 --- a/core/mkalloc.go +++ b/core/mkalloc.go @@ -45,7 +45,7 @@ import ( "sort" "strconv" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/core" "github.com/ethereum/go-ethereum/rlp" ) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index d5ae7472fb..74ee74b0cb 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -31,9 +31,9 @@ import ( "encoding/binary" "errors" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 02bb9b2891..ee5d391652 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -25,8 +25,8 @@ import ( "reflect" "testing" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/sha3" diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go index 0ac332147b..ece8d2feb4 100644 --- a/core/rawdb/accessors_indexes.go +++ b/core/rawdb/accessors_indexes.go @@ -30,9 +30,9 @@ import ( "bytes" "math/big" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index e64818b113..8bbf9b1dd4 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -22,8 +22,8 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/sha3" diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index 0e19cfe0fd..83af88f767 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -30,8 +30,8 @@ import ( "encoding/json" "time" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index d80c34ae9c..b8b3741b21 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -27,7 +27,7 @@ package rawdb import ( - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 60d769f8d5..04f0a0b02c 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -27,7 +27,7 @@ package rawdb import ( - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 22c3a676d0..9a936bef90 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -32,9 +32,9 @@ import ( "os" "time" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/ethdb/leveldb" - "github.com/ava-labs/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/leveldb" + "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/olekukonko/tablewriter" diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index dcd030bdb1..2729a18848 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -31,7 +31,7 @@ import ( "bytes" "encoding/binary" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 179ebf39dd..1ac02ca3ae 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -27,7 +27,7 @@ package rawdb import ( - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/ethdb" ) // table is a wrapper around a database that prefixes each key access with a pre- diff --git a/core/rawdb/table_test.go b/core/rawdb/table_test.go index c7cac98236..c0013c1f4d 100644 --- a/core/rawdb/table_test.go +++ b/core/rawdb/table_test.go @@ -30,7 +30,7 @@ import ( "bytes" "testing" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/ethdb" ) func TestTableDatabase(t *testing.T) { testTableDatabase(t, "prefix") } diff --git a/core/rlp_test.go b/core/rlp_test.go index d238e2caec..f972522ea6 100644 --- a/core/rlp_test.go +++ b/core/rlp_test.go @@ -31,10 +31,10 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" diff --git a/core/state/database.go b/core/state/database.go index bef0af1e7b..45a45d813d 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -31,10 +31,10 @@ import ( "fmt" "github.com/VictoriaMetrics/fastcache" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" lru "github.com/hashicorp/golang-lru" ) diff --git a/core/state/dump.go b/core/state/dump.go index 8f30d4827e..72a2dc09e0 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -31,8 +31,8 @@ import ( "fmt" "time" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" diff --git a/core/state/iterator.go b/core/state/iterator.go index 2ad4ed937b..c174d88d5c 100644 --- a/core/state/iterator.go +++ b/core/state/iterator.go @@ -30,8 +30,8 @@ import ( "bytes" "fmt" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" ) diff --git a/core/state/metrics.go b/core/state/metrics.go index cc7838a7ef..156bfd7116 100644 --- a/core/state/metrics.go +++ b/core/state/metrics.go @@ -26,7 +26,7 @@ package state -import "github.com/ava-labs/coreth/metrics" +import "github.com/tenderly/coreth/metrics" var ( accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil) diff --git a/core/state/pruner/bloom.go b/core/state/pruner/bloom.go index 4a2f3a5916..913b017dee 100644 --- a/core/state/pruner/bloom.go +++ b/core/state/pruner/bloom.go @@ -31,7 +31,7 @@ import ( "errors" "os" - "github.com/ava-labs/coreth/core/rawdb" + "github.com/tenderly/coreth/core/rawdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" bloomfilter "github.com/holiman/bloomfilter/v2" diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index f8f1e6699e..eb1bce24bd 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -37,11 +37,11 @@ import ( "strings" "time" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index bfb157a107..65a249270b 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -36,9 +36,9 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 245acc835c..c4ca35142f 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -32,7 +32,7 @@ import ( "testing" "github.com/VictoriaMetrics/fastcache" - "github.com/ava-labs/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index 07add6bee0..35997fad1c 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -32,9 +32,9 @@ import ( "time" "github.com/VictoriaMetrics/fastcache" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" ) diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go index adf3778e8d..98f190e8d9 100644 --- a/core/state/snapshot/disklayer_test.go +++ b/core/state/snapshot/disklayer_test.go @@ -30,9 +30,9 @@ import ( "bytes" "testing" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" ) diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index af449380eb..45a8146312 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -34,9 +34,9 @@ import ( "time" "github.com/VictoriaMetrics/fastcache" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index 8c480e1246..51531c8da2 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/generate_test.go @@ -33,9 +33,9 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/ethdb/memorydb" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/log" diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go index c9b98353fb..13309117c9 100644 --- a/core/state/snapshot/iterator.go +++ b/core/state/snapshot/iterator.go @@ -31,8 +31,8 @@ import ( "fmt" "sort" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index 9d1b74406c..ebc9338d06 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -33,7 +33,7 @@ import ( "math/rand" "testing" - "github.com/ava-labs/coreth/core/rawdb" + "github.com/tenderly/coreth/core/rawdb" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index d970913a6f..f6ec85e9af 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -33,9 +33,9 @@ import ( "time" "github.com/VictoriaMetrics/fastcache" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 0fd824f512..b2c8feed5f 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -36,10 +36,10 @@ import ( "time" "github.com/VictoriaMetrics/fastcache" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/metrics" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/metrics" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index 6ccee14dc7..2d4177a164 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -33,7 +33,7 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/core/rawdb" + "github.com/tenderly/coreth/core/rawdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" ) diff --git a/core/state/snapshot/wipe.go b/core/state/snapshot/wipe.go index 36bf376f3b..611d52ac69 100644 --- a/core/state/snapshot/wipe.go +++ b/core/state/snapshot/wipe.go @@ -30,8 +30,8 @@ import ( "bytes" "time" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/core/state/snapshot/wipe_test.go b/core/state/snapshot/wipe_test.go index e7ac20c13a..7d4828f1b6 100644 --- a/core/state/snapshot/wipe_test.go +++ b/core/state/snapshot/wipe_test.go @@ -30,8 +30,8 @@ import ( "math/rand" "testing" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/state/state_object.go b/core/state/state_object.go index 4d959f42b2..98b46bf016 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -34,8 +34,8 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" diff --git a/core/state/state_test.go b/core/state/state_test.go index e7a05ef2e7..1feb535dec 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -27,8 +27,8 @@ package state import ( - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/state/statedb.go b/core/state/statedb.go index 621971e0b6..03b461b3a8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -34,11 +34,11 @@ import ( "sort" "time" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/metrics" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/metrics" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 9c295f9e5b..ab21f1f0b7 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -39,9 +39,9 @@ import ( "testing" "testing/quick" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index e00544a7d2..4957c0d15f 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -29,7 +29,7 @@ package state import ( "sync" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/core/state/trie_prefetcher_test.go b/core/state/trie_prefetcher_test.go index 563a773dba..503911a84b 100644 --- a/core/state/trie_prefetcher_test.go +++ b/core/state/trie_prefetcher_test.go @@ -31,7 +31,7 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/core/rawdb" + "github.com/tenderly/coreth/core/rawdb" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/state_manager.go b/core/state_manager.go index 5067fcf7aa..d2903abd3a 100644 --- a/core/state_manager.go +++ b/core/state_manager.go @@ -31,8 +31,8 @@ import ( "math/rand" "time" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/state_manager_test.go b/core/state_manager_test.go index 7e3bda1a3d..5ce7b237ac 100644 --- a/core/state_manager_test.go +++ b/core/state_manager_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index a1e0cde55d..a97f3fdf7b 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -30,11 +30,11 @@ import ( "math/big" "sync/atomic" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" ) // statePrefetcher is a basic Prefetcher, which blindly executes a block on top diff --git a/core/state_processor.go b/core/state_processor.go index 535a39b642..6756eb926b 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -30,12 +30,12 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/consensus/misc" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/consensus/misc" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 79185dd232..cfd5eded74 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -30,12 +30,12 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/trie" diff --git a/core/state_transition.go b/core/state_transition.go index 5af28d9980..b5537c877c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -34,10 +34,10 @@ import ( "github.com/ethereum/go-ethereum/crypto" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/vmerrs" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/test_blockchain.go b/core/test_blockchain.go index 5a311cf03c..dcdf6010d5 100644 --- a/core/test_blockchain.go +++ b/core/test_blockchain.go @@ -9,12 +9,12 @@ import ( "strings" "testing" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/core/tx_cacher.go b/core/tx_cacher.go index feff21d5f6..5d2670114c 100644 --- a/core/tx_cacher.go +++ b/core/tx_cacher.go @@ -29,7 +29,7 @@ package core import ( "sync" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" ) // txSenderCacherRequest is a request for recovering transaction senders with a diff --git a/core/tx_journal.go b/core/tx_journal.go index b2bfa53865..3628d22391 100644 --- a/core/tx_journal.go +++ b/core/tx_journal.go @@ -31,7 +31,7 @@ import ( "io" "os" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/core/tx_list.go b/core/tx_list.go index 4a6999e373..be1cf88ef8 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -35,7 +35,7 @@ import ( "sync/atomic" "time" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/tx_list_test.go b/core/tx_list_test.go index ecfa91542a..1022432dfa 100644 --- a/core/tx_list_test.go +++ b/core/tx_list_test.go @@ -31,7 +31,7 @@ import ( "math/rand" "testing" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/core/tx_noncer.go b/core/tx_noncer.go index 0dcd31c41a..dfb9b18c8f 100644 --- a/core/tx_noncer.go +++ b/core/tx_noncer.go @@ -29,7 +29,7 @@ package core import ( "sync" - "github.com/ava-labs/coreth/core/state" + "github.com/tenderly/coreth/core/state" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/tx_pool.go b/core/tx_pool.go index c45f13fb7a..a5a4355d33 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -36,11 +36,11 @@ import ( "sync/atomic" "time" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/metrics" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/metrics" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/event" diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index b4f121f4d8..453825b139 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -39,11 +39,11 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" diff --git a/core/types.go b/core/types.go index aa8d987324..cf8ff22aa7 100644 --- a/core/types.go +++ b/core/types.go @@ -27,9 +27,9 @@ package core import ( - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" ) // Validator is an interface which defines the standard for block validation. It diff --git a/core/types/block_test.go b/core/types/block_test.go index 892e5c4cf6..034db8f7bc 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -33,7 +33,7 @@ import ( "reflect" "testing" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go index 60f9da10eb..a239db9d8a 100644 --- a/core/types/hashing_test.go +++ b/core/types/hashing_test.go @@ -34,8 +34,8 @@ import ( mrand "math/rand" "testing" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" diff --git a/core/types/receipt.go b/core/types/receipt.go index 176cb109b6..970c90a400 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -34,7 +34,7 @@ import ( "math/big" "unsafe" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index d0c1553ee1..9c0bad4fd5 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -33,7 +33,7 @@ import ( "reflect" "testing" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index a717749b6a..1297fe2ea7 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -32,7 +32,7 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index a380d5d8cd..302e4a0efc 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -33,10 +33,10 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/constants" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/precompile" - "github.com/ava-labs/coreth/vmerrs" + "github.com/tenderly/coreth/constants" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/precompile" + "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" diff --git a/core/vm/contracts_stateful.go b/core/vm/contracts_stateful.go index 096026e537..10927c8ce9 100644 --- a/core/vm/contracts_stateful.go +++ b/core/vm/contracts_stateful.go @@ -7,8 +7,8 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/precompile" - "github.com/ava-labs/coreth/vmerrs" + "github.com/tenderly/coreth/precompile" + "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" ) diff --git a/core/vm/contracts_stateful_test.go b/core/vm/contracts_stateful_test.go index e07dbf7eee..8d503a7710 100644 --- a/core/vm/contracts_stateful_test.go +++ b/core/vm/contracts_stateful_test.go @@ -7,9 +7,9 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/assert" diff --git a/core/vm/eips.go b/core/vm/eips.go index e79b66bc0b..966f13dbe0 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -30,7 +30,7 @@ import ( "fmt" "sort" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/holiman/uint256" ) diff --git a/core/vm/evm.go b/core/vm/evm.go index ec1dab6d18..57638b03db 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -31,10 +31,10 @@ import ( "sync/atomic" "time" - "github.com/ava-labs/coreth/constants" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/precompile" - "github.com/ava-labs/coreth/vmerrs" + "github.com/tenderly/coreth/constants" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/precompile" + "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/holiman/uint256" diff --git a/core/vm/gas.go b/core/vm/gas.go index 1a195acf2a..dbae956489 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -27,7 +27,7 @@ package vm import ( - "github.com/ava-labs/coreth/vmerrs" + "github.com/tenderly/coreth/vmerrs" "github.com/holiman/uint256" ) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index d361e23d68..23a2cf01e1 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -29,8 +29,8 @@ package vm import ( "errors" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/vmerrs" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" ) diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index 92d5d30113..bb00859b5f 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -31,9 +31,9 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index c2d3d6db26..d61e4c9e75 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -30,9 +30,9 @@ import ( "errors" "sync/atomic" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/vmerrs" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" "golang.org/x/crypto/sha3" diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 8c9e283d39..b607213379 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -34,7 +34,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/holiman/uint256" diff --git a/core/vm/interface.go b/core/vm/interface.go index bde4b08e2b..375c786a24 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -29,7 +29,7 @@ package vm import ( "math/big" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 1aef682c51..4878878b29 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -29,7 +29,7 @@ package vm import ( "hash" - "github.com/ava-labs/coreth/vmerrs" + "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/log" diff --git a/core/vm/interpreter_test.go b/core/vm/interpreter_test.go index 39dfff959c..5b4c42f8b6 100644 --- a/core/vm/interpreter_test.go +++ b/core/vm/interpreter_test.go @@ -31,9 +31,9 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" ) diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index b87dfb4f56..6c44615b56 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -29,7 +29,7 @@ package vm import ( "fmt" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" ) type ( diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 07e6e07eb6..3355455a9c 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -29,8 +29,8 @@ package vm import ( "errors" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/vmerrs" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" ) diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 5293c40846..6296065672 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -27,8 +27,8 @@ package runtime import ( - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/vm" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/vm" ) func NewEnv(cfg *Config) *vm.EVM { diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 74d5499ff4..04dac84343 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -31,10 +31,10 @@ import ( "math/big" "time" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/core/vm/runtime/runtime_example_test.go b/core/vm/runtime/runtime_example_test.go index 9850e283be..8fa1217255 100644 --- a/core/vm/runtime/runtime_example_test.go +++ b/core/vm/runtime/runtime_example_test.go @@ -29,7 +29,7 @@ package runtime_test import ( "fmt" - "github.com/ava-labs/coreth/core/vm/runtime" + "github.com/tenderly/coreth/core/vm/runtime" "github.com/ethereum/go-ethereum/common" ) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index b68b62cf39..429d89bf61 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -34,21 +34,21 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/accounts/abi" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth/tracers" - "github.com/ava-labs/coreth/eth/tracers/logger" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/accounts/abi" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/tracers" + "github.com/tenderly/coreth/eth/tracers/logger" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/asm" // force-load native tracers to trigger registration - _ "github.com/ava-labs/coreth/eth/tracers/native" + _ "github.com/tenderly/coreth/eth/tracers/native" ) func TestDefaults(t *testing.T) { diff --git a/core/vm/stack_table.go b/core/vm/stack_table.go index 487acaefdb..0ee3c12611 100644 --- a/core/vm/stack_table.go +++ b/core/vm/stack_table.go @@ -27,7 +27,7 @@ package vm import ( - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" ) func minSwapStack(n int) int { diff --git a/eth/api.go b/eth/api.go index 338d957fb2..8d6184828f 100644 --- a/eth/api.go +++ b/eth/api.go @@ -36,13 +36,13 @@ import ( "strings" "time" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/internal/ethapi" - "github.com/ava-labs/coreth/rpc" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/internal/ethapi" + "github.com/tenderly/coreth/rpc" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" diff --git a/eth/api_backend.go b/eth/api_backend.go index 6b8e0fbb16..dfd66a48fb 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -32,19 +32,19 @@ import ( "math/big" "time" - "github.com/ava-labs/coreth/accounts" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/bloombits" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth/gasprice" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/accounts" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/bloombits" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/gasprice" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" ) diff --git a/eth/backend.go b/eth/backend.go index a31a1b962c..1f26bcb3b3 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -34,26 +34,26 @@ import ( "time" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/coreth/accounts" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/bloombits" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state/pruner" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth/ethconfig" - "github.com/ava-labs/coreth/eth/filters" - "github.com/ava-labs/coreth/eth/gasprice" - "github.com/ava-labs/coreth/eth/tracers" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/internal/ethapi" - "github.com/ava-labs/coreth/internal/shutdowncheck" - "github.com/ava-labs/coreth/miner" - "github.com/ava-labs/coreth/node" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/accounts" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/bloombits" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state/pruner" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/ethconfig" + "github.com/tenderly/coreth/eth/filters" + "github.com/tenderly/coreth/eth/gasprice" + "github.com/tenderly/coreth/eth/tracers" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/internal/ethapi" + "github.com/tenderly/coreth/internal/shutdowncheck" + "github.com/tenderly/coreth/miner" + "github.com/tenderly/coreth/node" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" diff --git a/eth/bloombits.go b/eth/bloombits.go index ecc0aaf157..31be317434 100644 --- a/eth/bloombits.go +++ b/eth/bloombits.go @@ -29,7 +29,7 @@ package eth import ( "time" - "github.com/ava-labs/coreth/core/rawdb" + "github.com/tenderly/coreth/core/rawdb" "github.com/ethereum/go-ethereum/common/bitutil" ) diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 903beaf426..b24a9eeca6 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -29,9 +29,9 @@ package ethconfig import ( "time" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/eth/gasprice" - "github.com/ava-labs/coreth/miner" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/eth/gasprice" + "github.com/tenderly/coreth/miner" "github.com/ethereum/go-ethereum/common" ) diff --git a/eth/filters/api.go b/eth/filters/api.go index 619ebb743c..a1e155906f 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -35,9 +35,9 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/interfaces" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/interfaces" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/event" diff --git a/eth/filters/api_test.go b/eth/filters/api_test.go index 6f35639581..1b9ab3ec02 100644 --- a/eth/filters/api_test.go +++ b/eth/filters/api_test.go @@ -21,7 +21,7 @@ import ( "fmt" "testing" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" ) diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 227ccd6425..f7f549be24 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -32,13 +32,13 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/core/vm" + "github.com/tenderly/coreth/core/vm" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/bloombits" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/bloombits" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" ) diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index 86f5a0bd4d..81f8089db3 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -34,11 +34,11 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/interfaces" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/interfaces" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index c6a9b64e57..c4f0d6e6d6 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -33,8 +33,8 @@ import ( "math/big" "sort" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/eth/gasprice/feehistory_test.go b/eth/gasprice/feehistory_test.go index 8d6260f8b5..84801c22cf 100644 --- a/eth/gasprice/feehistory_test.go +++ b/eth/gasprice/feehistory_test.go @@ -32,11 +32,11 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/types" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" ) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index f2e091a48c..394998dc42 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -33,11 +33,11 @@ import ( "sync" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/event" diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 974101dcc1..2285f6a2c9 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -31,14 +31,14 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 690af25e71..295b6a06df 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -32,11 +32,11 @@ import ( "math/big" "time" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 482723ce0e..6c19a9a1cb 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -36,16 +36,16 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth/tracers/logger" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/internal/ethapi" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/tracers/logger" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/internal/ethapi" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 7cd7a37a72..90e1b52e18 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -37,17 +37,17 @@ import ( "sort" "testing" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/internal/ethapi" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/internal/ethapi" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index fc13ea2422..1d27088714 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -36,13 +36,13 @@ import ( "testing" "unicode" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth/tracers" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/tests" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/tracers" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/tests" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" @@ -50,7 +50,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" // Force-load native, to trigger registration - _ "github.com/ava-labs/coreth/eth/tracers/native" + _ "github.com/tenderly/coreth/eth/tracers/native" ) // To generate a new callTracer test, copy paste the makeTest method below into diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index 068de3c7ef..088c3b1159 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -20,8 +20,8 @@ import ( "math/big" "time" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" "github.com/ethereum/go-ethereum/common" ) diff --git a/eth/tracers/logger/gen_structlog.go b/eth/tracers/logger/gen_structlog.go index 4e32862c99..6c050a32f5 100644 --- a/eth/tracers/logger/gen_structlog.go +++ b/eth/tracers/logger/gen_structlog.go @@ -5,7 +5,7 @@ package logger import ( "encoding/json" - "github.com/ava-labs/coreth/core/vm" + "github.com/tenderly/coreth/core/vm" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index f9059fb48b..995804669c 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -24,9 +24,9 @@ import ( "strings" "time" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index 0eb4082e58..3a435218cd 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -22,7 +22,7 @@ import ( "math/big" "time" - "github.com/ava-labs/coreth/core/vm" + "github.com/tenderly/coreth/core/vm" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" ) diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index 47b5be5eff..c723e338fc 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -20,9 +20,9 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" ) diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index 08fe26966f..8d0c0c7713 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -33,8 +33,8 @@ import ( "sync/atomic" "time" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth/tracers" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/tracers" "github.com/ethereum/go-ethereum/common" ) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 2a133cd552..64ed05db33 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -35,8 +35,8 @@ import ( "sync/atomic" "time" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth/tracers" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/tracers" "github.com/ethereum/go-ethereum/common" ) diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index 4dfc32930e..267add8cb5 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -31,8 +31,8 @@ import ( "math/big" "time" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth/tracers" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/tracers" "github.com/ethereum/go-ethereum/common" ) diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 914cfcdb48..e3f3120603 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -32,9 +32,9 @@ import ( "sync/atomic" "time" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth/tracers" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/tracers" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" diff --git a/eth/tracers/native/tracer.go b/eth/tracers/native/tracer.go index 0703ddbf30..fc93fe0817 100644 --- a/eth/tracers/native/tracer.go +++ b/eth/tracers/native/tracer.go @@ -47,7 +47,7 @@ package native import ( "errors" - "github.com/ava-labs/coreth/eth/tracers" + "github.com/tenderly/coreth/eth/tracers" ) // init registers itself this packages as a lookup for tracers. diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index cb1576f651..06f6965ee6 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -21,7 +21,7 @@ import ( "encoding/json" "errors" - "github.com/ava-labs/coreth/core/vm" + "github.com/tenderly/coreth/core/vm" "github.com/ethereum/go-ethereum/common" ) diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index adb0ec5cd4..ffd3eba504 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -30,13 +30,13 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth/tracers/logger" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/tests" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/tracers/logger" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/tests" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" diff --git a/ethclient/corethclient/corethclient.go b/ethclient/corethclient/corethclient.go index 8d7654fcf2..7598c15f66 100644 --- a/ethclient/corethclient/corethclient.go +++ b/ethclient/corethclient/corethclient.go @@ -33,10 +33,10 @@ import ( "runtime" "runtime/debug" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethclient" - "github.com/ava-labs/coreth/interfaces" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethclient" + "github.com/tenderly/coreth/interfaces" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ) diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index e166ba75f2..8dc6c3938f 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -35,10 +35,10 @@ import ( "math/big" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/accounts/abi/bind" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/interfaces" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/accounts/abi/bind" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/interfaces" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ) diff --git a/ethclient/signer.go b/ethclient/signer.go index ba647de1a6..f4822449ae 100644 --- a/ethclient/signer.go +++ b/ethclient/signer.go @@ -30,7 +30,7 @@ import ( "errors" "math/big" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" ) diff --git a/ethdb/dbtest/testsuite.go b/ethdb/dbtest/testsuite.go index 90c92ee3dc..6fd215b44b 100644 --- a/ethdb/dbtest/testsuite.go +++ b/ethdb/dbtest/testsuite.go @@ -32,7 +32,7 @@ import ( "sort" "testing" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/ethdb" ) // TestDatabaseSuite runs a suite of tests against a KeyValueStore database diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index 27bad94c61..28f7fc51fc 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -37,8 +37,8 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/syndtr/goleveldb/leveldb" diff --git a/ethdb/leveldb/leveldb_test.go b/ethdb/leveldb/leveldb_test.go index 8498a5a6be..7de97878dc 100644 --- a/ethdb/leveldb/leveldb_test.go +++ b/ethdb/leveldb/leveldb_test.go @@ -29,8 +29,8 @@ package leveldb import ( "testing" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/ethdb/dbtest" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/dbtest" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/storage" ) diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index 769581ff86..a75ebb555e 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -33,7 +33,7 @@ import ( "strings" "sync" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" ) diff --git a/ethdb/memorydb/memorydb_test.go b/ethdb/memorydb/memorydb_test.go index 34361e9f1b..1db0383ccc 100644 --- a/ethdb/memorydb/memorydb_test.go +++ b/ethdb/memorydb/memorydb_test.go @@ -29,8 +29,8 @@ package memorydb import ( "testing" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/ethdb/dbtest" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/dbtest" ) func TestMemoryDB(t *testing.T) { diff --git a/go.mod b/go.mod index a096bf4368..cd79b52146 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/ava-labs/coreth +module github.com/tenderly/coreth go 1.17 diff --git a/interfaces/interfaces.go b/interfaces/interfaces.go index 100e658c3c..05593ecdd8 100644 --- a/interfaces/interfaces.go +++ b/interfaces/interfaces.go @@ -32,7 +32,7 @@ import ( "errors" "math/big" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" ) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 1b1640c244..9f284eccb9 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -35,17 +35,17 @@ import ( "time" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/accounts" - "github.com/ava-labs/coreth/accounts/keystore" - "github.com/ava-labs/coreth/accounts/scwallet" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/eth/tracers/logger" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/rpc" - "github.com/ava-labs/coreth/vmerrs" + "github.com/tenderly/coreth/accounts" + "github.com/tenderly/coreth/accounts/keystore" + "github.com/tenderly/coreth/accounts/scwallet" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/tracers/logger" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rpc" + "github.com/tenderly/coreth/vmerrs" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 2607f0b085..be712f8e28 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -32,16 +32,16 @@ import ( "math/big" "time" - "github.com/ava-labs/coreth/accounts" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/bloombits" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/accounts" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/bloombits" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" ) diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 642cc2b773..8c7f6bf3ec 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -33,8 +33,8 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" diff --git a/internal/shutdowncheck/shutdown_tracker.go b/internal/shutdowncheck/shutdown_tracker.go index 8395da4260..d82fef7bd7 100644 --- a/internal/shutdowncheck/shutdown_tracker.go +++ b/internal/shutdowncheck/shutdown_tracker.go @@ -29,8 +29,8 @@ package shutdowncheck import ( "time" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 46d7c5bced..2af79742d3 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/metrics" "github.com/prometheus/client_golang/prometheus" diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 967e3f2602..97bb7d627d 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/metrics" ) func TestGatherer(t *testing.T) { diff --git a/miner/miner.go b/miner/miner.go index 6a9979fd49..f070ae4163 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -29,10 +29,10 @@ package miner import ( "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" ) diff --git a/miner/worker.go b/miner/worker.go index c89225adec..6d9fed0bff 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -38,14 +38,14 @@ import ( "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/utils/units" - "github.com/ava-labs/coreth/consensus" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/consensus/misc" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/vmerrs" + "github.com/tenderly/coreth/consensus" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/consensus/misc" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" diff --git a/node/api.go b/node/api.go index 4a4049753e..3b77d80d2f 100644 --- a/node/api.go +++ b/node/api.go @@ -27,8 +27,8 @@ package node import ( - "github.com/ava-labs/coreth/internal/debug" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/internal/debug" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/node/config.go b/node/config.go index 5b7be4e3d6..7f33363a45 100644 --- a/node/config.go +++ b/node/config.go @@ -32,10 +32,10 @@ import ( "os" "path/filepath" - "github.com/ava-labs/coreth/accounts" - "github.com/ava-labs/coreth/accounts/external" - "github.com/ava-labs/coreth/accounts/keystore" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/accounts" + "github.com/tenderly/coreth/accounts/external" + "github.com/tenderly/coreth/accounts/keystore" + "github.com/tenderly/coreth/rpc" "github.com/ethereum/go-ethereum/log" ) diff --git a/node/defaults.go b/node/defaults.go index e4c826b97c..6a9f174b8c 100644 --- a/node/defaults.go +++ b/node/defaults.go @@ -27,7 +27,7 @@ package node import ( - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/rpc" ) const ( diff --git a/node/node.go b/node/node.go index da935bc963..3e7e06d54b 100644 --- a/node/node.go +++ b/node/node.go @@ -27,8 +27,8 @@ package node import ( - "github.com/ava-labs/coreth/accounts" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/accounts" + "github.com/tenderly/coreth/rpc" ) // Node is a container on which services can be registered. diff --git a/params/config.go b/params/config.go index 27287e99ef..9ff2a776d5 100644 --- a/params/config.go +++ b/params/config.go @@ -32,8 +32,8 @@ import ( "math/big" "time" - "github.com/ava-labs/coreth/precompile" - "github.com/ava-labs/coreth/utils" + "github.com/tenderly/coreth/precompile" + "github.com/tenderly/coreth/utils" "github.com/ethereum/go-ethereum/common" ) diff --git a/peer/network.go b/peer/network.go index b824f80420..1e3ebf7ccb 100644 --- a/peer/network.go +++ b/peer/network.go @@ -18,8 +18,8 @@ import ( "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/version" - "github.com/ava-labs/coreth/peer/stats" - "github.com/ava-labs/coreth/plugin/evm/message" + "github.com/tenderly/coreth/peer/stats" + "github.com/tenderly/coreth/plugin/evm/message" "github.com/ethereum/go-ethereum/log" ) diff --git a/peer/network_test.go b/peer/network_test.go index a0fbc20c12..b31b380976 100644 --- a/peer/network_test.go +++ b/peer/network_test.go @@ -14,7 +14,7 @@ import ( "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/coreth/plugin/evm/message" + "github.com/tenderly/coreth/plugin/evm/message" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" diff --git a/peer/peer_tracker.go b/peer/peer_tracker.go index 1096466015..4122419e20 100644 --- a/peer/peer_tracker.go +++ b/peer/peer_tracker.go @@ -11,7 +11,7 @@ import ( "github.com/ava-labs/avalanchego/ids" utils_math "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/version" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/log" ) diff --git a/peer/stats/stats.go b/peer/stats/stats.go index b17ef23455..d8e48c5b92 100644 --- a/peer/stats/stats.go +++ b/peer/stats/stats.go @@ -6,7 +6,7 @@ package stats import ( "time" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/metrics" ) // RequestHandlerStats provides the interface for metrics on request handling. diff --git a/peer/waiting_handler.go b/peer/waiting_handler.go index 53ac48bf46..68d4a4aa0e 100644 --- a/peer/waiting_handler.go +++ b/peer/waiting_handler.go @@ -5,7 +5,7 @@ package peer import ( "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/plugin/evm/message" + "github.com/tenderly/coreth/plugin/evm/message" ) var _ message.ResponseHandler = &waitingResponseHandler{} diff --git a/plugin/evm/atomic_syncer.go b/plugin/evm/atomic_syncer.go index 4dcef2642c..928ab6a858 100644 --- a/plugin/evm/atomic_syncer.go +++ b/plugin/evm/atomic_syncer.go @@ -11,8 +11,8 @@ import ( "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/coreth/plugin/evm/message" - syncclient "github.com/ava-labs/coreth/sync/client" + "github.com/tenderly/coreth/plugin/evm/message" + syncclient "github.com/tenderly/coreth/sync/client" "github.com/ethereum/go-ethereum/common" ) diff --git a/plugin/evm/atomic_syncer_test.go b/plugin/evm/atomic_syncer_test.go index 63e729d76f..31819ce45e 100644 --- a/plugin/evm/atomic_syncer_test.go +++ b/plugin/evm/atomic_syncer_test.go @@ -14,12 +14,12 @@ import ( "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/database/versiondb" - "github.com/ava-labs/coreth/ethdb/memorydb" - "github.com/ava-labs/coreth/plugin/evm/message" - syncclient "github.com/ava-labs/coreth/sync/client" - "github.com/ava-labs/coreth/sync/handlers" - handlerstats "github.com/ava-labs/coreth/sync/handlers/stats" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/plugin/evm/message" + syncclient "github.com/tenderly/coreth/sync/client" + "github.com/tenderly/coreth/sync/handlers" + handlerstats "github.com/tenderly/coreth/sync/handlers/stats" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" ) diff --git a/plugin/evm/atomic_trie.go b/plugin/evm/atomic_trie.go index c97eba6afd..19b1062c7e 100644 --- a/plugin/evm/atomic_trie.go +++ b/plugin/evm/atomic_trie.go @@ -17,9 +17,9 @@ import ( "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/coreth/core/types" - syncclient "github.com/ava-labs/coreth/sync/client" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/types" + syncclient "github.com/tenderly/coreth/sync/client" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/plugin/evm/atomic_trie_iterator.go b/plugin/evm/atomic_trie_iterator.go index 394af477a3..5faead248a 100644 --- a/plugin/evm/atomic_trie_iterator.go +++ b/plugin/evm/atomic_trie_iterator.go @@ -12,7 +12,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" ) diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 969396691d..596efe5f57 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -12,8 +12,8 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" diff --git a/plugin/evm/block_builder.go b/plugin/evm/block_builder.go index 627e6edae9..6ff658aaca 100644 --- a/plugin/evm/block_builder.go +++ b/plugin/evm/block_builder.go @@ -8,8 +8,8 @@ import ( "sync" "time" - coreth "github.com/ava-labs/coreth/chain" - "github.com/ava-labs/coreth/params" + coreth "github.com/tenderly/coreth/chain" + "github.com/tenderly/coreth/params" "github.com/ava-labs/avalanchego/snow" commonEng "github.com/ava-labs/avalanchego/snow/engine/common" diff --git a/plugin/evm/block_builder_test.go b/plugin/evm/block_builder_test.go index 95b3b18f53..8864a68d54 100644 --- a/plugin/evm/block_builder_test.go +++ b/plugin/evm/block_builder_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/ava-labs/avalanchego/snow" ) diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index 86da1306d0..49cf13c5c4 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -11,11 +11,11 @@ import ( safemath "github.com/ava-labs/avalanchego/utils/math" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/trie" - coreth "github.com/ava-labs/coreth/chain" + coreth "github.com/tenderly/coreth/chain" ) var ( diff --git a/plugin/evm/config.go b/plugin/evm/config.go index 53f09747ad..9d1da725e5 100644 --- a/plugin/evm/config.go +++ b/plugin/evm/config.go @@ -8,7 +8,7 @@ import ( "fmt" "time" - "github.com/ava-labs/coreth/eth" + "github.com/tenderly/coreth/eth" "github.com/spf13/cast" ) diff --git a/plugin/evm/database.go b/plugin/evm/database.go index f13de4168d..21d42afc84 100644 --- a/plugin/evm/database.go +++ b/plugin/evm/database.go @@ -5,7 +5,7 @@ package evm import ( "github.com/ava-labs/avalanchego/database" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/ethdb" ) var _ ethdb.Database = &Database{} diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index 506b983028..43ebe53057 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -7,8 +7,8 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/params" "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go index c9dfca68e5..af08543613 100644 --- a/plugin/evm/export_tx_test.go +++ b/plugin/evm/export_tx_test.go @@ -16,7 +16,7 @@ import ( "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/secp256k1fx" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" ) diff --git a/plugin/evm/gasprice_update.go b/plugin/evm/gasprice_update.go index 71a4ea1a70..5056fea880 100644 --- a/plugin/evm/gasprice_update.go +++ b/plugin/evm/gasprice_update.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" ) type gasPriceUpdater struct { diff --git a/plugin/evm/gasprice_update_test.go b/plugin/evm/gasprice_update_test.go index 24d8337f97..07dfa04d7b 100644 --- a/plugin/evm/gasprice_update_test.go +++ b/plugin/evm/gasprice_update_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" ) type mockGasPriceSetter struct { diff --git a/plugin/evm/gossiper.go b/plugin/evm/gossiper.go index a962cfb932..8d305177ef 100644 --- a/plugin/evm/gossiper.go +++ b/plugin/evm/gossiper.go @@ -11,7 +11,7 @@ import ( "github.com/ava-labs/avalanchego/codec" - "github.com/ava-labs/coreth/peer" + "github.com/tenderly/coreth/peer" "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/ids" @@ -22,10 +22,10 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/plugin/evm/message" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/plugin/evm/message" ) const ( diff --git a/plugin/evm/gossiper_atomic_gossiping_test.go b/plugin/evm/gossiper_atomic_gossiping_test.go index 5721f1a2d3..28441f193c 100644 --- a/plugin/evm/gossiper_atomic_gossiping_test.go +++ b/plugin/evm/gossiper_atomic_gossiping_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ava-labs/coreth/plugin/evm/message" + "github.com/tenderly/coreth/plugin/evm/message" ) // locally issued txs should be gossiped diff --git a/plugin/evm/gossiper_eth_gossiping_test.go b/plugin/evm/gossiper_eth_gossiping_test.go index 27f9932196..d364bb6cb3 100644 --- a/plugin/evm/gossiper_eth_gossiping_test.go +++ b/plugin/evm/gossiper_eth_gossiping_test.go @@ -20,10 +20,10 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/plugin/evm/message" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/plugin/evm/message" ) func fundAddressByGenesis(addrs []common.Address) (string, error) { diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 9be6e69747..fd97d48d06 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -7,8 +7,8 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/params" "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" diff --git a/plugin/evm/import_tx_test.go b/plugin/evm/import_tx_test.go index 94d26ed3d7..eaa7eed424 100644 --- a/plugin/evm/import_tx_test.go +++ b/plugin/evm/import_tx_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ava-labs/avalanchego/chains/atomic" diff --git a/plugin/evm/mempool_atomic_gossiping_test.go b/plugin/evm/mempool_atomic_gossiping_test.go index 87efbff4d6..814c59d842 100644 --- a/plugin/evm/mempool_atomic_gossiping_test.go +++ b/plugin/evm/mempool_atomic_gossiping_test.go @@ -6,7 +6,7 @@ package evm import ( "testing" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto" diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 89baadfc5c..e6ac9aabc4 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -15,7 +15,7 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto" "github.com/ava-labs/avalanchego/utils/formatting" "github.com/ava-labs/avalanchego/utils/json" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" diff --git a/plugin/evm/static_service.go b/plugin/evm/static_service.go index 9c592251d0..4ba73966ad 100644 --- a/plugin/evm/static_service.go +++ b/plugin/evm/static_service.go @@ -8,7 +8,7 @@ import ( "encoding/json" "github.com/ava-labs/avalanchego/utils/formatting" - "github.com/ava-labs/coreth/core" + "github.com/tenderly/coreth/core" ) // StaticService defines the static API services exposed by the evm diff --git a/plugin/evm/syncervm_client.go b/plugin/evm/syncervm_client.go index ebe35cc3e5..493106c3c4 100644 --- a/plugin/evm/syncervm_client.go +++ b/plugin/evm/syncervm_client.go @@ -14,14 +14,14 @@ import ( commonEng "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/vms/components/chain" - coreth "github.com/ava-labs/coreth/chain" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/plugin/evm/message" - syncclient "github.com/ava-labs/coreth/sync/client" - "github.com/ava-labs/coreth/sync/statesync" + coreth "github.com/tenderly/coreth/chain" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/plugin/evm/message" + syncclient "github.com/tenderly/coreth/sync/client" + "github.com/tenderly/coreth/sync/statesync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/plugin/evm/syncervm_server.go b/plugin/evm/syncervm_server.go index 173486411d..69a06477ce 100644 --- a/plugin/evm/syncervm_server.go +++ b/plugin/evm/syncervm_server.go @@ -9,8 +9,8 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/plugin/evm/message" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/plugin/evm/message" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index 5c3ede57d7..af68021ef1 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -23,18 +23,18 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto" "github.com/ava-labs/avalanchego/utils/units" - "github.com/ava-labs/coreth/accounts/keystore" - coreth "github.com/ava-labs/coreth/chain" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/metrics" - "github.com/ava-labs/coreth/params" - statesyncclient "github.com/ava-labs/coreth/sync/client" - "github.com/ava-labs/coreth/sync/statesync" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/accounts/keystore" + coreth "github.com/tenderly/coreth/chain" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/metrics" + "github.com/tenderly/coreth/params" + statesyncclient "github.com/tenderly/coreth/sync/client" + "github.com/tenderly/coreth/sync/statesync" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/plugin/evm/test_tx.go b/plugin/evm/test_tx.go index 3257dd3f93..8a7399f1bb 100644 --- a/plugin/evm/test_tx.go +++ b/plugin/evm/test_tx.go @@ -15,8 +15,8 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/params" ) type TestTx struct { diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go index 78d0c7623b..0389b3f174 100644 --- a/plugin/evm/tx.go +++ b/plugin/evm/tx.go @@ -12,8 +12,8 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/params" "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/codec" diff --git a/plugin/evm/tx_test.go b/plugin/evm/tx_test.go index 25aef6644f..36ad084ce2 100644 --- a/plugin/evm/tx_test.go +++ b/plugin/evm/tx_test.go @@ -10,7 +10,7 @@ import ( "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" ) func TestCalculateDynamicFee(t *testing.T) { diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 33e14c1bd3..7d1695546e 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -19,25 +19,25 @@ import ( avalanchegoMetrics "github.com/ava-labs/avalanchego/api/metrics" - coreth "github.com/ava-labs/coreth/chain" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/eth/ethconfig" - "github.com/ava-labs/coreth/ethdb" - corethPrometheus "github.com/ava-labs/coreth/metrics/prometheus" - "github.com/ava-labs/coreth/node" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/peer" - "github.com/ava-labs/coreth/plugin/evm/message" - "github.com/ava-labs/coreth/rpc" - statesyncclient "github.com/ava-labs/coreth/sync/client" - "github.com/ava-labs/coreth/sync/client/stats" - "github.com/ava-labs/coreth/sync/handlers" - handlerstats "github.com/ava-labs/coreth/sync/handlers/stats" - "github.com/ava-labs/coreth/trie" + coreth "github.com/tenderly/coreth/chain" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/eth/ethconfig" + "github.com/tenderly/coreth/ethdb" + corethPrometheus "github.com/tenderly/coreth/metrics/prometheus" + "github.com/tenderly/coreth/node" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/peer" + "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/rpc" + statesyncclient "github.com/tenderly/coreth/sync/client" + "github.com/tenderly/coreth/sync/client/stats" + "github.com/tenderly/coreth/sync/handlers" + handlerstats "github.com/tenderly/coreth/sync/handlers/stats" + "github.com/tenderly/coreth/trie" "github.com/prometheus/client_golang/prometheus" // Force-load tracer engine to trigger registration @@ -45,9 +45,9 @@ import ( // We must import this package (not referenced elsewhere) so that the native "callTracer" // is added to a map of client-accessible tracers. In geth, this is done // inside of cmd/geth. - _ "github.com/ava-labs/coreth/eth/tracers/native" + _ "github.com/tenderly/coreth/eth/tracers/native" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index f9ada41628..a429d9c273 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -17,7 +17,7 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" @@ -44,14 +44,14 @@ import ( engCommon "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/eth" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/rpc" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/eth" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rpc" - accountKeystore "github.com/ava-labs/coreth/accounts/keystore" + accountKeystore "github.com/tenderly/coreth/accounts/keystore" ) var ( diff --git a/plugin/main.go b/plugin/main.go index bfd6c965eb..d5fd84aa13 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -11,7 +11,7 @@ import ( "github.com/ava-labs/avalanchego/utils/ulimit" "github.com/ava-labs/avalanchego/vms/rpcchainvm" - "github.com/ava-labs/coreth/plugin/evm" + "github.com/tenderly/coreth/plugin/evm" ) func main() { diff --git a/precompile/stateful_precompile_config.go b/precompile/stateful_precompile_config.go index b262fcdba6..710e0e2b3b 100644 --- a/precompile/stateful_precompile_config.go +++ b/precompile/stateful_precompile_config.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/ava-labs/coreth/utils" + "github.com/tenderly/coreth/utils" ) // StatefulPrecompileConfig defines the interface for a stateful precompile to diff --git a/precompile/utils.go b/precompile/utils.go index 758b29d5a6..1a75ffb2ab 100644 --- a/precompile/utils.go +++ b/precompile/utils.go @@ -7,7 +7,7 @@ import ( "fmt" "regexp" - "github.com/ava-labs/coreth/vmerrs" + "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/rpc/handler.go b/rpc/handler.go index 45e4c518e1..1d017cf25b 100644 --- a/rpc/handler.go +++ b/rpc/handler.go @@ -35,7 +35,7 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/log" "golang.org/x/time/rate" ) diff --git a/rpc/metrics.go b/rpc/metrics.go index 14a8626c78..a2a2c3f896 100644 --- a/rpc/metrics.go +++ b/rpc/metrics.go @@ -29,7 +29,7 @@ package rpc import ( "fmt" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/metrics" ) var ( diff --git a/signer/core/apitypes/types.go b/signer/core/apitypes/types.go index cab7f9cf0a..34094d66a6 100644 --- a/signer/core/apitypes/types.go +++ b/signer/core/apitypes/types.go @@ -32,7 +32,7 @@ import ( "math/big" "strings" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ) diff --git a/sync/client/client.go b/sync/client/client.go index ddad77fa3f..bdac407ebe 100644 --- a/sync/client/client.go +++ b/sync/client/client.go @@ -13,19 +13,19 @@ import ( "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/ethdb/memorydb" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/sync/client/stats" + "github.com/tenderly/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/sync/client/stats" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/version" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/peer" - "github.com/ava-labs/coreth/plugin/evm/message" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/peer" + "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" diff --git a/sync/client/client_test.go b/sync/client/client_test.go index 4eb97e1dfd..fd76bfc3cf 100644 --- a/sync/client/client_test.go +++ b/sync/client/client_test.go @@ -15,16 +15,16 @@ import ( "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb/memorydb" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/plugin/evm/message" - clientstats "github.com/ava-labs/coreth/sync/client/stats" - "github.com/ava-labs/coreth/sync/handlers" - handlerstats "github.com/ava-labs/coreth/sync/handlers/stats" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/plugin/evm/message" + clientstats "github.com/tenderly/coreth/sync/client/stats" + "github.com/tenderly/coreth/sync/handlers" + handlerstats "github.com/tenderly/coreth/sync/handlers/stats" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/sync/client/leaf_syncer.go b/sync/client/leaf_syncer.go index 65e61fdf83..72703909e0 100644 --- a/sync/client/leaf_syncer.go +++ b/sync/client/leaf_syncer.go @@ -9,8 +9,8 @@ import ( "fmt" "sync" - "github.com/ava-labs/coreth/plugin/evm/message" - "github.com/ava-labs/coreth/utils" + "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "golang.org/x/sync/errgroup" diff --git a/sync/client/mock_client.go b/sync/client/mock_client.go index 038bdf73bf..e23cdf4322 100644 --- a/sync/client/mock_client.go +++ b/sync/client/mock_client.go @@ -10,9 +10,9 @@ import ( "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/plugin/evm/message" - "github.com/ava-labs/coreth/sync/handlers" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/sync/handlers" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" ) diff --git a/sync/client/mock_network.go b/sync/client/mock_network.go index e17b704e36..59620814ff 100644 --- a/sync/client/mock_network.go +++ b/sync/client/mock_network.go @@ -7,7 +7,7 @@ import ( "errors" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/peer" + "github.com/tenderly/coreth/peer" "github.com/ava-labs/avalanchego/version" ) diff --git a/sync/client/stats/stats.go b/sync/client/stats/stats.go index 14bcb3e732..8e846b03be 100644 --- a/sync/client/stats/stats.go +++ b/sync/client/stats/stats.go @@ -7,8 +7,8 @@ import ( "fmt" "time" - "github.com/ava-labs/coreth/metrics" - "github.com/ava-labs/coreth/plugin/evm/message" + "github.com/tenderly/coreth/metrics" + "github.com/tenderly/coreth/plugin/evm/message" ) var ( diff --git a/sync/handlers/block_request.go b/sync/handlers/block_request.go index db99d9b847..0746db3a2b 100644 --- a/sync/handlers/block_request.go +++ b/sync/handlers/block_request.go @@ -11,9 +11,9 @@ import ( "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/peer" - "github.com/ava-labs/coreth/plugin/evm/message" - "github.com/ava-labs/coreth/sync/handlers/stats" + "github.com/tenderly/coreth/peer" + "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/sync/handlers/stats" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/sync/handlers/block_request_test.go b/sync/handlers/block_request_test.go index 4930d3f230..e261e1da84 100644 --- a/sync/handlers/block_request_test.go +++ b/sync/handlers/block_request_test.go @@ -8,13 +8,13 @@ import ( "testing" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb/memorydb" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/plugin/evm/message" - "github.com/ava-labs/coreth/sync/handlers/stats" + "github.com/tenderly/coreth/consensus/dummy" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/sync/handlers/stats" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/assert" diff --git a/sync/handlers/code_request.go b/sync/handlers/code_request.go index 396322d8a1..26b738b4ec 100644 --- a/sync/handlers/code_request.go +++ b/sync/handlers/code_request.go @@ -10,10 +10,10 @@ import ( "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/plugin/evm/message" - "github.com/ava-labs/coreth/sync/handlers/stats" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/sync/handlers/stats" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/sync/handlers/code_request_test.go b/sync/handlers/code_request_test.go index 15c20f2dfe..cd79509e0d 100644 --- a/sync/handlers/code_request_test.go +++ b/sync/handlers/code_request_test.go @@ -8,13 +8,13 @@ import ( "crypto/rand" "testing" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb/memorydb" - "github.com/ava-labs/coreth/plugin/evm/message" - "github.com/ava-labs/coreth/sync/handlers/stats" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/sync/handlers/stats" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/assert" diff --git a/sync/handlers/handler.go b/sync/handlers/handler.go index bc872e1c49..acb4479c5c 100644 --- a/sync/handlers/handler.go +++ b/sync/handlers/handler.go @@ -8,11 +8,11 @@ import ( "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/plugin/evm/message" - "github.com/ava-labs/coreth/sync/handlers/stats" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/sync/handlers/stats" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" ) diff --git a/sync/handlers/iterators.go b/sync/handlers/iterators.go index 53125c9e99..c6c9377a92 100644 --- a/sync/handlers/iterators.go +++ b/sync/handlers/iterators.go @@ -4,8 +4,8 @@ package handlers import ( - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/ethdb" ) var ( diff --git a/sync/handlers/leafs_request.go b/sync/handlers/leafs_request.go index 38194fe9dc..8c47cd869f 100644 --- a/sync/handlers/leafs_request.go +++ b/sync/handlers/leafs_request.go @@ -14,14 +14,14 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/ethdb/memorydb" - "github.com/ava-labs/coreth/plugin/evm/message" - "github.com/ava-labs/coreth/sync/handlers/stats" - "github.com/ava-labs/coreth/trie" - "github.com/ava-labs/coreth/utils" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/sync/handlers/stats" + "github.com/tenderly/coreth/trie" + "github.com/tenderly/coreth/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) diff --git a/sync/handlers/leafs_request_test.go b/sync/handlers/leafs_request_test.go index 8dad57b5bd..36d4bf2871 100644 --- a/sync/handlers/leafs_request_test.go +++ b/sync/handlers/leafs_request_test.go @@ -10,14 +10,14 @@ import ( "testing" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/ethdb/memorydb" - "github.com/ava-labs/coreth/plugin/evm/message" - "github.com/ava-labs/coreth/sync/handlers/stats" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/sync/handlers/stats" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" diff --git a/sync/handlers/stats/stats.go b/sync/handlers/stats/stats.go index 9dd04c4ea0..9105835f70 100644 --- a/sync/handlers/stats/stats.go +++ b/sync/handlers/stats/stats.go @@ -6,7 +6,7 @@ package stats import ( "time" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/metrics" ) // HandlerStats reports prometheus metrics for the state sync handlers diff --git a/sync/handlers/test_providers.go b/sync/handlers/test_providers.go index 81dafbfd00..cb8278e671 100644 --- a/sync/handlers/test_providers.go +++ b/sync/handlers/test_providers.go @@ -4,8 +4,8 @@ package handlers import ( - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" ) diff --git a/sync/statesync/code_syncer.go b/sync/statesync/code_syncer.go index a3c680d5e1..6a7fba8c60 100644 --- a/sync/statesync/code_syncer.go +++ b/sync/statesync/code_syncer.go @@ -7,9 +7,9 @@ import ( "context" "fmt" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" - statesyncclient "github.com/ava-labs/coreth/sync/client" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + statesyncclient "github.com/tenderly/coreth/sync/client" "github.com/ethereum/go-ethereum/common" ) diff --git a/sync/statesync/code_syncer_test.go b/sync/statesync/code_syncer_test.go index 863b9aa198..4637074f4e 100644 --- a/sync/statesync/code_syncer_test.go +++ b/sync/statesync/code_syncer_test.go @@ -8,12 +8,12 @@ import ( "testing" "github.com/ava-labs/avalanchego/utils" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb/memorydb" - "github.com/ava-labs/coreth/plugin/evm/message" - statesyncclient "github.com/ava-labs/coreth/sync/client" - "github.com/ava-labs/coreth/sync/handlers" - handlerstats "github.com/ava-labs/coreth/sync/handlers/stats" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/plugin/evm/message" + statesyncclient "github.com/tenderly/coreth/sync/client" + "github.com/tenderly/coreth/sync/handlers" + handlerstats "github.com/tenderly/coreth/sync/handlers/stats" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/sync/statesync/state_syncer.go b/sync/statesync/state_syncer.go index 970b12bbad..1a59c60cbb 100644 --- a/sync/statesync/state_syncer.go +++ b/sync/statesync/state_syncer.go @@ -9,12 +9,12 @@ import ( "sync" "time" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/plugin/evm/message" - syncclient "github.com/ava-labs/coreth/sync/client" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/plugin/evm/message" + syncclient "github.com/tenderly/coreth/sync/client" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/sync/statesync/state_syncer_progress.go b/sync/statesync/state_syncer_progress.go index 2fdda50a23..a65e04c770 100644 --- a/sync/statesync/state_syncer_progress.go +++ b/sync/statesync/state_syncer_progress.go @@ -4,7 +4,7 @@ package statesync import ( - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" ) diff --git a/sync/statesync/sync_helpers.go b/sync/statesync/sync_helpers.go index 8cecdc1df6..77841afe25 100644 --- a/sync/statesync/sync_helpers.go +++ b/sync/statesync/sync_helpers.go @@ -6,12 +6,12 @@ package statesync import ( "fmt" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/trie" - "github.com/ava-labs/coreth/utils" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/trie" + "github.com/tenderly/coreth/utils" "github.com/ethereum/go-ethereum/common" ) diff --git a/sync/statesync/sync_test.go b/sync/statesync/sync_test.go index e2f9ef70c6..87a8f9d6d7 100644 --- a/sync/statesync/sync_test.go +++ b/sync/statesync/sync_test.go @@ -10,16 +10,16 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/ethdb/memorydb" - "github.com/ava-labs/coreth/plugin/evm/message" - statesyncclient "github.com/ava-labs/coreth/sync/client" - "github.com/ava-labs/coreth/sync/handlers" - handlerstats "github.com/ava-labs/coreth/sync/handlers/stats" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/plugin/evm/message" + statesyncclient "github.com/tenderly/coreth/sync/client" + "github.com/tenderly/coreth/sync/handlers" + handlerstats "github.com/tenderly/coreth/sync/handlers/stats" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" diff --git a/sync/statesync/test_sync.go b/sync/statesync/test_sync.go index 425507a8cc..0c9523efbe 100644 --- a/sync/statesync/test_sync.go +++ b/sync/statesync/test_sync.go @@ -8,11 +8,11 @@ import ( "math/rand" "testing" - "github.com/ava-labs/coreth/accounts/keystore" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/trie" + "github.com/tenderly/coreth/accounts/keystore" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" diff --git a/tests/init.go b/tests/init.go index 56e2157df5..8d64f787d1 100644 --- a/tests/init.go +++ b/tests/init.go @@ -31,7 +31,7 @@ import ( "math/big" "sort" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" ) // Forks table defines supported forks and their chain config. diff --git a/tests/init_test.go b/tests/init_test.go index 1ddbfcc6ba..35725f4f6e 100644 --- a/tests/init_test.go +++ b/tests/init_test.go @@ -40,7 +40,7 @@ import ( "strings" "testing" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/params" ) func readJSON(reader io.Reader, value interface{}) error { diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 6e33efa56d..dc66c0a872 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -34,13 +34,13 @@ import ( "strconv" "strings" - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/params" + "github.com/tenderly/coreth/core" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/core/state/snapshot" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" diff --git a/trie/database.go b/trie/database.go index 40c61ce913..8458a66976 100644 --- a/trie/database.go +++ b/trie/database.go @@ -35,9 +35,9 @@ import ( "time" "github.com/VictoriaMetrics/fastcache" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/metrics" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/trie/database_test.go b/trie/database_test.go index 3a943a8a7c..c14fd87642 100644 --- a/trie/database_test.go +++ b/trie/database_test.go @@ -29,7 +29,7 @@ package trie import ( "testing" - "github.com/ava-labs/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" ) diff --git a/trie/iterator.go b/trie/iterator.go index a66f310d57..44af99be06 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -31,7 +31,7 @@ import ( "container/heap" "errors" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" ) diff --git a/trie/iterator_test.go b/trie/iterator_test.go index 1141dd8cda..75827bb891 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -33,8 +33,8 @@ import ( "math/rand" "testing" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/trie/proof.go b/trie/proof.go index 2a55e076d6..4d92131eb5 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -31,8 +31,8 @@ import ( "errors" "fmt" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/trie/proof_test.go b/trie/proof_test.go index 47afd4333e..ae99be94c6 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -35,7 +35,7 @@ import ( "testing" "time" - "github.com/ava-labs/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 5017bebca8..73e590d4fb 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -29,7 +29,7 @@ package trie import ( "fmt" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index c9c29d7065..e81cf10467 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -32,7 +32,7 @@ import ( "sync" "testing" - "github.com/ava-labs/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/trie/stacktrie.go b/trie/stacktrie.go index f06493937e..f2ffcf7940 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -35,7 +35,7 @@ import ( "io" "sync" - "github.com/ava-labs/coreth/ethdb" + "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go index 7a8841c090..1d68a40688 100644 --- a/trie/stacktrie_test.go +++ b/trie/stacktrie_test.go @@ -31,7 +31,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) diff --git a/trie/sync_test.go b/trie/sync_test.go index 22731255f6..79bc1f3ae9 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -27,7 +27,7 @@ package trie import ( - "github.com/ava-labs/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" ) diff --git a/trie/test_trie.go b/trie/test_trie.go index d464ccc790..35952cb4b0 100644 --- a/trie/test_trie.go +++ b/trie/test_trie.go @@ -11,8 +11,8 @@ import ( "testing" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/coreth/accounts/keystore" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/accounts/keystore" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" diff --git a/trie/trie.go b/trie/trie.go index ed42f31264..9ddf758b0c 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -33,7 +33,7 @@ import ( "fmt" "sync" - "github.com/ava-labs/coreth/core/types" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/trie/trie_test.go b/trie/trie_test.go index 8f2447ffd8..10aa01bd8e 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -38,9 +38,9 @@ import ( "testing" "testing/quick" - "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/ethdb" - "github.com/ava-labs/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/memorydb" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" From 058aa7179fd36181ee57813b3058d5dac6740756 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Wed, 5 Oct 2022 23:51:37 +0200 Subject: [PATCH 4/9] Banff support --- accounts/abi/bind/base_test.go | 10 +- chain/chain_test.go | 8 +- chain/counter_test.go | 4 +- chain/multicoin_test.go | 10 +- core/README.md | 31 + core/bench_test.go | 27 +- core/blockchain.go | 159 ++- core/blockchain_reader.go | 19 +- core/blockchain_repair_test.go | 10 +- core/blockchain_snapshot_test.go | 66 +- core/blockchain_test.go | 119 +- core/bloom_indexer.go | 6 +- core/bloombits/matcher_test.go | 4 +- core/chain_indexer.go | 6 +- core/chain_indexer_test.go | 2 +- core/chain_makers.go | 4 +- core/chain_makers_test.go | 4 +- core/dao_test.go | 2 +- core/events.go | 2 +- core/evm.go | 9 +- core/genesis.go | 49 +- core/genesis_test.go | 76 +- core/headerchain.go | 4 +- core/headerchain_test.go | 4 +- core/mkalloc.go | 2 +- core/rawdb/accessors_chain.go | 12 +- core/rawdb/accessors_chain_test.go | 10 +- core/rawdb/accessors_indexes.go | 6 +- core/rawdb/accessors_indexes_test.go | 4 +- core/rawdb/accessors_metadata.go | 6 +- core/rawdb/accessors_snapshot.go | 6 +- core/rawdb/accessors_state.go | 83 +- core/rawdb/accessors_state_sync.go | 155 +++ core/rawdb/database.go | 47 +- core/rawdb/key_length_iterator.go | 57 + core/rawdb/key_length_iterator_test.go | 60 + core/rawdb/schema.go | 13 +- core/rawdb/table.go | 5 + core/rlp_test.go | 8 +- core/state/database.go | 48 +- core/state/dump.go | 6 +- core/state/iterator.go | 4 +- core/state/metrics.go | 12 +- core/state/pruner/bloom.go | 8 +- core/state/pruner/pruner.go | 14 +- core/state/snapshot/account.go | 2 +- core/state/snapshot/conversion.go | 14 +- core/state/snapshot/difflayer.go | 6 +- core/state/snapshot/difflayer_test.go | 3 +- core/state/snapshot/disklayer.go | 4 +- core/state/snapshot/disklayer_test.go | 8 +- core/state/snapshot/generate.go | 12 +- core/state/snapshot/generate_test.go | 606 ++++++----- core/state/snapshot/iterator.go | 2 +- core/state/snapshot/iterator_fast.go | 4 +- core/state/snapshot/iterator_test.go | 2 +- core/state/snapshot/journal.go | 6 +- core/state/snapshot/snapshot.go | 55 +- core/state/snapshot/snapshot_test.go | 8 +- core/state/snapshot/utils.go | 81 ++ core/state/snapshot/wipe.go | 4 +- core/state/snapshot/wipe_test.go | 2 +- core/state/state_object.go | 25 +- core/state/state_test.go | 2 +- core/state/statedb.go | 119 +- core/state/statedb_test.go | 77 +- core/state/trie_prefetcher.go | 101 +- core/state/trie_prefetcher_test.go | 38 +- core/state_manager.go | 8 +- core/state_manager_test.go | 12 - core/state_processor.go | 10 +- core/state_processor_test.go | 25 +- core/state_transition.go | 29 +- core/stateful_precompile_test.go | 43 + core/test_blockchain.go | 6 +- core/tx_journal.go | 13 +- core/tx_list.go | 2 +- core/tx_list_test.go | 2 +- core/tx_noncer.go | 2 +- core/tx_pool.go | 26 +- core/tx_pool_test.go | 10 +- core/types/access_list_tx.go | 4 +- core/types/block.go | 10 +- core/types/block_test.go | 4 +- core/types/bloom9.go | 2 +- core/types/bloom9_test.go | 1 - core/types/gen_account_rlp.go | 31 + core/types/gen_header_json.go | 6 + core/types/gen_header_rlp.go | 84 ++ core/types/gen_log_rlp.go | 26 + core/types/hashing.go | 6 +- core/types/hashing_test.go | 17 +- core/types/legacy_tx.go | 2 +- core/types/log.go | 25 +- core/types/receipt.go | 56 +- core/types/receipt_test.go | 6 +- core/types/state_account.go | 2 + core/types/transaction.go | 30 +- core/types/transaction_signing.go | 2 +- core/types/transaction_signing_test.go | 3 +- core/types/transaction_test.go | 13 +- core/types/types_test.go | 2 +- core/vm/analysis.go | 2 +- core/vm/contracts.go | 31 +- core/vm/contracts_stateful.go | 4 +- core/vm/contracts_stateful_test.go | 27 +- core/vm/contracts_test.go | 6 +- core/vm/eips.go | 20 +- core/vm/evm.go | 13 +- core/vm/evm_test.go | 24 + core/vm/gas.go | 2 +- core/vm/gas_table.go | 4 +- core/vm/gas_table_test.go | 11 +- core/vm/instructions.go | 18 +- core/vm/instructions_test.go | 74 +- core/vm/interface.go | 2 +- core/vm/interpreter.go | 12 +- core/vm/interpreter_test.go | 5 +- core/vm/jump_table.go | 3 +- core/vm/logger.go | 10 +- core/vm/memory.go | 24 +- core/vm/opcodes.go | 8 +- core/vm/operations_acl.go | 4 +- core/vm/runtime/runtime.go | 4 +- core/vm/runtime/runtime_example_test.go | 2 +- core/vm/runtime/runtime_test.go | 36 +- core/vm/stack.go | 14 - ethdb/batch.go | 3 + ethdb/dbtest/testsuite.go | 1 - ethdb/leveldb/leveldb.go | 12 +- ethdb/leveldb/leveldb_test.go | 4 +- ethdb/memorydb/memorydb.go | 47 +- params/avalanche_params.go | 5 + params/config.go | 292 ++--- params/config_test.go | 82 +- params/version.go | 10 +- plugin/evm/block.go | 2 +- plugin/evm/gossiper.go | 2 +- plugin/evm/gossiper_eth_gossiping_test.go | 2 +- plugin/evm/syncervm_test.go | 6 +- plugin/evm/vm.go | 4 +- plugin/evm/vm_test.go | 4 +- rlp/decode.go | 1120 +++++++++++++++++++ rlp/decode_tail_test.go | 49 + rlp/decode_test.go | 1210 +++++++++++++++++++++ rlp/doc.go | 161 +++ rlp/encbuffer.go | 398 +++++++ rlp/encbuffer_example_test.go | 45 + rlp/encode.go | 471 ++++++++ rlp/encode_test.go | 585 ++++++++++ rlp/encoder_example_test.go | 48 + rlp/internal/rlpstruct/rlpstruct.go | 213 ++++ rlp/iterator.go | 60 + rlp/iterator_test.go | 59 + rlp/raw.go | 261 +++++ rlp/raw_test.go | 285 +++++ rlp/rlpgen/gen.go | 751 +++++++++++++ rlp/rlpgen/gen_test.go | 107 ++ rlp/rlpgen/main.go | 147 +++ rlp/rlpgen/testdata/bigint.in.txt | 10 + rlp/rlpgen/testdata/bigint.out.txt | 49 + rlp/rlpgen/testdata/nil.in.txt | 30 + rlp/rlpgen/testdata/nil.out.txt | 289 +++++ rlp/rlpgen/testdata/optional.in.txt | 17 + rlp/rlpgen/testdata/optional.out.txt | 153 +++ rlp/rlpgen/testdata/rawvalue.in.txt | 11 + rlp/rlpgen/testdata/rawvalue.out.txt | 64 ++ rlp/rlpgen/testdata/uints.in.txt | 10 + rlp/rlpgen/testdata/uints.out.txt | 53 + rlp/rlpgen/types.go | 114 ++ rlp/safe.go | 27 + rlp/typecache.go | 240 ++++ rlp/unsafe.go | 35 + scripts/build.sh | 2 +- sync/client/mock_client.go | 4 +- sync/handlers/block_request_test.go | 6 +- sync/handlers/leafs_request_test.go | 8 +- sync/statesync/state_syncer.go | 6 +- sync/statesync/sync_test.go | 8 +- sync/statesync/test_sync.go | 8 +- tests/rlp_test_util.go | 2 +- trie/committer.go | 184 ++-- trie/database.go | 297 +++-- trie/database_test.go | 2 +- trie/errors.go | 13 +- trie/hasher.go | 64 +- trie/iterator.go | 42 +- trie/iterator_test.go | 129 ++- trie/node.go | 48 +- trie/node_enc.go | 97 ++ trie/node_test.go | 125 ++- trie/nodeset.go | 104 ++ trie/preimages.go | 107 ++ trie/proof.go | 40 +- trie/proof_test.go | 55 +- trie/secure_trie.go | 137 ++- trie/secure_trie_test.go | 36 +- trie/stacktrie.go | 246 +++-- trie/stacktrie_test.go | 17 +- trie/sync_test.go | 20 +- trie/test_trie.go | 24 +- trie/trie.go | 183 ++-- trie/trie_test.go | 265 +++-- trie/util_test.go | 134 +++ trie/utils.go | 177 +++ 205 files changed, 11245 insertions(+), 2155 deletions(-) create mode 100644 core/README.md create mode 100644 core/rawdb/accessors_state_sync.go create mode 100644 core/rawdb/key_length_iterator.go create mode 100644 core/rawdb/key_length_iterator_test.go create mode 100644 core/state/snapshot/utils.go create mode 100644 core/stateful_precompile_test.go create mode 100644 core/types/gen_account_rlp.go create mode 100644 core/types/gen_header_rlp.go create mode 100644 core/types/gen_log_rlp.go create mode 100644 core/vm/evm_test.go create mode 100644 rlp/decode.go create mode 100644 rlp/decode_tail_test.go create mode 100644 rlp/decode_test.go create mode 100644 rlp/doc.go create mode 100644 rlp/encbuffer.go create mode 100644 rlp/encbuffer_example_test.go create mode 100644 rlp/encode.go create mode 100644 rlp/encode_test.go create mode 100644 rlp/encoder_example_test.go create mode 100644 rlp/internal/rlpstruct/rlpstruct.go create mode 100644 rlp/iterator.go create mode 100644 rlp/iterator_test.go create mode 100644 rlp/raw.go create mode 100644 rlp/raw_test.go create mode 100644 rlp/rlpgen/gen.go create mode 100644 rlp/rlpgen/gen_test.go create mode 100644 rlp/rlpgen/main.go create mode 100644 rlp/rlpgen/testdata/bigint.in.txt create mode 100644 rlp/rlpgen/testdata/bigint.out.txt create mode 100644 rlp/rlpgen/testdata/nil.in.txt create mode 100644 rlp/rlpgen/testdata/nil.out.txt create mode 100644 rlp/rlpgen/testdata/optional.in.txt create mode 100644 rlp/rlpgen/testdata/optional.out.txt create mode 100644 rlp/rlpgen/testdata/rawvalue.in.txt create mode 100644 rlp/rlpgen/testdata/rawvalue.out.txt create mode 100644 rlp/rlpgen/testdata/uints.in.txt create mode 100644 rlp/rlpgen/testdata/uints.out.txt create mode 100644 rlp/rlpgen/types.go create mode 100644 rlp/safe.go create mode 100644 rlp/typecache.go create mode 100644 rlp/unsafe.go create mode 100644 trie/node_enc.go create mode 100644 trie/nodeset.go create mode 100644 trie/preimages.go create mode 100644 trie/util_test.go create mode 100644 trie/utils.go diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go index 84216f0e46..8f39b98559 100644 --- a/accounts/abi/bind/base_test.go +++ b/accounts/abi/bind/base_test.go @@ -34,16 +34,16 @@ import ( "strings" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" "github.com/tenderly/coreth/accounts/abi" "github.com/tenderly/coreth/accounts/abi/bind" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/interfaces" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" - "github.com/stretchr/testify/assert" + "github.com/tenderly/coreth/rlp" ) func mockSign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { return tx, nil } diff --git a/chain/chain_test.go b/chain/chain_test.go index 459a93b73b..a6a2db25ae 100644 --- a/chain/chain_test.go +++ b/chain/chain_test.go @@ -10,6 +10,9 @@ import ( "testing" "github.com/ava-labs/avalanchego/utils/timer/mockable" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/accounts/keystore" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core" @@ -20,10 +23,7 @@ import ( "github.com/tenderly/coreth/eth/ethconfig" "github.com/tenderly/coreth/node" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) type testChain struct { diff --git a/chain/counter_test.go b/chain/counter_test.go index 2178dbe137..10e9cbc19b 100644 --- a/chain/counter_test.go +++ b/chain/counter_test.go @@ -15,8 +15,8 @@ import ( "testing" - "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/log" ) @@ -30,7 +30,7 @@ func TestCounter(t *testing.T) { // NOTE: use precompiled `counter.sol` for portability, do not remove the // following code (for debug purpose) - //counterSrc, err := filepath.Abs(gopath + "/src/github.com/ava-labs/coreth/examples/counter/counter.sol") + //counterSrc, err := filepath.Abs(gopath + "/src/github.com/tenderly/coreth/examples/counter/counter.sol") // if err != nil { // t.Fatal(err) // } diff --git a/chain/multicoin_test.go b/chain/multicoin_test.go index 7dd53235d8..0a1fdae26f 100644 --- a/chain/multicoin_test.go +++ b/chain/multicoin_test.go @@ -29,6 +29,10 @@ import ( "testing" "github.com/ava-labs/avalanchego/utils/timer/mockable" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/accounts/keystore" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core" @@ -38,10 +42,6 @@ import ( "github.com/tenderly/coreth/eth" "github.com/tenderly/coreth/eth/ethconfig" "github.com/tenderly/coreth/node" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" ) // TestMulticoin tests multicoin low-level state management and regular @@ -73,7 +73,7 @@ func TestMulticoin(t *testing.T) { //if gopath == "" { // gopath = build.Default.GOPATH //} - //counterSrc, err := filepath.Abs(gopath + "/src/github.com/ava-labs/coreth/examples/multicoin/mc_test.sol") + //counterSrc, err := filepath.Abs(gopath + "/src/github.com/tenderly/coreth/examples/multicoin/mc_test.sol") //if err != nil { // t.Fatal(err) // } diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000000..5f623ff79e --- /dev/null +++ b/core/README.md @@ -0,0 +1,31 @@ +# Core Package + +The core package maintains the backend for the blockchain, transaction pool, and maintains the required indexes for blocks, transactions, logs, and transaction receipts. + +## Blockchain + +The [BlockChain](./blockchain.go) struct handles the insertion of blocks into the maintained chain. It maintains a "canonical chain", which is essentially the preferred chain (the chain that ends with the block preferred by the AvalancheGo consensus engine). + +When the consensus engine verifies blocks as they are ready to be issued into consensus, it calls `Verify()` on the ChainVM Block interface implemented [here](../plugin/evm/block.go). This calls `InsertBlockManual` on the BlockChain struct implemented in this package, which is the first entrypoint of a block into the blockchain. + +InsertBlockManual verifies the block, inserts it into the state manager to track the merkle trie for the block, and adds it to the canonical chain if it extends the currently preferred chain. + +Coreth adds functions for Accept and Reject, which take care of marking a block as finalized and performing garbage collection where possible. + +The consensus engine can also call `SetPreference` on a VM to tell the VM that a specific block is preferred by the consensus engine to be accepted. This triggers a call to `reorg` the blockchain and set the newly preferred block as the preferred chain. + +## Transaction Pool + +The transaction pool maintains the set of transactions that need to be issued into a new block. The VM exposes APIs that allow clients to issue transactions into the transaction pool and also performs gossip across the network in order to send and receive pending transactions that need to be issued into a new block. The transaction pool asynchronously follows the preferred block of the `BlockChain` struct by subscribing to new head events and updating its state accordingly. When the transaction pool updates, it ensures that any transactions it contains are still valid to be issued on top of the new preferred block. + +## State Manager + +The State Manager manages the [TrieDB](../trie/database.go). The TrieDB tracks a merkle forest of all of the merkle tries for the last accepted block and processing blocks. When a block is processed, the state transition results in a new merkle trie added to the merkle forest. The State Manager can operate in either archival or pruning mode. + +### Archival Mode + +In archival mode, every merkle trie is written to disk so that the node maintains a complete history of all the blocks that it has processed. + +### Pruning Mode + +In pruning mode, the State Manager keeps a reference to merkle tries of processing blocks. When a block gets accepted, it stays in memory. When a block gets rejected, the state manager can dereference and clean up the no longer needed merkle trie. The State Manager does not immediately write the merkle trie to disk of a block when it gets accepted. Instead, at a regular interval (~4096 blocks) it writes the merkle trie to disk, so that it does not add the overhead of storing every accepted block's merkle trie to disk. diff --git a/core/bench_test.go b/core/bench_test.go index f87e639663..4706e199dc 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -28,20 +28,18 @@ package core import ( "crypto/ecdsa" - "io/ioutil" "math/big" - "os" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/crypto" ) func BenchmarkInsertChain_empty_memdb(b *testing.B) { @@ -144,14 +142,11 @@ func genTxRing(naccounts int) func(int, *BlockGen) { func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { // Create the database in memory or in a temporary directory. var db ethdb.Database + var err error if !disk { db = rawdb.NewMemoryDatabase() } else { - dir, err := ioutil.TempDir("", "eth-core-bench") - if err != nil { - b.Fatalf("cannot create temporary directory: %v", err) - } - defer os.RemoveAll(dir) + dir := b.TempDir() db, err = rawdb.NewLevelDBDatabase(dir, 128, 128, "", false) if err != nil { b.Fatalf("cannot create temporary database: %v", err) @@ -245,26 +240,18 @@ func makeChainForBench(db ethdb.Database, full bool, count uint64) { func benchWriteChain(b *testing.B, full bool, count uint64) { for i := 0; i < b.N; i++ { - dir, err := ioutil.TempDir("", "eth-chain-bench") - if err != nil { - b.Fatalf("cannot create temporary directory: %v", err) - } + dir := b.TempDir() db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } makeChainForBench(db, full, count) db.Close() - os.RemoveAll(dir) } } func benchReadChain(b *testing.B, full bool, count uint64) { - dir, err := ioutil.TempDir("", "eth-chain-bench") - if err != nil { - b.Fatalf("cannot create temporary directory: %v", err) - } - defer os.RemoveAll(dir) + dir := b.TempDir() db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) if err != nil { diff --git a/core/blockchain.go b/core/blockchain.go index dbcd51bd7b..89bd53815e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -38,6 +38,10 @@ import ( "sync/atomic" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + lru "github.com/hashicorp/golang-lru" "github.com/tenderly/coreth/consensus" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state" @@ -48,14 +52,13 @@ import ( "github.com/tenderly/coreth/metrics" "github.com/tenderly/coreth/params" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/log" - lru "github.com/hashicorp/golang-lru" ) var ( - acceptorQueueGauge = metrics.NewRegisteredGauge("blockchain/acceptor/queue/size", nil) + acceptorQueueGauge = metrics.NewRegisteredGauge("blockchain/acceptor/queue/size", nil) + processedBlockGasUsedCounter = metrics.NewRegisteredCounter("blockchain/blocks/gas/used/processed", nil) + acceptedBlockGasUsedCounter = metrics.NewRegisteredCounter("blockchain/blocks/gas/used/accepted", nil) + badBlockCounter = metrics.NewRegisteredCounter("blockchain/blocks/bad/count", nil) ErrRefuseToCorruptArchiver = errors.New("node has operated with pruning disabled, shutting down to prevent missing tries") @@ -533,21 +536,33 @@ func (bc *BlockChain) Export(w io.Writer) error { // ExportN writes a subset of the active chain to the given writer. func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error { - bc.chainmu.RLock() - defer bc.chainmu.RUnlock() + return bc.ExportCallback(func(block *types.Block) error { + return block.EncodeRLP(w) + }, first, last) +} +// ExportCallback invokes [callback] for every block from [first] to [last] in order. +func (bc *BlockChain) ExportCallback(callback func(block *types.Block) error, first uint64, last uint64) error { if first > last { return fmt.Errorf("export failed: first (%d) is greater than last (%d)", first, last) } log.Info("Exporting batch of blocks", "count", last-first+1) - start, reported := time.Now(), time.Now() + var ( + parentHash common.Hash + start = time.Now() + reported = time.Now() + ) for nr := first; nr <= last; nr++ { block := bc.GetBlockByNumber(nr) if block == nil { return fmt.Errorf("export failed on #%d: not found", nr) } - if err := block.EncodeRLP(w); err != nil { + if nr > first && block.ParentHash() != parentHash { + return fmt.Errorf("export failed: chain reorg during export") + } + parentHash = block.Hash() + if err := callback(block); err != nil { return err } if time.Since(reported) >= statsReportLimit { @@ -696,6 +711,10 @@ func (bc *BlockChain) Stop() { log.Error("Failed to Shutdown state manager", "err", err) } log.Info("State manager shut down", "t", time.Since(start)) + // Flush the collected preimages to disk + if err := bc.stateCache.TrieDB().CommitPreimages(); err != nil { + log.Error("Failed to commit trie preimages", "err", err) + } // Stop senderCacher's goroutines log.Info("Shutting down sender cacher") @@ -804,6 +823,8 @@ func (bc *BlockChain) Accept(block *types.Block) error { bc.lastAccepted = block bc.addAcceptorQueue(block) + acceptedBlockGasUsedCounter.Inc(int64(block.GasUsed())) + return nil } @@ -868,7 +889,7 @@ func (bc *BlockChain) newTip(block *types.Block) bool { // writeBlockAndSetHead expects to be the last verification step during InsertBlock // since it creates a reference that will only be cleaned up by Accept/Reject. func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB) error { - if err := bc.writeBlockWithState(block, receipts, logs, state); err != nil { + if err := bc.writeBlockWithState(block, receipts, state); err != nil { return err } @@ -885,7 +906,7 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types // writeBlockWithState writes the block and all associated state to the database, // but it expects the chain mutex to be held. -func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB) error { +func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) error { // Irrelevant of the canonical status, write the block itself to the database. // // Note all the components of block(hash->number map, header, body, receipts) @@ -897,14 +918,15 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. if err := blockBatch.Write(); err != nil { log.Crit("Failed to write block into disk", "err", err) } + // Commit all cached state changes into underlying memory database. // If snapshots are enabled, call CommitWithSnaps to explicitly create a snapshot // diff layer for the block. var err error if bc.snaps == nil { - _, err = state.Commit(bc.chainConfig.IsEIP158(block.Number())) + _, err = state.Commit(bc.chainConfig.IsEIP158(block.Number()), true) } else { - _, err = state.CommitWithSnap(bc.chainConfig.IsEIP158(block.Number()), bc.snaps, block.Hash(), block.ParentHash()) + _, err = state.CommitWithSnap(bc.chainConfig.IsEIP158(block.Number()), bc.snaps, block.Hash(), block.ParentHash(), true) } if err != nil { return err @@ -924,7 +946,6 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. } return err } - return nil } @@ -1066,6 +1087,9 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { // transactions and probabilistically some of the account/storage trie nodes. // Process block using the parent state as reference point receipts, logs, usedGas, err := bc.processor.Process(block, parent, statedb, bc.vmConfig) + if serr := statedb.Error(); serr != nil { + log.Error("statedb error encountered", "err", serr, "number", block.Number(), "hash", block.Hash()) + } if err != nil { bc.reportBlock(block, receipts, err) return err @@ -1098,6 +1122,7 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { "root", block.Root(), "baseFeePerGas", block.BaseFee(), "blockGasCost", block.BlockGasCost(), ) + processedBlockGasUsedCounter.Inc(int64(block.GasUsed())) return nil } @@ -1243,6 +1268,7 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { if err := indexesBatch.Write(); err != nil { log.Crit("Failed to delete useless indexes", "err", err) } + // If any logs need to be fired, do it now. In theory we could avoid creating // this goroutine if there are no events to fire, but realistcally that only // ever happens if we're reorging empty blocks, which will only happen on idle @@ -1261,44 +1287,78 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { return nil } -// BadBlocks returns a list of the last 'bad blocks' that the client has seen on the network -func (bc *BlockChain) BadBlocks() []*types.Block { +type badBlock struct { + block *types.Block + reason *BadBlockReason +} + +type BadBlockReason struct { + ChainConfig *params.ChainConfig `json:"chainConfig"` + Receipts types.Receipts `json:"receipts"` + Number uint64 `json:"number"` + Hash common.Hash `json:"hash"` + Error error `json:"error"` +} + +func (b *BadBlockReason) String() string { + var receiptString string + for i, receipt := range b.Receipts { + receiptString += fmt.Sprintf("\t %d: cumulative: %v gas: %v contract: %v status: %v tx: %v logs: %v bloom: %x state: %x\n", + i, receipt.CumulativeGasUsed, receipt.GasUsed, receipt.ContractAddress.Hex(), + receipt.Status, receipt.TxHash.Hex(), receipt.Logs, receipt.Bloom, receipt.PostState) + } + reason := fmt.Sprintf(` + ########## BAD BLOCK ######### + Chain config: %v + + Number: %v + Hash: %#x + %v + + Error: %v + ############################## + `, b.ChainConfig, b.Number, b.Hash, receiptString, b.Error) + + return reason +} + +// BadBlocks returns a list of the last 'bad blocks' that the client has seen on the network and the BadBlockReason +// that caused each to be reported as a bad block. +// BadBlocks ensures that the length of the blocks and the BadBlockReason slice have the same length. +func (bc *BlockChain) BadBlocks() ([]*types.Block, []*BadBlockReason) { blocks := make([]*types.Block, 0, bc.badBlocks.Len()) + reasons := make([]*BadBlockReason, 0, bc.badBlocks.Len()) for _, hash := range bc.badBlocks.Keys() { if blk, exist := bc.badBlocks.Peek(hash); exist { - block := blk.(*types.Block) - blocks = append(blocks, block) + badBlk := blk.(*badBlock) + blocks = append(blocks, badBlk.block) + reasons = append(reasons, badBlk.reason) } } - return blocks + return blocks, reasons } // addBadBlock adds a bad block to the bad-block LRU cache -func (bc *BlockChain) addBadBlock(block *types.Block) { - bc.badBlocks.Add(block.Hash(), block) +func (bc *BlockChain) addBadBlock(block *types.Block, reason *BadBlockReason) { + bc.badBlocks.Add(block.Hash(), &badBlock{ + block: block, + reason: reason, + }) } // reportBlock logs a bad block error. func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error) { - bc.addBadBlock(block) - - var receiptString string - for i, receipt := range receipts { - receiptString += fmt.Sprintf("\t %d: cumulative: %v gas: %v contract: %v status: %v tx: %v logs: %v bloom: %x state: %x\n", - i, receipt.CumulativeGasUsed, receipt.GasUsed, receipt.ContractAddress.Hex(), - receipt.Status, receipt.TxHash.Hex(), receipt.Logs, receipt.Bloom, receipt.PostState) + reason := &BadBlockReason{ + ChainConfig: bc.chainConfig, + Receipts: receipts, + Number: block.NumberU64(), + Hash: block.Hash(), + Error: err, } - log.Error(fmt.Sprintf(` -########## BAD BLOCK ######### -Chain config: %v -Number: %v -Hash: 0x%x -%v - -Error: %v -############################## -`, bc.chainConfig, block.Number(), block.Hash(), receiptString, err)) + badBlockCounter.Inc(1) + bc.addBadBlock(block, reason) + log.Debug(reason.String()) } func (bc *BlockChain) RemoveRejectedBlocks(start, end uint64) error { @@ -1373,9 +1433,9 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block) // If snapshots are enabled, call CommitWithSnaps to explicitly create a snapshot // diff layer for the block. if bc.snaps == nil { - return statedb.Commit(bc.chainConfig.IsEIP158(current.Number())) + return statedb.Commit(bc.chainConfig.IsEIP158(current.Number()), false) } - return statedb.CommitWithSnap(bc.chainConfig.IsEIP158(current.Number()), bc.snaps, current.Hash(), current.ParentHash()) + return statedb.CommitWithSnap(bc.chainConfig.IsEIP158(current.Number()), bc.snaps, current.Hash(), current.ParentHash(), false) } // initSnapshot instantiates a Snapshot instance and adds it to [bc] @@ -1668,15 +1728,16 @@ func (bc *BlockChain) CleanBlockRootsAboveLastAccepted() error { // consensus engine will reject the lowest ancestor first. In this case, these blocks will not be considered acceptable in // the future. // Ex. -// A -// / \ -// B C -// | -// D -// | -// E -// | -// F +// +// A +// / \ +// B C +// | +// D +// | +// E +// | +// F // // The consensus engine accepts block C and proceeds to reject the other branch in order (B, D, E, F). // If the consensus engine dies after rejecting block D, block D will be deleted, such that the forward iteration diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index 032cb87290..1159239306 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -27,6 +27,8 @@ package core import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/event" "github.com/tenderly/coreth/consensus" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state" @@ -34,8 +36,6 @@ import ( "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/event" ) // CurrentHeader retrieves the current head header of the canonical chain. The @@ -100,6 +100,9 @@ func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool { if bc.blockCache.Contains(hash) { return true } + if !bc.HasHeader(hash, number) { + return false + } return rawdb.HasBody(bc.db, hash, number) } @@ -235,18 +238,6 @@ func (bc *BlockChain) ContractCode(hash common.Hash) ([]byte, error) { return bc.stateCache.ContractCode(common.Hash{}, hash) } -// ContractCodeWithPrefix retrieves a blob of data associated with a contract -// hash either from ephemeral in-memory cache, or from persistent storage. -// -// If the code doesn't exist in the in-memory cache, check the storage with -// new code scheme. -func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) ([]byte, error) { - type codeReader interface { - ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error) - } - return bc.stateCache.(codeReader).ContractCodeWithPrefix(common.Hash{}, hash) -} - // State returns a new mutable state based on the current HEAD block. func (bc *BlockChain) State() (*state.StateDB, error) { return bc.StateAt(bc.CurrentBlock().Root()) diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index d48d6f6c62..6598a51069 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -31,17 +31,15 @@ package core import ( - "io/ioutil" "math/big" - "os" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" ) // rewindTest is a test case for chain rollback upon user request. @@ -508,11 +506,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { // fmt.Println(tt.dump(true)) // Create a temporary persistent database - datadir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Failed to create temporary datadir: %v", err) - } - os.RemoveAll(datadir) + datadir := t.TempDir() db, err := rawdb.NewLevelDBDatabase(datadir, 0, 0, "", false) if err != nil { diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index f080461211..806a3c6b95 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -32,12 +32,12 @@ package core import ( "bytes" "fmt" - "io/ioutil" "math/big" "os" "strings" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/consensus" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core/rawdb" @@ -45,7 +45,6 @@ import ( "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" ) // snapshotTestBasic wraps the common testing fields in the snapshot tests. @@ -68,11 +67,7 @@ type snapshotTestBasic struct { func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Block) { // Create a temporary persistent database - datadir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Failed to create temporary datadir: %v", err) - } - os.RemoveAll(datadir) + datadir := t.TempDir() db, err := rawdb.NewLevelDBDatabase(datadir, 0, 0, "", false) if err != nil { @@ -164,6 +159,7 @@ func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks [ } } +//nolint:unused func (basic *snapshotTestBasic) dump() string { buffer := new(strings.Builder) @@ -321,60 +317,6 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { snaptest.verify(t, newchain, blocks) } -// restartCrashSnapshotTest is the test type used to test this scenario: -// - have a complete snapshot -// - restart chain -// - insert more blocks with enabling the snapshot -// - commit the snapshot -// - crash -// - restart again -type restartCrashSnapshotTest struct { - snapshotTestBasic - newBlocks int -} - -func (snaptest *restartCrashSnapshotTest) test(t *testing.T) { - // It's hard to follow the test case, visualize the input - // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) - // fmt.Println(tt.dump()) - chain, blocks := snaptest.prepare(t) - - // Firstly, stop the chain properly, with all snapshot journal - // and state committed. - chain.Stop() - - newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfig, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - newBlocks, _, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, 10, func(i int, b *BlockGen) {}) - newchain.InsertChain(newBlocks) - - // Commit the entire snapshot into the disk if requested. Note only - // (a) snapshot root and (b) snapshot generator will be committed, - // the diff journal is not. - for i := uint64(0); i < uint64(len(newBlocks)); i++ { - if err := newchain.Accept(newBlocks[i]); err != nil { - t.Fatalf("Failed to accept block %v: %v", i, err) - } - snaptest.lastAcceptedHash = newBlocks[i].Hash() - } - chain.DrainAcceptorQueue() - - // Simulate the blockchain crash - // Don't call chain.Stop here, so that no snapshot - // journal and latest state will be committed - - // Restart the chain after the crash - newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfig, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - defer newchain.Stop() - - snaptest.verify(t, newchain, blocks) -} - // wipeCrashSnapshotTest is the test type used to test this scenario: // - have a complete snapshot // - restart, insert more blocks without enabling the snapshot @@ -418,7 +360,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { Pruning: true, CommitInterval: 4096, } - newchain, err = NewBlockChain(snaptest.db, config, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash) + _, err = NewBlockChain(snaptest.db, config, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 327d9b4083..051e3564e4 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -8,6 +8,8 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state" @@ -16,8 +18,6 @@ import ( "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" ) var ( @@ -277,6 +277,7 @@ func TestBlockChainOfflinePruningUngracefulShutdown(t *testing.T) { } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { + t.Parallel() tt.testFunc(t, create) }) } @@ -530,3 +531,117 @@ func TestUngracefulAsyncShutdown(t *testing.T) { } } } + +// TestCanonicalHashMarker tests all the canonical hash markers are updated/deleted +// correctly in case reorg is called. +func TestCanonicalHashMarker(t *testing.T) { + var cases = []struct { + forkA int + forkB int + }{ + // ForkA: 10 blocks + // ForkB: 1 blocks + // + // reorged: + // markers [2, 10] should be deleted + // markers [1] should be updated + {10, 1}, + + // ForkA: 10 blocks + // ForkB: 2 blocks + // + // reorged: + // markers [3, 10] should be deleted + // markers [1, 2] should be updated + {10, 2}, + + // ForkA: 10 blocks + // ForkB: 10 blocks + // + // reorged: + // markers [1, 10] should be updated + {10, 10}, + + // ForkA: 10 blocks + // ForkB: 11 blocks + // + // reorged: + // markers [1, 11] should be updated + {10, 11}, + } + for _, c := range cases { + var ( + db = rawdb.NewMemoryDatabase() + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{}, + BaseFee: big.NewInt(params.ApricotPhase3InitialBaseFee), + } + genesis = gspec.MustCommit(db) + engine = dummy.NewFaker() + ) + forkA, _, err := GenerateChain(params.TestChainConfig, genesis, engine, db, c.forkA, 10, func(i int, gen *BlockGen) {}) + if err != nil { + t.Fatal(err) + } + forkB, _, err := GenerateChain(params.TestChainConfig, genesis, engine, db, c.forkB, 10, func(i int, gen *BlockGen) {}) + if err != nil { + t.Fatal(err) + } + + // Initialize test chain + diskdb := rawdb.NewMemoryDatabase() + gspec.MustCommit(diskdb) + chain, err := NewBlockChain(diskdb, DefaultCacheConfig, params.TestChainConfig, engine, vm.Config{}, common.Hash{}) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + // Insert forkA and forkB, the canonical should on forkA still + if n, err := chain.InsertChain(forkA); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + if n, err := chain.InsertChain(forkB); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + verify := func(head *types.Block) { + if chain.CurrentBlock().Hash() != head.Hash() { + t.Fatalf("Unexpected block hash, want %x, got %x", head.Hash(), chain.CurrentBlock().Hash()) + } + if chain.CurrentHeader().Hash() != head.Hash() { + t.Fatalf("Unexpected head header, want %x, got %x", head.Hash(), chain.CurrentHeader().Hash()) + } + if !chain.HasState(head.Root()) { + t.Fatalf("Lost block state %v %x", head.Number(), head.Hash()) + } + } + + // Switch canonical chain to forkB if necessary + if len(forkA) < len(forkB) { + verify(forkB[len(forkB)-1]) + } else { + verify(forkA[len(forkA)-1]) + if err := chain.SetPreference(forkB[len(forkB)-1]); err != nil { + t.Fatal(err) + } + verify(forkB[len(forkB)-1]) + } + + // Ensure all hash markers are updated correctly + for i := 0; i < len(forkB); i++ { + block := forkB[i] + hash := chain.GetCanonicalHash(block.NumberU64()) + if hash != block.Hash() { + t.Fatalf("Unexpected canonical hash %d", block.NumberU64()) + } + } + if c.forkA > c.forkB { + for i := uint64(c.forkB) + 1; i <= uint64(c.forkA); i++ { + hash := chain.GetCanonicalHash(i) + if hash != (common.Hash{}) { + t.Fatalf("Unexpected canonical hash %d", i) + } + } + } + } +} diff --git a/core/bloom_indexer.go b/core/bloom_indexer.go index d6bac3fa4d..d61fc42416 100644 --- a/core/bloom_indexer.go +++ b/core/bloom_indexer.go @@ -20,12 +20,12 @@ import ( "context" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/bitutil" "github.com/tenderly/coreth/core/bloombits" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/bitutil" ) const ( @@ -75,7 +75,7 @@ func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error // Commit implements core.ChainIndexerBackend, finalizing the bloom section and // writing it out into the database. func (b *BloomIndexer) Commit() error { - batch := b.db.NewBatch() + batch := b.db.NewBatchWithSize((int(b.size) / 8) * types.BloomBitLength) for i := 0; i < types.BloomBitLength; i++ { bits, err := b.gen.Bitset(uint(i)) if err != nil { diff --git a/core/bloombits/matcher_test.go b/core/bloombits/matcher_test.go index 09475571b5..ae08ecbace 100644 --- a/core/bloombits/matcher_test.go +++ b/core/bloombits/matcher_test.go @@ -134,13 +134,13 @@ func makeRandomIndexes(lengths []int, max int) [][]bloomIndexes { // testMatcherDiffBatches runs the given matches test in single-delivery and also // in batches delivery mode, verifying that all kinds of deliveries are handled -// correctly withn. +// correctly within. func testMatcherDiffBatches(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, intermittent bool, retrievals uint32) { singleton := testMatcher(t, filter, start, blocks, intermittent, retrievals, 1) batched := testMatcher(t, filter, start, blocks, intermittent, retrievals, 16) if singleton != batched { - t.Errorf("filter = %v blocks = %v intermittent = %v: request count mismatch, %v in signleton vs. %v in batched mode", filter, blocks, intermittent, singleton, batched) + t.Errorf("filter = %v blocks = %v intermittent = %v: request count mismatch, %v in singleton vs. %v in batched mode", filter, blocks, intermittent, singleton, batched) } } diff --git a/core/chain_indexer.go b/core/chain_indexer.go index 8f42923340..c67eb9adb1 100644 --- a/core/chain_indexer.go +++ b/core/chain_indexer.go @@ -34,12 +34,12 @@ import ( "sync/atomic" "time" - "github.com/tenderly/coreth/core/rawdb" - "github.com/tenderly/coreth/core/types" - "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/ethdb" ) // ChainIndexerBackend defines the methods needed to process chain segments in diff --git a/core/chain_indexer_test.go b/core/chain_indexer_test.go index 5c9305fe9b..8194cc0396 100644 --- a/core/chain_indexer_test.go +++ b/core/chain_indexer_test.go @@ -35,9 +35,9 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/types" - "github.com/ethereum/go-ethereum/common" ) // Runs multiple tests with randomized parameters. diff --git a/core/chain_makers.go b/core/chain_makers.go index 51fb40fd04..6294160d21 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -30,6 +30,7 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/consensus" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/consensus/misc" @@ -38,7 +39,6 @@ import ( "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" ) // BlockGen creates blocks for testing. @@ -250,7 +250,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } // Write state changes to db - root, err := statedb.Commit(config.IsEIP158(b.header.Number)) + root, err := statedb.Commit(config.IsEIP158(b.header.Number), false) if err != nil { panic(fmt.Sprintf("state write error: %v", err)) } diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 01a0ee5daa..2d41f1b3a0 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -30,13 +30,13 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" ) func ExampleGenerateChain() { diff --git a/core/dao_test.go b/core/dao_test.go index 49df2c7251..ea6230fe85 100644 --- a/core/dao_test.go +++ b/core/dao_test.go @@ -30,11 +30,11 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" ) // Tests that DAO-fork enabled clients can properly filter out fork-commencing diff --git a/core/events.go b/core/events.go index dc4c8ff6ba..759c785b44 100644 --- a/core/events.go +++ b/core/events.go @@ -27,8 +27,8 @@ package core import ( - "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/core/types" ) // NewTxsEvent is posted when a batch of transactions enter the transaction pool. diff --git a/core/evm.go b/core/evm.go index e809be0f20..7517f9bba1 100644 --- a/core/evm.go +++ b/core/evm.go @@ -29,10 +29,10 @@ package core import ( "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/consensus" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/core/vm" - "github.com/ethereum/go-ethereum/common" //"github.com/ethereum/go-ethereum/log" ) @@ -42,7 +42,7 @@ type ChainContext interface { // Engine retrieves the chain's consensus engine. Engine() consensus.Engine - // GetHeader returns the hash corresponding to their hash. + // GetHeader returns the header corresponding to the hash/number argument pair. GetHeader(common.Hash, uint64) *types.Header } @@ -92,6 +92,11 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash var cache []common.Hash return func(n uint64) common.Hash { + if ref.Number.Uint64() <= n { + // This situation can happen if we're doing tracing and using + // block overrides. + return common.Hash{} + } // If there's no hash cache yet, make one if len(cache) == 0 { cache = append(cache, ref.ParentHash) diff --git a/core/genesis.go b/core/genesis.go index 7c0a7eb19b..3d9f0141dc 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -34,20 +34,20 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/params" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/log" ) -//go:generate gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go -//go:generate gencodec -type GenesisAccount -field-override genesisAccountMarshaling -out gen_genesis_account.go +//go:generate go run github.com/fjl/gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go +//go:generate go run github.com/fjl/gencodec -type GenesisAccount -field-override genesisAccountMarshaling -out gen_genesis_account.go var errGenesisNoConfig = errors.New("genesis has no chain configuration") @@ -154,15 +154,15 @@ func (e *GenesisMismatchError) Error() string { // SetupGenesisBlock writes or updates the genesis block in db. // The block that will be used is: // -// genesis == nil genesis != nil -// +------------------------------------------ -// db has no genesis | main-net default | genesis -// db has genesis | from DB | genesis (if compatible) +// genesis == nil genesis != nil +// +------------------------------------------ +// db has no genesis | main-net default | genesis +// db has genesis | from DB | genesis (if compatible) // // The stored chain configuration will be updated if it is compatible (i.e. does not // specify a fork block below the local head block). In case of a conflict, the // error is a *params.ConfigCompatError and the new, unwritten config is returned. -func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, error) { +func SetupGenesisBlock(db ethdb.Database, genesis *Genesis, lastAcceptedHash common.Hash) (*params.ChainConfig, error) { if genesis == nil { return nil, ErrNoGenesis } @@ -209,12 +209,18 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig // Check config compatibility and write the config. Compatibility errors // are returned to the caller unless we're already at block zero. - headBlock := rawdb.ReadHeadBlock(db) - if headBlock == nil { - return newcfg, fmt.Errorf("missing head block") + // we use last accepted block for cfg compatibility check. Note this allows + // the node to continue if it previously halted due to attempting to process blocks with + // an incorrect chain config. + lastBlock := ReadBlockByHash(db, lastAcceptedHash) + // this should never happen, but we check anyway + // when we start syncing from scratch, the last accepted block + // will be genesis block + if lastBlock == nil { + return newcfg, fmt.Errorf("missing last accepted block") } - height := headBlock.NumberU64() - timestamp := headBlock.Time() + height := lastBlock.NumberU64() + timestamp := lastBlock.Time() compatErr := storedcfg.CheckCompatible(newcfg, height, timestamp) if compatErr != nil && height != 0 && compatErr.RewindTo != 0 { return newcfg, compatErr @@ -280,7 +286,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { head.BaseFee = big.NewInt(params.ApricotPhase3InitialBaseFee) } } - statedb.Commit(false) + statedb.Commit(false, false) if err := statedb.Database().TrieDB().Commit(root, true, nil); err != nil { panic(fmt.Sprintf("unable to commit genesis block: %v", err)) } @@ -330,3 +336,12 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big } return g.MustCommit(db) } + +// ReadBlockByHash reads the block with the given hash from the database. +func ReadBlockByHash(db ethdb.Reader, hash common.Hash) *types.Block { + blockNumber := rawdb.ReadHeaderNumber(db, hash) + if blockNumber == nil { + return nil + } + return rawdb.ReadBlock(db, hash, *blockNumber) +} diff --git a/core/genesis_test.go b/core/genesis_test.go index ee0090c437..9fabdac788 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -32,17 +32,18 @@ import ( "reflect" "testing" + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/params" - "github.com/davecgh/go-spew/spew" - "github.com/ethereum/go-ethereum/common" ) -func setupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) { - conf, err := SetupGenesisBlock(db, genesis) +func setupGenesisBlock(db ethdb.Database, genesis *Genesis, lastAcceptedHash common.Hash) (*params.ChainConfig, common.Hash, error) { + conf, err := SetupGenesisBlock(db, genesis, lastAcceptedHash) stored := rawdb.ReadCanonicalHash(db, 0) return conf, stored, err } @@ -82,7 +83,7 @@ func TestSetupGenesis(t *testing.T) { { name: "genesis without ChainConfig", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - return setupGenesisBlock(db, new(Genesis)) + return setupGenesisBlock(db, new(Genesis), common.Hash{}) }, wantErr: errGenesisNoConfig, wantConfig: nil, @@ -90,7 +91,7 @@ func TestSetupGenesis(t *testing.T) { { name: "no block in DB, genesis == nil", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - return setupGenesisBlock(db, nil) + return setupGenesisBlock(db, nil, common.Hash{}) }, wantErr: ErrNoGenesis, wantConfig: nil, @@ -99,7 +100,7 @@ func TestSetupGenesis(t *testing.T) { name: "custom block in DB, genesis == nil", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { customg.MustCommit(db) - return setupGenesisBlock(db, nil) + return setupGenesisBlock(db, nil, common.Hash{}) }, wantErr: ErrNoGenesis, wantHash: customghash, @@ -109,7 +110,7 @@ func TestSetupGenesis(t *testing.T) { name: "compatible config in DB", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { oldcustomg.MustCommit(db) - return setupGenesisBlock(db, &customg) + return setupGenesisBlock(db, &customg, customghash) }, wantHash: customghash, wantConfig: customg.Config, @@ -127,8 +128,14 @@ func TestSetupGenesis(t *testing.T) { blocks, _, _ := GenerateChain(oldcustomg.Config, genesis, dummy.NewFullFaker(), db, 4, 25, nil) bc.InsertChain(blocks) bc.CurrentBlock() + for _, block := range blocks { + if err := bc.Accept(block); err != nil { + t.Fatal(err) + } + } + // This should return a compatibility error. - return setupGenesisBlock(db, &customg) + return setupGenesisBlock(db, &customg, bc.lastAccepted.Hash()) }, wantHash: customghash, wantConfig: customg.Config, @@ -165,3 +172,54 @@ func TestSetupGenesis(t *testing.T) { }) } } + +// regression test for precompile activation after header block +func TestNetworkUpgradeBetweenHeadAndAcceptedBlock(t *testing.T) { + db := rawdb.NewMemoryDatabase() + + customg := Genesis{ + Config: params.TestApricotPhase1Config, + Alloc: GenesisAlloc{ + {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, + }, + } + genesis := customg.MustCommit(db) + bc, _ := NewBlockChain(db, DefaultCacheConfig, customg.Config, dummy.NewFullFaker(), vm.Config{}, common.Hash{}) + defer bc.Stop() + + // Advance header to block #4, past the ApricotPhase2 timestamp. + blocks, _, _ := GenerateChain(customg.Config, genesis, dummy.NewFullFaker(), db, 4, 25, nil) + + require := require.New(t) + _, err := bc.InsertChain(blocks) + require.NoError(err) + + // accept up to block #2 + for _, block := range blocks[:2] { + require.NoError(bc.Accept(block)) + } + block := bc.CurrentBlock() + + require.Equal(blocks[1].Hash(), bc.lastAccepted.Hash()) + // header must be bigger than last accepted + require.Greater(block.Time(), bc.lastAccepted.Time()) + + activatedGenesis := customg + apricotPhase2Timestamp := big.NewInt(51) + updatedApricotPhase2Config := *params.TestApricotPhase1Config + updatedApricotPhase2Config.ApricotPhase2BlockTimestamp = apricotPhase2Timestamp + + activatedGenesis.Config = &updatedApricotPhase2Config + + // assert block is after the activation block + require.Greater(block.Time(), apricotPhase2Timestamp.Uint64()) + // assert last accepted block is before the activation block + require.Less(bc.lastAccepted.Time(), apricotPhase2Timestamp.Uint64()) + + // This should not return any error since the last accepted block is before the activation block. + config, _, err := setupGenesisBlock(db, &activatedGenesis, bc.lastAccepted.Hash()) + require.NoError(err) + if !reflect.DeepEqual(config, activatedGenesis.Config) { + t.Errorf("returned %v\nwant %v", config, activatedGenesis.Config) + } +} diff --git a/core/headerchain.go b/core/headerchain.go index 434db4eaf6..a20d70c3bf 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -33,13 +33,13 @@ import ( mrand "math/rand" "sync/atomic" + "github.com/ethereum/go-ethereum/common" + lru "github.com/hashicorp/golang-lru" "github.com/tenderly/coreth/consensus" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - lru "github.com/hashicorp/golang-lru" ) const ( diff --git a/core/headerchain_test.go b/core/headerchain_test.go index e3d2ee987d..6ff5d0c451 100644 --- a/core/headerchain_test.go +++ b/core/headerchain_test.go @@ -32,14 +32,14 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/consensus" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" ) func verifyUnbrokenCanonchain(bc *BlockChain) error { diff --git a/core/mkalloc.go b/core/mkalloc.go index 2890aaad53..d05cdde787 100644 --- a/core/mkalloc.go +++ b/core/mkalloc.go @@ -46,7 +46,7 @@ import ( "strconv" "github.com/tenderly/coreth/core" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) type allocItem struct{ Addr, Balance *big.Int } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 74ee74b0cb..4471978255 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -31,12 +31,12 @@ import ( "encoding/binary" "errors" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) // ReadCanonicalHash retrieves the hash assigned to a canonical block number. @@ -84,8 +84,8 @@ type NumberHash struct { Hash common.Hash } -// ReadAllHashes retrieves all the hashes assigned to blocks at a certain heights, -// both canonical and reorged forks included. +// ReadAllHashesInRange retrieves all the hashes assigned to blocks at a certain +// heights, both canonical and reorged forks included. // This method considers both limits to be _inclusive_. func ReadAllHashesInRange(db ethdb.Iteratee, first, last uint64) []*NumberHash { var ( @@ -378,7 +378,7 @@ func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Rec } // ReadReceipts retrieves all the transaction receipts belonging to a block, including -// its correspoinding metadata fields. If it is unable to populate these metadata +// its corresponding metadata fields. If it is unable to populate these metadata // fields then nil is returned. // // The current implementation populates these metadata fields by reading the receipts' diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index ee5d391652..215b4534d4 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -20,15 +20,15 @@ import ( "bytes" "encoding/hex" "fmt" - "io/ioutil" "math/big" + "os" "reflect" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" "golang.org/x/crypto/sha3" ) @@ -191,7 +191,7 @@ func TestPartialBlockStorage(t *testing.T) { func TestCanonicalMappingStorage(t *testing.T) { db := NewMemoryDatabase() - // Create a test canonical number and assinged hash to move around + // Create a test canonical number and assigned hash to move around hash, number := common.Hash{0: 0xff}, uint64(314) if entry := ReadCanonicalHash(db, number); entry != (common.Hash{}) { t.Fatalf("Non existent canonical mapping returned: %v", entry) @@ -601,7 +601,7 @@ func TestDeriveLogFields(t *testing.T) { func BenchmarkDecodeRLPLogs(b *testing.B) { // Encoded receipts from block 0x14ee094309fbe8f70b65f45ebcc08fb33f126942d97464aad5eb91cfd1e2d269 - buf, err := ioutil.ReadFile("testdata/stored_receipts.bin") + buf, err := os.ReadFile("testdata/stored_receipts.bin") if err != nil { b.Fatal(err) } diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go index ece8d2feb4..ebad242eb5 100644 --- a/core/rawdb/accessors_indexes.go +++ b/core/rawdb/accessors_indexes.go @@ -30,12 +30,12 @@ import ( "bytes" "math/big" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) // ReadTxLookupEntry retrieves the positional metadata associated with a transaction diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index 8bbf9b1dd4..747784d4d9 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -22,10 +22,10 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" "golang.org/x/crypto/sha3" ) diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index 83af88f767..506736b9b0 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -30,11 +30,11 @@ import ( "encoding/json" "time" - "github.com/tenderly/coreth/ethdb" - "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rlp" ) // ReadDatabaseVersion retrieves the version number of the database. diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index b8b3741b21..693bb2de22 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -27,9 +27,9 @@ package rawdb import ( - "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/tenderly/coreth/ethdb" ) // ReadSnapshotRoot retrieves the root of the block whose state is contained in @@ -131,12 +131,12 @@ func DeleteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash com // IterateStorageSnapshots returns an iterator for walking the entire storage // space of a specific account. func IterateStorageSnapshots(db ethdb.Iteratee, accountHash common.Hash) ethdb.Iterator { - return db.NewIterator(storageSnapshotsKey(accountHash), nil) + return NewKeyLengthIterator(db.NewIterator(storageSnapshotsKey(accountHash), nil), len(SnapshotStoragePrefix)+2*common.HashLength) } // IterateAccountSnapshots returns an iterator for walking all of the accounts in the snapshot func IterateAccountSnapshots(db ethdb.Iteratee) ethdb.Iterator { - return db.NewIterator(SnapshotAccountPrefix, nil) + return NewKeyLengthIterator(db.NewIterator(SnapshotAccountPrefix, nil), len(SnapshotAccountPrefix)+common.HashLength) } // ReadSnapshotGenerator retrieves the serialized snapshot generator saved at diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 04f0a0b02c..d747f5b508 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -27,9 +27,9 @@ package rawdb import ( - "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/tenderly/coreth/ethdb" ) // ReadPreimage retrieves a single preimage of the provided hash. @@ -38,71 +38,49 @@ func ReadPreimage(db ethdb.KeyValueReader, hash common.Hash) []byte { return data } -// WritePreimages writes the provided set of preimages to the database. -func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) { - for hash, preimage := range preimages { - if err := db.Put(preimageKey(hash), preimage); err != nil { - log.Crit("Failed to store trie preimage", "err", err) - } - } - preimageCounter.Inc(int64(len(preimages))) - preimageHitCounter.Inc(int64(len(preimages))) -} - // ReadCode retrieves the contract code of the provided code hash. func ReadCode(db ethdb.KeyValueReader, hash common.Hash) []byte { - // Try with the prefixed code scheme first, if not then try with legacy - // scheme. - data := ReadCodeWithPrefix(db, hash) - if len(data) != 0 { - return data - } - // TODO: we have never used the legacy scheme, so we should be able to - // remove the latter attempt. - data, _ = db.Get(hash[:]) + // Try with the prefixed code scheme first and only. The legacy scheme was never used in coreth. + data, _ := db.Get(codeKey(hash)) return data } -// ReadCodeWithPrefix retrieves the contract code of the provided code hash. -// The main difference between this function and ReadCode is this function -// will only check the existence with latest scheme(with prefix). -func ReadCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) []byte { - data, _ := db.Get(codeKey(hash)) +// ReadTrieNode retrieves the trie node of the provided hash. +func ReadTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte { + data, _ := db.Get(hash.Bytes()) return data } -// HasCodeWithPrefix checks if the contract code corresponding to the -// provided code hash is present in the db. This function will only check -// presence using the prefix-scheme. -func HasCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) bool { +// HasCode checks if the contract code corresponding to the +// provided code hash is present in the db. +func HasCode(db ethdb.KeyValueReader, hash common.Hash) bool { + // Try with the prefixed code scheme first and only. The legacy scheme was never used in coreth. ok, _ := db.Has(codeKey(hash)) return ok } -// WriteCode writes the provided contract code database. -func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) { - if err := db.Put(codeKey(hash), code); err != nil { - log.Crit("Failed to store contract code", "err", err) - } +// HasTrieNode checks if the trie node with the provided hash is present in db. +func HasTrieNode(db ethdb.KeyValueReader, hash common.Hash) bool { + ok, _ := db.Has(hash.Bytes()) + return ok } -// DeleteCode deletes the specified contract code from the database. -func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) { - if err := db.Delete(codeKey(hash)); err != nil { - log.Crit("Failed to delete contract code", "err", err) +// WritePreimages writes the provided set of preimages to the database. +func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) { + for hash, preimage := range preimages { + if err := db.Put(preimageKey(hash), preimage); err != nil { + log.Crit("Failed to store trie preimage", "err", err) + } } + preimageCounter.Inc(int64(len(preimages))) + preimageHitCounter.Inc(int64(len(preimages))) } -// ReadTrieNode retrieves the trie node of the provided hash. -func ReadTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte { - data, _ := db.Get(hash.Bytes()) - return data -} - -// HasTrieNode checks if the trie node with the provided hash is present in db. -func HasTrieNode(db ethdb.KeyValueReader, hash common.Hash) bool { - ok, _ := db.Has(hash.Bytes()) - return ok +// WriteCode writes the provided contract code database. +func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) { + if err := db.Put(codeKey(hash), code); err != nil { + log.Crit("Failed to store contract code", "err", err) + } } // WriteTrieNode writes the provided trie node database. @@ -112,6 +90,13 @@ func WriteTrieNode(db ethdb.KeyValueWriter, hash common.Hash, node []byte) { } } +// DeleteCode deletes the specified contract code from the database. +func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(codeKey(hash)); err != nil { + log.Crit("Failed to delete contract code", "err", err) + } +} + // DeleteTrieNode deletes the specified trie node from the database. func DeleteTrieNode(db ethdb.KeyValueWriter, hash common.Hash) { if err := db.Delete(hash.Bytes()); err != nil { diff --git a/core/rawdb/accessors_state_sync.go b/core/rawdb/accessors_state_sync.go new file mode 100644 index 0000000000..2ef9a88ece --- /dev/null +++ b/core/rawdb/accessors_state_sync.go @@ -0,0 +1,155 @@ +// (c) 2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rawdb + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/tenderly/coreth/ethdb" +) + +// ReadSyncRoot reads the root corresponding to the main trie of an in-progress +// sync and returns common.Hash{} if no in-progress sync was found. +func ReadSyncRoot(db ethdb.KeyValueReader) (common.Hash, error) { + has, err := db.Has(syncRootKey) + if err != nil || !has { + return common.Hash{}, err + } + root, err := db.Get(syncRootKey) + if err != nil { + return common.Hash{}, err + } + return common.BytesToHash(root), nil +} + +// WriteSyncRoot writes root as the root of the main trie of the in-progress sync. +func WriteSyncRoot(db ethdb.KeyValueWriter, root common.Hash) error { + return db.Put(syncRootKey, root[:]) +} + +// AddCodeToFetch adds a marker that we need to fetch the code for [hash]. +func AddCodeToFetch(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Put(codeToFetchKey(hash), nil); err != nil { + log.Crit("Failed to put code to fetch", "codeHash", hash, "err", err) + } +} + +// DeleteCodeToFetch removes the marker that the code corresponding to [hash] needs to be fetched. +func DeleteCodeToFetch(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(codeToFetchKey(hash)); err != nil { + log.Crit("Failed to delete code to fetch", "codeHash", hash, "err", err) + } +} + +// NewCodeToFetchIterator returns a KeyLength iterator over all code +// hashes that are pending syncing. It is the caller's responsibility to +// unpack the key and call Release on the returned iterator. +func NewCodeToFetchIterator(db ethdb.Iteratee) ethdb.Iterator { + return NewKeyLengthIterator( + db.NewIterator(CodeToFetchPrefix, nil), + codeToFetchKeyLength, + ) +} + +func codeToFetchKey(hash common.Hash) []byte { + codeToFetchKey := make([]byte, codeToFetchKeyLength) + copy(codeToFetchKey, CodeToFetchPrefix) + copy(codeToFetchKey[len(CodeToFetchPrefix):], hash[:]) + return codeToFetchKey +} + +// NewSyncSegmentsIterator returns a KeyLength iterator over all trie segments +// added for root. It is the caller's responsibility to unpack the key and call +// Release on the returned iterator. +func NewSyncSegmentsIterator(db ethdb.Iteratee, root common.Hash) ethdb.Iterator { + segmentsPrefix := make([]byte, len(syncSegmentsPrefix)+common.HashLength) + copy(segmentsPrefix, syncSegmentsPrefix) + copy(segmentsPrefix[len(syncSegmentsPrefix):], root[:]) + + return NewKeyLengthIterator( + db.NewIterator(segmentsPrefix, nil), + syncSegmentsKeyLength, + ) +} + +// WriteSyncSegment adds a trie segment for root at the given start position. +func WriteSyncSegment(db ethdb.KeyValueWriter, root common.Hash, start []byte) error { + return db.Put(packSyncSegmentKey(root, start), []byte{0x01}) +} + +// ClearSegment removes segment markers for root from db +func ClearSyncSegments(db ethdb.KeyValueStore, root common.Hash) error { + segmentsPrefix := make([]byte, len(syncSegmentsPrefix)+common.HashLength) + copy(segmentsPrefix, syncSegmentsPrefix) + copy(segmentsPrefix[len(syncSegmentsPrefix):], root[:]) + + return ClearPrefix(db, segmentsPrefix) +} + +// ClearAllSyncSegments removes all segment markers from db +func ClearAllSyncSegments(db ethdb.KeyValueStore) error { + return ClearPrefix(db, syncSegmentsPrefix) +} + +// UnpackSyncSegmentKey returns the root and start position for a trie segment +// key returned from NewSyncSegmentsIterator. +func UnpackSyncSegmentKey(keyBytes []byte) (common.Hash, []byte) { + keyBytes = keyBytes[len(syncSegmentsPrefix):] // skip prefix + root := common.BytesToHash(keyBytes[:common.HashLength]) + start := keyBytes[common.HashLength:] + return root, start +} + +// packSyncSegmentKey packs root and account into a key for storage in db. +func packSyncSegmentKey(root common.Hash, start []byte) []byte { + bytes := make([]byte, len(syncSegmentsPrefix)+common.HashLength+len(start)) + copy(bytes, syncSegmentsPrefix) + copy(bytes[len(syncSegmentsPrefix):], root[:]) + copy(bytes[len(syncSegmentsPrefix)+common.HashLength:], start) + return bytes +} + +// NewSyncStorageTriesIterator returns a KeyLength iterator over all storage tries +// added for syncing (beginning at seek). It is the caller's responsibility to unpack +// the key and call Release on the returned iterator. +func NewSyncStorageTriesIterator(db ethdb.Iteratee, seek []byte) ethdb.Iterator { + return NewKeyLengthIterator(db.NewIterator(syncStorageTriesPrefix, seek), syncStorageTriesKeyLength) +} + +// WriteSyncStorageTrie adds a storage trie for account (with the given root) to be synced. +func WriteSyncStorageTrie(db ethdb.KeyValueWriter, root common.Hash, account common.Hash) error { + return db.Put(packSyncStorageTrieKey(root, account), []byte{0x01}) +} + +// ClearSyncStorageTrie removes all storage trie accounts (with the given root) from db. +// Intended for use when the trie with root has completed syncing. +func ClearSyncStorageTrie(db ethdb.KeyValueStore, root common.Hash) error { + accountsPrefix := make([]byte, len(syncStorageTriesPrefix)+common.HashLength) + copy(accountsPrefix, syncStorageTriesPrefix) + copy(accountsPrefix[len(syncStorageTriesPrefix):], root[:]) + return ClearPrefix(db, accountsPrefix) +} + +// ClearAllSyncStorageTries removes all storage tries added for syncing from db +func ClearAllSyncStorageTries(db ethdb.KeyValueStore) error { + return ClearPrefix(db, syncStorageTriesPrefix) +} + +// UnpackSyncStorageTrieKey returns the root and account for a storage trie +// key returned from NewSyncStorageTriesIterator. +func UnpackSyncStorageTrieKey(keyBytes []byte) (common.Hash, common.Hash) { + keyBytes = keyBytes[len(syncStorageTriesPrefix):] // skip prefix + root := common.BytesToHash(keyBytes[:common.HashLength]) + account := common.BytesToHash(keyBytes[common.HashLength:]) + return root, account +} + +// packSyncStorageTrieKey packs root and account into a key for storage in db. +func packSyncStorageTrieKey(root common.Hash, account common.Hash) []byte { + bytes := make([]byte, 0, syncStorageTriesKeyLength) + bytes = append(bytes, syncStorageTriesPrefix...) + bytes = append(bytes, root[:]...) + bytes = append(bytes, account[:]...) + return bytes +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 9a936bef90..098c453125 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -32,12 +32,12 @@ import ( "os" "time" - "github.com/tenderly/coreth/ethdb" - "github.com/tenderly/coreth/ethdb/leveldb" - "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/olekukonko/tablewriter" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/leveldb" + "github.com/tenderly/coreth/ethdb/memorydb" ) // nofreezedb is a database wrapper that disables freezer data retrievals. @@ -130,6 +130,11 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { bloomBits stat cliqueSnaps stat + // State sync statistics + codeToFetch stat + syncProgress stat + syncSegments stat + // Les statistic chtTrieNodes stat bloomTrieNodes stat @@ -187,11 +192,18 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { bytes.HasPrefix(key, []byte("bltIndex-")) || bytes.HasPrefix(key, []byte("bltRoot-")): // Bloomtrie sub bloomTrieNodes.Add(size) + case bytes.HasPrefix(key, syncStorageTriesPrefix) && len(key) == syncStorageTriesKeyLength: + syncProgress.Add(size) + case bytes.HasPrefix(key, syncSegmentsPrefix) && len(key) == syncSegmentsKeyLength: + syncSegments.Add(size) + case bytes.HasPrefix(key, CodeToFetchPrefix) && len(key) == codeToFetchKeyLength: + codeToFetch.Add(size) default: var accounted bool for _, meta := range [][]byte{ databaseVersionKey, headHeaderKey, headBlockKey, - snapshotRootKey, snapshotGeneratorKey, uncleanShutdownKey, + snapshotRootKey, snapshotBlockHashKey, snapshotGeneratorKey, + uncleanShutdownKey, syncRootKey, } { if bytes.Equal(key, meta) { metadata.Add(size) @@ -227,6 +239,9 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { {"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()}, {"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()}, {"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()}, + {"State sync", "Trie segments", syncSegments.Size(), syncSegments.Count()}, + {"State sync", "Storage tries to fetch", syncProgress.Size(), syncProgress.Count()}, + {"State sync", "Code to fetch", codeToFetch.Size(), codeToFetch.Count()}, } table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"Database", "Category", "Size", "Items"}) @@ -240,3 +255,27 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { return nil } + +// ClearPrefix removes all keys in db that begin with prefix +func ClearPrefix(db ethdb.KeyValueStore, prefix []byte) error { + it := db.NewIterator(prefix, nil) + defer it.Release() + + batch := db.NewBatch() + for it.Next() { + key := common.CopyBytes(it.Key()) + if err := batch.Delete(key); err != nil { + return err + } + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + return err + } + batch.Reset() + } + } + if err := it.Error(); err != nil { + return err + } + return batch.Write() +} diff --git a/core/rawdb/key_length_iterator.go b/core/rawdb/key_length_iterator.go new file mode 100644 index 0000000000..1ef48adf21 --- /dev/null +++ b/core/rawdb/key_length_iterator.go @@ -0,0 +1,57 @@ +// (c) 2022, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import "github.com/tenderly/coreth/ethdb" + +// KeyLengthIterator is a wrapper for a database iterator that ensures only key-value pairs +// with a specific key length will be returned. +type KeyLengthIterator struct { + requiredKeyLength int + ethdb.Iterator +} + +// NewKeyLengthIterator returns a wrapped version of the iterator that will only return key-value +// pairs where keys with a specific key length will be returned. +func NewKeyLengthIterator(it ethdb.Iterator, keyLen int) ethdb.Iterator { + return &KeyLengthIterator{ + Iterator: it, + requiredKeyLength: keyLen, + } +} + +func (it *KeyLengthIterator) Next() bool { + // Return true as soon as a key with the required key length is discovered + for it.Iterator.Next() { + if len(it.Iterator.Key()) == it.requiredKeyLength { + return true + } + } + + // Return false when we exhaust the keys in the underlying iterator. + return false +} diff --git a/core/rawdb/key_length_iterator_test.go b/core/rawdb/key_length_iterator_test.go new file mode 100644 index 0000000000..654efc5b55 --- /dev/null +++ b/core/rawdb/key_length_iterator_test.go @@ -0,0 +1,60 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "encoding/binary" + "testing" +) + +func TestKeyLengthIterator(t *testing.T) { + db := NewMemoryDatabase() + + keyLen := 8 + expectedKeys := make(map[string]struct{}) + for i := 0; i < 100; i++ { + key := make([]byte, keyLen) + binary.BigEndian.PutUint64(key, uint64(i)) + if err := db.Put(key, []byte{0x1}); err != nil { + t.Fatal(err) + } + expectedKeys[string(key)] = struct{}{} + + longerKey := make([]byte, keyLen*2) + binary.BigEndian.PutUint64(longerKey, uint64(i)) + if err := db.Put(longerKey, []byte{0x1}); err != nil { + t.Fatal(err) + } + } + + it := NewKeyLengthIterator(db.NewIterator(nil, nil), keyLen) + for it.Next() { + key := it.Key() + _, exists := expectedKeys[string(key)] + if !exists { + t.Fatalf("Found unexpected key %d", binary.BigEndian.Uint64(key)) + } + delete(expectedKeys, string(key)) + if len(key) != keyLen { + t.Fatalf("Found unexpected key in key length iterator with length %d", len(key)) + } + } + + if len(expectedKeys) != 0 { + t.Fatalf("Expected all keys of length %d to be removed from expected keys during iteration", keyLen) + } +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 2729a18848..a7b3fda93e 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -31,8 +31,8 @@ import ( "bytes" "encoding/binary" - "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/metrics" ) // The fields below define the low level database schema prefixing. @@ -85,6 +85,17 @@ var ( SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value CodePrefix = []byte("c") // CodePrefix + code hash -> account code + // State sync progress keys and prefixes + syncRootKey = []byte("sync_root") // indicates the root of the main account trie currently being synced + syncStorageTriesPrefix = []byte("sync_storage") // syncStorageTriesPrefix + trie root + account hash: indicates a storage trie must be fetched for the account + syncSegmentsPrefix = []byte("sync_segments") // syncSegmentsPrefix + trie root + 32-byte start key: indicates the trie at root has a segment starting at the specified key + CodeToFetchPrefix = []byte("CP") // CodeToFetchPrefix + code hash -> empty value tracks the outstanding code hashes we need to fetch. + + // State sync progress key lengths + syncStorageTriesKeyLength = len(syncStorageTriesPrefix) + 2*common.HashLength + syncSegmentsKeyLength = len(syncSegmentsPrefix) + 2*common.HashLength + codeToFetchKeyLength = len(CodeToFetchPrefix) + common.HashLength + preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 1ac02ca3ae..c42ca3caab 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -131,6 +131,11 @@ func (t *table) NewBatch() ethdb.Batch { return &tableBatch{t.db.NewBatch(), t.prefix} } +// NewBatchWithSize creates a write-only database batch with pre-allocated buffer. +func (t *table) NewBatchWithSize(size int) ethdb.Batch { + return &tableBatch{t.db.NewBatchWithSize(size), t.prefix} +} + // tableBatch is a wrapper around a database batch that prefixes each key access // with a pre-configured string. type tableBatch struct { diff --git a/core/rlp_test.go b/core/rlp_test.go index f972522ea6..bc2a260541 100644 --- a/core/rlp_test.go +++ b/core/rlp_test.go @@ -8,7 +8,7 @@ // // Much love to the original authors for their work. // ********** -// Copyright 2019 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -31,13 +31,13 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" "golang.org/x/crypto/sha3" ) diff --git a/core/state/database.go b/core/state/database.go index 45a45d813d..f55c787b5c 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -31,12 +31,12 @@ import ( "fmt" "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + lru "github.com/hashicorp/golang-lru" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - lru "github.com/hashicorp/golang-lru" ) const ( @@ -73,7 +73,7 @@ type Trie interface { // GetKey returns the sha3 preimage of a hashed key that was previously used // to store a value. // - // TODO(fjl): remove this when SecureTrie is removed + // TODO(fjl): remove this when StateTrie is removed GetKey([]byte) []byte // TryGet returns the value for key stored in the trie. The value bytes must @@ -81,8 +81,8 @@ type Trie interface { // trie.MissingNodeError is returned. TryGet(key []byte) ([]byte, error) - // TryUpdateAccount abstract an account write in the trie. - TryUpdateAccount(key []byte, account *types.StateAccount) error + // TryGetAccount abstract an account read from the trie. + TryGetAccount(key []byte) (*types.StateAccount, error) // TryUpdate associates key with value in the trie. If value has length zero, any // existing value is deleted from the trie. The value bytes must not be modified @@ -90,17 +90,27 @@ type Trie interface { // database, a trie.MissingNodeError is returned. TryUpdate(key, value []byte) error + // TryUpdateAccount abstract an account write to the trie. + TryUpdateAccount(key []byte, account *types.StateAccount) error + // TryDelete removes any existing value for key from the trie. If a node was not // found in the database, a trie.MissingNodeError is returned. TryDelete(key []byte) error + // TryDeleteAccount abstracts an account deletion from the trie. + TryDeleteAccount(key []byte) error + // Hash returns the root hash of the trie. It does not write to the database and // can be used even if the trie doesn't have one. Hash() common.Hash - // Commit writes all nodes to the trie's memory database, tracking the internal - // and external (for account tries) references. - Commit(onleaf trie.LeafCallback) (common.Hash, int, error) + // Commit collects all dirty nodes in the trie and replace them with the + // corresponding node hash. All collected nodes(including dirty leaves if + // collectLeaf is true) will be encapsulated into a nodeset for return. + // The returned nodeset can be nil if the trie is clean(nothing to commit). + // Once the trie is committed, it's not usable anymore. A new trie must + // be created with new root and updated trie database for following usage + Commit(collectLeaf bool) (common.Hash, *trie.NodeSet, error) // NodeIterator returns an iterator that returns nodes of the trie. Iteration // starts at the key after the given start key. @@ -143,7 +153,7 @@ type cachingDB struct { // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { - tr, err := trie.NewSecure(root, db.db) + tr, err := trie.NewStateTrie(common.Hash{}, root, db.db) if err != nil { return nil, err } @@ -152,7 +162,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { // OpenStorageTrie opens the storage trie of an account. func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) { - tr, err := trie.NewSecure(root, db.db) + tr, err := trie.NewStateTrie(addrHash, root, db.db) if err != nil { return nil, err } @@ -162,7 +172,7 @@ func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) { // CopyTrie returns an independent copy of the given trie. func (db *cachingDB) CopyTrie(t Trie) Trie { switch t := t.(type) { - case *trie.SecureTrie: + case *trie.StateTrie: return t.Copy() default: panic(fmt.Errorf("unknown trie type %T", t)) @@ -183,22 +193,6 @@ func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error return nil, errors.New("not found") } -// ContractCodeWithPrefix retrieves a particular contract's code. If the -// code can't be found in the cache, then check the existence with **new** -// db scheme. -func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error) { - if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 { - return code, nil - } - code := rawdb.ReadCodeWithPrefix(db.db.DiskDB(), codeHash) - if len(code) > 0 { - db.codeCache.Set(codeHash.Bytes(), code) - db.codeSizeCache.Add(codeHash, len(code)) - return code, nil - } - return nil, errors.New("not found") -} - // ContractCodeSize retrieves a particular contracts code's size. func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) { if cached, ok := db.codeSizeCache.Get(codeHash); ok { diff --git a/core/state/dump.go b/core/state/dump.go index 72a2dc09e0..3895dd2ab3 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -31,12 +31,12 @@ import ( "fmt" "time" - "github.com/tenderly/coreth/core/types" - "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/rlp" + "github.com/tenderly/coreth/trie" ) // DumpConfig is a set of options to control what portions of the statewill be diff --git a/core/state/iterator.go b/core/state/iterator.go index c174d88d5c..fd6b72f8d4 100644 --- a/core/state/iterator.go +++ b/core/state/iterator.go @@ -30,10 +30,10 @@ import ( "bytes" "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/rlp" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" ) // NodeIterator is an iterator to traverse the entire state trie post-order, diff --git a/core/state/metrics.go b/core/state/metrics.go index 156bfd7116..7ac971b8ad 100644 --- a/core/state/metrics.go +++ b/core/state/metrics.go @@ -29,10 +29,10 @@ package state import "github.com/tenderly/coreth/metrics" var ( - accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil) - storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil) - accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil) - storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil) - accountCommittedMeter = metrics.NewRegisteredMeter("state/commit/account", nil) - storageCommittedMeter = metrics.NewRegisteredMeter("state/commit/storage", nil) + accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil) + storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil) + accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil) + storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil) + accountTrieCommittedMeter = metrics.NewRegisteredMeter("state/commit/accountnodes", nil) + storageTriesCommittedMeter = metrics.NewRegisteredMeter("state/commit/storagenodes", nil) ) diff --git a/core/state/pruner/bloom.go b/core/state/pruner/bloom.go index 913b017dee..e5a3da3612 100644 --- a/core/state/pruner/bloom.go +++ b/core/state/pruner/bloom.go @@ -8,7 +8,7 @@ // // Much love to the original authors for their work. // ********** -// Copyright 2020 The go-ethereum Authors +// Copyright 2021 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -31,10 +31,10 @@ import ( "errors" "os" - "github.com/tenderly/coreth/core/rawdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" bloomfilter "github.com/holiman/bloomfilter/v2" + "github.com/tenderly/coreth/core/rawdb" ) // stateBloomHasher is a wrapper around a byte blob to satisfy the interface API @@ -49,7 +49,7 @@ func (f stateBloomHasher) BlockSize() int { panic("not implem func (f stateBloomHasher) Size() int { return 8 } func (f stateBloomHasher) Sum64() uint64 { return binary.BigEndian.Uint64(f) } -// stateBloom is a bloom filter used during the state convesion(snapshot->state). +// stateBloom is a bloom filter used during the state conversion(snapshot->state). // The keys of all generated entries will be recorded here so that in the pruning // stage the entries belong to the specific version can be avoided for deletion. // @@ -110,7 +110,7 @@ func (bloom *stateBloom) Commit(filename, tempname string) error { } f.Close() - // Move the teporary file into it's final location + // Move the temporary file into it's final location return os.Rename(tempname, filename) } diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index eb1bce24bd..5cd0885f8a 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -8,7 +8,7 @@ // // Much love to the original authors for their work. // ********** -// Copyright 2020 The go-ethereum Authors +// Copyright 2021 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -37,15 +37,15 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state/snapshot" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/rlp" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) const ( @@ -341,7 +341,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { if genesis == nil { return errors.New("missing genesis block") } - t, err := trie.NewSecure(genesis.Root(), trie.NewDatabase(db)) + t, err := trie.NewStateTrie(common.Hash{}, genesis.Root(), trie.NewDatabase(db)) if err != nil { return err } @@ -361,7 +361,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { return err } if acc.Root != emptyRoot { - storageTrie, err := trie.NewSecure(acc.Root, trie.NewDatabase(db)) + storageTrie, err := trie.NewStateTrie(common.BytesToHash(accIter.LeafKey()), acc.Root, trie.NewDatabase(db)) if err != nil { return err } diff --git a/core/state/snapshot/account.go b/core/state/snapshot/account.go index 296c6e7741..d23896b66f 100644 --- a/core/state/snapshot/account.go +++ b/core/state/snapshot/account.go @@ -31,7 +31,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) // Account is a modified version of a state.Account, where the root is replaced diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index 65a249270b..75502d7174 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -36,12 +36,12 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/rlp" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) // trieKV represents a trie key-value pair @@ -53,7 +53,7 @@ type trieKV struct { type ( // trieGeneratorFn is the interface of trie generation which can // be implemented by different trie algorithm. - trieGeneratorFn func(db ethdb.KeyValueWriter, in chan (trieKV), out chan (common.Hash)) + trieGeneratorFn func(db ethdb.KeyValueWriter, owner common.Hash, in chan (trieKV), out chan (common.Hash)) // leafCallbackFn is the callback invoked at the leaves of the trie, // returns the subtrie root with the specified subtrie identifier. @@ -263,7 +263,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, it Iterator, account common.Hash, wg.Add(1) go func() { defer wg.Done() - generatorFn(db, in, out) + generatorFn(db, account, in, out) }() // Spin up a go-routine for progress logging if report && stats != nil { @@ -370,8 +370,8 @@ func generateTrieRoot(db ethdb.KeyValueWriter, it Iterator, account common.Hash, return stop(nil) } -func stackTrieGenerate(db ethdb.KeyValueWriter, in chan trieKV, out chan common.Hash) { - t := trie.NewStackTrie(db) +func stackTrieGenerate(db ethdb.KeyValueWriter, owner common.Hash, in chan trieKV, out chan common.Hash) { + t := trie.NewStackTrieWithOwner(db, owner) for leaf := range in { t.TryUpdate(leaf.key[:], leaf.value) } diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index bb94f612f8..b2f23a7a33 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -37,8 +37,8 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" bloomfilter "github.com/holiman/bloomfilter/v2" + "github.com/tenderly/coreth/rlp" ) var ( @@ -275,6 +275,9 @@ func (dl *diffLayer) BlockHash() common.Hash { // Parent returns the subsequent layer of a diff layer. func (dl *diffLayer) Parent() snapshot { + dl.lock.RLock() + defer dl.lock.RUnlock() + return dl.parent } @@ -487,7 +490,6 @@ func (dl *diffLayer) flatten() snapshot { for storageHash, data := range storage { comboData[storageHash] = data } - parent.storageData[accountHash] = comboData } // Return the combo parent return &diffLayer{ diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index c4ca35142f..e4dbc47055 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -32,9 +32,9 @@ import ( "testing" "github.com/VictoriaMetrics/fastcache" - "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/tenderly/coreth/ethdb/memorydb" ) func copyDestructs(destructs map[common.Hash]struct{}) map[common.Hash]struct{} { @@ -342,7 +342,6 @@ func BenchmarkFlatten(b *testing.B) { value := make([]byte, 32) rand.Read(value) accStorage[randomHash()] = value - } storage[accountKey] = accStorage } diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index 35997fad1c..96ed409393 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -32,11 +32,11 @@ import ( "time" "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/rlp" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" ) // diskLayer is a low level persistent snapshot built on top of a key-value store. diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go index 98f190e8d9..4f211da82b 100644 --- a/core/state/snapshot/disklayer_test.go +++ b/core/state/snapshot/disklayer_test.go @@ -30,11 +30,10 @@ import ( "bytes" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/rawdb" - "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/ethdb/memorydb" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) // reverse reverses the contents of a byte slice. It's used to update random accs @@ -514,7 +513,8 @@ func TestDiskMidAccountPartialMerge(t *testing.T) { // TestDiskSeek tests that seek-operations work on the disk layer func TestDiskSeek(t *testing.T) { // Create some accounts in the disk layer - var db ethdb.Database = rawdb.NewMemoryDatabase() + db := rawdb.NewMemoryDatabase() + defer db.Close() // Fill even keys [0,2,4...] for i := 0; i < 0xff; i += 2 { diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 45a8146312..c89db04cfb 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -34,14 +34,14 @@ import ( "time" "github.com/VictoriaMetrics/fastcache" - "github.com/tenderly/coreth/core/rawdb" - "github.com/tenderly/coreth/ethdb" - "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/rlp" + "github.com/tenderly/coreth/trie" ) var ( @@ -274,7 +274,7 @@ func (dl *diskLayer) generate(stats *generatorStats) { } } // Create an account and state iterator pointing to the current generator marker - accTrie, err := trie.NewSecure(dl.root, dl.triedb) + accTrie, err := trie.NewStateTrie(common.Hash{}, dl.root, dl.triedb) if err != nil { // The account trie is missing (GC), surf the chain until one becomes available stats.Info("Trie missing, state snapshotting paused", dl.root, dl.genMarker) @@ -329,7 +329,7 @@ func (dl *diskLayer) generate(stats *generatorStats) { // If the iterated account is a contract, iterate through corresponding contract // storage to generate snapshot entries. if acc.Root != emptyRoot { - storeTrie, err := trie.NewSecure(acc.Root, dl.triedb) + storeTrie, err := trie.NewStateTrie(accountHash, acc.Root, dl.triedb) if err != nil { log.Error("Generator failed to access storage trie", "root", dl.root, "account", accountHash, "stroot", acc.Root, "err", err) abort := <-dl.genAbort diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index 51531c8da2..505039917f 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/generate_test.go @@ -8,7 +8,7 @@ // // Much love to the original authors for their work. // ********** -// Copyright 2020 The go-ethereum Authors +// Copyright 2019 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -33,50 +33,45 @@ import ( "testing" "time" - "github.com/tenderly/coreth/ethdb" - "github.com/tenderly/coreth/ethdb/memorydb" - "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/rlp" + "github.com/tenderly/coreth/trie" "golang.org/x/crypto/sha3" ) +var testBlockHash = common.HexToHash("0xdeadbeef") + +func hashData(input []byte) common.Hash { + var hasher = sha3.NewLegacyKeccak256() + var hash common.Hash + hasher.Reset() + hasher.Write(input) + hasher.Sum(hash[:0]) + return hash +} + // Tests that snapshot generation from an empty database. func TestGeneration(t *testing.T) { // We can't use statedb to make a test trie (circular dependency), so make // a fake one manually. We're going with a small account trie of 3 accounts, - // two of which also has the same 3-slot storage trie attached. - var ( - diskdb = memorydb.New() - triedb = trie.NewDatabase(diskdb) - ) - stTrie, _ := trie.NewSecure(common.Hash{}, triedb) - stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0 - stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371 - stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78 - stTrie.Commit(nil) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 - - accTrie, _ := trie.NewSecure(common.Hash{}, triedb) - acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} - val, _ := rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-1"), val) + var helper = newHelper() + stRoot := helper.makeStorageTrie(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, false) + + helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) + helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) - acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} - val, _ = rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-2"), val) + helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} - val, _ = rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-3"), val) - root, _, _ := accTrie.Commit(nil) // Root: 0xa819054cfef894169a5b56ccc4e5e06f14829d4a57498e8b9fb13ff21491828d - triedb.Commit(root, false, nil) + root, snap := helper.CommitAndGenerate() // two of which also has the same 3-slot storage trie attached. if have, want := root, common.HexToHash("0xa819054cfef894169a5b56ccc4e5e06f14829d4a57498e8b9fb13ff21491828d"); have != want { t.Fatalf("have %#x want %#x", have, want) } - snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), root, nil) select { case <-snap.genPending: // Snapshot generation succeeded @@ -85,63 +80,34 @@ func TestGeneration(t *testing.T) { t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down stop := make(chan struct{}) snap.genAbort <- stop <-stop } -func hashData(input []byte) common.Hash { - var hasher = sha3.NewLegacyKeccak256() - var hash common.Hash - hasher.Reset() - hasher.Write(input) - hasher.Sum(hash[:0]) - return hash -} - // Tests that snapshot generation with existent flat state. func TestGenerateExistentState(t *testing.T) { // We can't use statedb to make a test trie (circular dependency), so make // a fake one manually. We're going with a small account trie of 3 accounts, // two of which also has the same 3-slot storage trie attached. - var ( - diskdb = memorydb.New() - triedb = trie.NewDatabase(diskdb) - ) - stTrie, _ := trie.NewSecure(common.Hash{}, triedb) - stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0 - stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371 - stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78 - stTrie.Commit(nil) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 - - accTrie, _ := trie.NewSecure(common.Hash{}, triedb) - acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} - val, _ := rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e - rawdb.WriteAccountSnapshot(diskdb, hashData([]byte("acc-1")), val) - rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-1")), hashData([]byte("key-1")), []byte("val-1")) - rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-1")), hashData([]byte("key-2")), []byte("val-2")) - rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-1")), hashData([]byte("key-3")), []byte("val-3")) - - acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} - val, _ = rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 - diskdb.Put(hashData([]byte("acc-2")).Bytes(), val) - rawdb.WriteAccountSnapshot(diskdb, hashData([]byte("acc-2")), val) - - acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} - val, _ = rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 - rawdb.WriteAccountSnapshot(diskdb, hashData([]byte("acc-3")), val) - rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-3")), hashData([]byte("key-1")), []byte("val-1")) - rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-3")), hashData([]byte("key-2")), []byte("val-2")) - rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-3")), hashData([]byte("key-3")), []byte("val-3")) - - root, _, _ := accTrie.Commit(nil) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd - triedb.Commit(root, false, nil) - - snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), root, nil) + var helper = newHelper() + + stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) + helper.addSnapAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) + + stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + root, snap := helper.CommitAndGenerate() select { case <-snap.genPending: // Snapshot generation succeeded @@ -150,6 +116,7 @@ func TestGenerateExistentState(t *testing.T) { t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down stop := make(chan struct{}) snap.genAbort <- stop @@ -171,29 +138,33 @@ func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) { } return hash, nil }, newGenerateStats(), true) - if err != nil { t.Fatal(err) } if snapRoot != trieRoot { t.Fatalf("snaproot: %#x != trieroot #%x", snapRoot, trieRoot) } + if err := CheckDanglingStorage(snap.diskdb); err != nil { + t.Fatalf("Detected dangling storages: %v", err) + } } type testHelper struct { - diskdb *memorydb.Database + diskdb ethdb.Database triedb *trie.Database - accTrie *trie.SecureTrie + accTrie *trie.StateTrie + nodes *trie.MergedNodeSet } func newHelper() *testHelper { - diskdb := memorydb.New() + diskdb := rawdb.NewMemoryDatabase() triedb := trie.NewDatabase(diskdb) - accTrie, _ := trie.NewSecure(common.Hash{}, triedb) + accTrie, _ := trie.NewStateTrie(common.Hash{}, common.Hash{}, triedb) return &testHelper{ diskdb: diskdb, triedb: triedb, accTrie: accTrie, + nodes: trie.NewMergedNodeSet(), } } @@ -220,19 +191,34 @@ func (t *testHelper) addSnapStorage(accKey string, keys []string, vals []string) } } -func (t *testHelper) makeStorageTrie(keys []string, vals []string) []byte { - stTrie, _ := trie.NewSecure(common.Hash{}, t.triedb) +func (t *testHelper) makeStorageTrie(owner common.Hash, keys []string, vals []string, commit bool) []byte { + stTrie, _ := trie.NewStateTrie(owner, common.Hash{}, t.triedb) for i, k := range keys { stTrie.Update([]byte(k), []byte(vals[i])) } - root, _, _ := stTrie.Commit(nil) + if !commit { + return stTrie.Hash().Bytes() + } + root, nodes, _ := stTrie.Commit(false) + if nodes != nil { + t.nodes.Merge(nodes) + } return root.Bytes() } -func (t *testHelper) Generate() (common.Hash, *diskLayer) { - root, _, _ := t.accTrie.Commit(nil) +func (t *testHelper) Commit() common.Hash { + root, nodes, _ := t.accTrie.Commit(true) + if nodes != nil { + t.nodes.Merge(nodes) + } + t.triedb.Update(t.nodes) t.triedb.Commit(root, false, nil) - snap := generateSnapshot(t.diskdb, t.triedb, 16, common.HexToHash("0xdeadbeef"), root, nil) + return root +} + +func (t *testHelper) CommitAndGenerate() (common.Hash, *diskLayer) { + root := t.Commit() + snap := generateSnapshot(t.diskdb, t.triedb, 16, testBlockHash, root, nil) return root, snap } @@ -254,26 +240,29 @@ func (t *testHelper) Generate() (common.Hash, *diskLayer) { // - extra slots in the end func TestGenerateExistentStateWithWrongStorage(t *testing.T) { helper := newHelper() - stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) // Account one, empty root but non-empty database helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) // Account two, non empty root but empty database + stRoot := helper.makeStorageTrie(hashData([]byte("acc-2")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) // Miss slots { // Account three, non empty root but misses slots in the beginning + helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) helper.addSnapStorage("acc-3", []string{"key-2", "key-3"}, []string{"val-2", "val-3"}) // Account four, non empty root but misses slots in the middle + helper.makeStorageTrie(hashData([]byte("acc-4")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addAccount("acc-4", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) helper.addSnapStorage("acc-4", []string{"key-1", "key-3"}, []string{"val-1", "val-3"}) // Account five, non empty root but misses slots in the end + helper.makeStorageTrie(hashData([]byte("acc-5")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addAccount("acc-5", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) helper.addSnapStorage("acc-5", []string{"key-1", "key-2"}, []string{"val-1", "val-2"}) } @@ -281,18 +270,22 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) { // Wrong storage slots { // Account six, non empty root but wrong slots in the beginning + helper.makeStorageTrie(hashData([]byte("acc-6")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addAccount("acc-6", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) helper.addSnapStorage("acc-6", []string{"key-1", "key-2", "key-3"}, []string{"badval-1", "val-2", "val-3"}) // Account seven, non empty root but wrong slots in the middle + helper.makeStorageTrie(hashData([]byte("acc-7")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addAccount("acc-7", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) helper.addSnapStorage("acc-7", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "badval-2", "val-3"}) // Account eight, non empty root but wrong slots in the end + helper.makeStorageTrie(hashData([]byte("acc-8")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addAccount("acc-8", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) helper.addSnapStorage("acc-8", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "badval-3"}) // Account 9, non empty root but rotated slots + helper.makeStorageTrie(hashData([]byte("acc-9")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addAccount("acc-9", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) helper.addSnapStorage("acc-9", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-3", "val-2"}) } @@ -300,20 +293,23 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) { // Extra storage slots { // Account 10, non empty root but extra slots in the beginning + helper.makeStorageTrie(hashData([]byte("acc-10")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addAccount("acc-10", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) helper.addSnapStorage("acc-10", []string{"key-0", "key-1", "key-2", "key-3"}, []string{"val-0", "val-1", "val-2", "val-3"}) // Account 11, non empty root but extra slots in the middle + helper.makeStorageTrie(hashData([]byte("acc-11")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addAccount("acc-11", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) helper.addSnapStorage("acc-11", []string{"key-1", "key-2", "key-2-1", "key-3"}, []string{"val-1", "val-2", "val-2-1", "val-3"}) // Account 12, non empty root but extra slots in the end + helper.makeStorageTrie(hashData([]byte("acc-12")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addAccount("acc-12", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) helper.addSnapStorage("acc-12", []string{"key-1", "key-2", "key-3", "key-4"}, []string{"val-1", "val-2", "val-3", "val-4"}) } - root, snap := helper.Generate() - t.Logf("Root: %#x\n", root) // Root = 0x8746cce9fd9c658b2cfd639878ed6584b7a2b3e73bb40f607fcfa156002429a0 + root, snap := helper.CommitAndGenerate() + t.Logf("Root: %#x\n", root) // Root = 0xdd912272f1befd3e1ca84007817f532b6e8a08f7b273c88b0ea0d6701ad3ad03 select { case <-snap.genPending: @@ -336,7 +332,12 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) { // - extra accounts func TestGenerateExistentStateWithWrongAccounts(t *testing.T) { helper := newHelper() - stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.makeStorageTrie(hashData([]byte("acc-2")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.makeStorageTrie(hashData([]byte("acc-4")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + stRoot := helper.makeStorageTrie(hashData([]byte("acc-6")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // Trie accounts [acc-1, acc-2, acc-3, acc-4, acc-6] // Extra accounts [acc-0, acc-5, acc-7] @@ -364,8 +365,8 @@ func TestGenerateExistentStateWithWrongAccounts(t *testing.T) { helper.addSnapAccount("acc-7", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyRoot.Bytes()}) // after the end } - root, snap := helper.Generate() - t.Logf("Root: %#x\n", root) // Root = 0x825891472281463511e7ebcc7f109e4f9200c20fa384754e11fd605cd98464e8 + root, snap := helper.CommitAndGenerate() + t.Logf("Root: %#x\n", root) // Root = 0xe614035f519c878aea2b5e658a3dd61916ff090354222cfad624403e89d96440 select { case <-snap.genPending: @@ -388,29 +389,19 @@ func TestGenerateCorruptAccountTrie(t *testing.T) { // We can't use statedb to make a test trie (circular dependency), so make // a fake one manually. We're going with a small account trie of 3 accounts, // without any storage slots to keep the test smaller. - var ( - diskdb = memorydb.New() - triedb = trie.NewDatabase(diskdb) - ) - tr, _ := trie.NewSecure(common.Hash{}, triedb) - acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} - val, _ := rlp.EncodeToBytes(acc) - tr.Update([]byte("acc-1"), val) // 0x7dd654835190324640832972b7c4c6eaa0c50541e36766d054ed57721f1dc7eb + helper := newHelper() - acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} - val, _ = rlp.EncodeToBytes(acc) - tr.Update([]byte("acc-2"), val) // 0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e + helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x7dd654835190324640832972b7c4c6eaa0c50541e36766d054ed57721f1dc7eb + helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e + helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x515d3de35e143cd976ad476398d910aa7bf8a02e8fd7eb9e3baacddbbcbfcb41 - acc = &Account{Balance: big.NewInt(3), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} - val, _ = rlp.EncodeToBytes(acc) - tr.Update([]byte("acc-3"), val) // 0x515d3de35e143cd976ad476398d910aa7bf8a02e8fd7eb9e3baacddbbcbfcb41 - tr.Commit(nil) // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978 + root := helper.Commit() // Root: 0xfa04f652e8bd3938971bf7d71c3c688574af334ca8bc20e64b01ba610ae93cad // Delete an account trie leaf and ensure the generator chokes - triedb.Commit(common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), false, nil) - diskdb.Delete(common.HexToHash("0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7").Bytes()) + helper.triedb.Commit(root, false, nil) + helper.diskdb.Delete(common.HexToHash("0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e").Bytes()) - snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), nil) + snap := generateSnapshot(helper.diskdb, helper.triedb, 16, testBlockHash, root, nil) select { case <-snap.genPending: // Snapshot generation succeeded @@ -432,41 +423,19 @@ func TestGenerateMissingStorageTrie(t *testing.T) { // We can't use statedb to make a test trie (circular dependency), so make // a fake one manually. We're going with a small account trie of 3 accounts, // two of which also has the same 3-slot storage trie attached. - var ( - diskdb = memorydb.New() - triedb = trie.NewDatabase(diskdb) - ) - stTrie, _ := trie.NewSecure(common.Hash{}, triedb) - stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0 - stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371 - stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78 - stTrie.Commit(nil) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 - - accTrie, _ := trie.NewSecure(common.Hash{}, triedb) - acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} - val, _ := rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-1"), val) // 0x547b07c3a71669c00eda14077d85c7fd14575b92d459572540b25b9a11914dcb - - acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} - val, _ = rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-2"), val) // 0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e - - acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} - val, _ = rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-3"), val) // 0x70da4ebd7602dd313c936b39000ed9ab7f849986a90ea934f0c3ec4cc9840441 - accTrie.Commit(nil) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd + helper := newHelper() - // We can only corrupt the disk database, so flush the tries out - triedb.Reference( - common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"), - common.HexToHash("0xa819054cfef894169a5b56ccc4e5e06f14829d4a57498e8b9fb13ff21491828d"), - ) - triedb.Commit(common.HexToHash("0xa819054cfef894169a5b56ccc4e5e06f14829d4a57498e8b9fb13ff21491828d"), false, nil) + stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 + helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x547b07c3a71669c00eda14077d85c7fd14575b92d459572540b25b9a11914dcb + helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e + stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x70da4ebd7602dd313c936b39000ed9ab7f849986a90ea934f0c3ec4cc9840441 + root := helper.Commit() // Delete a storage trie root and ensure the generator chokes - diskdb.Delete(common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67").Bytes()) + helper.diskdb.Delete(stRoot) // We can only corrupt the disk database, so flush the tries out - snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), nil) + snap := generateSnapshot(helper.diskdb, helper.triedb, 16, testBlockHash, root, nil) select { case <-snap.genPending: // Snapshot generation succeeded @@ -487,41 +456,19 @@ func TestGenerateCorruptStorageTrie(t *testing.T) { // We can't use statedb to make a test trie (circular dependency), so make // a fake one manually. We're going with a small account trie of 3 accounts, // two of which also has the same 3-slot storage trie attached. - var ( - diskdb = memorydb.New() - triedb = trie.NewDatabase(diskdb) - ) - stTrie, _ := trie.NewSecure(common.Hash{}, triedb) - stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0 - stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371 - stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78 - stTrie.Commit(nil) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 - - accTrie, _ := trie.NewSecure(common.Hash{}, triedb) - acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} - val, _ := rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-1"), val) // 0x547b07c3a71669c00eda14077d85c7fd14575b92d459572540b25b9a11914dcb - - acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} - val, _ = rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-2"), val) // 0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e - - acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} - val, _ = rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-3"), val) // 0x70da4ebd7602dd313c936b39000ed9ab7f849986a90ea934f0c3ec4cc9840441 - accTrie.Commit(nil) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd + helper := newHelper() - // We can only corrupt the disk database, so flush the tries out - triedb.Reference( - common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"), - common.HexToHash("0xa819054cfef894169a5b56ccc4e5e06f14829d4a57498e8b9fb13ff21491828d"), - ) - triedb.Commit(common.HexToHash("0xa819054cfef894169a5b56ccc4e5e06f14829d4a57498e8b9fb13ff21491828d"), false, nil) + stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 + helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x547b07c3a71669c00eda14077d85c7fd14575b92d459572540b25b9a11914dcb + helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e + stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x70da4ebd7602dd313c936b39000ed9ab7f849986a90ea934f0c3ec4cc9840441 + root := helper.Commit() // Delete a storage trie leaf and ensure the generator chokes - diskdb.Delete(common.HexToHash("0x16691bc8a441197767e40bb66f521b92952edaf1462813f4f5bcca39aae72ffa").Bytes()) + helper.diskdb.Delete(common.HexToHash("0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371").Bytes()) - snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), nil) + snap := generateSnapshot(helper.diskdb, helper.triedb, 16, testBlockHash, root, nil) select { case <-snap.genPending: // Snapshot generation succeeded @@ -536,56 +483,51 @@ func TestGenerateCorruptStorageTrie(t *testing.T) { <-stop } -func getStorageTrie(n int, triedb *trie.Database) *trie.SecureTrie { - stTrie, _ := trie.NewSecure(common.Hash{}, triedb) - for i := 0; i < n; i++ { - k := fmt.Sprintf("key-%d", i) - v := fmt.Sprintf("val-%d", i) - stTrie.Update([]byte(k), []byte(v)) - } - stTrie.Commit(nil) - return stTrie -} - // Tests that snapshot generation when an extra account with storage exists in the snap state. func TestGenerateWithExtraAccounts(t *testing.T) { - var ( - diskdb = memorydb.New() - triedb = trie.NewDatabase(diskdb) - stTrie = getStorageTrie(5, triedb) - ) - accTrie, _ := trie.NewSecure(common.Hash{}, triedb) - { // Account one in the trie - acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + helper := newHelper() + { + // Account one in the trie + stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), + []string{"key-1", "key-2", "key-3", "key-4", "key-5"}, + []string{"val-1", "val-2", "val-3", "val-4", "val-5"}, + true, + ) + acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()} val, _ := rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + helper.accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + // Identical in the snap key := hashData([]byte("acc-1")) - rawdb.WriteAccountSnapshot(diskdb, key, val) - rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-1")), []byte("val-1")) - rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-2")), []byte("val-2")) - rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-3")), []byte("val-3")) - rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-4")), []byte("val-4")) - rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-5")), []byte("val-5")) - } - { // Account two exists only in the snapshot - acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + rawdb.WriteAccountSnapshot(helper.triedb.DiskDB(), key, val) + rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-1")), []byte("val-1")) + rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-2")), []byte("val-2")) + rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-3")), []byte("val-3")) + rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-4")), []byte("val-4")) + rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-5")), []byte("val-5")) + } + { + // Account two exists only in the snapshot + stRoot := helper.makeStorageTrie(hashData([]byte("acc-2")), + []string{"key-1", "key-2", "key-3", "key-4", "key-5"}, + []string{"val-1", "val-2", "val-3", "val-4", "val-5"}, + true, + ) + acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()} val, _ := rlp.EncodeToBytes(acc) key := hashData([]byte("acc-2")) - rawdb.WriteAccountSnapshot(diskdb, key, val) - rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("b-key-1")), []byte("b-val-1")) - rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("b-key-2")), []byte("b-val-2")) - rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("b-key-3")), []byte("b-val-3")) - } - root, _, _ := accTrie.Commit(nil) - t.Logf("root: %x", root) - triedb.Commit(root, false, nil) + rawdb.WriteAccountSnapshot(helper.triedb.DiskDB(), key, val) + rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("b-key-1")), []byte("b-val-1")) + rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("b-key-2")), []byte("b-val-2")) + rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("b-key-3")), []byte("b-val-3")) + } + root := helper.Commit() + // To verify the test: If we now inspect the snap db, there should exist extraneous storage items - if data := rawdb.ReadStorageSnapshot(diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data == nil { + if data := rawdb.ReadStorageSnapshot(helper.triedb.DiskDB(), hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data == nil { t.Fatalf("expected snap storage to exist") } - - snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), root, nil) + snap := generateSnapshot(helper.diskdb, helper.triedb, 16, testBlockHash, root, nil) select { case <-snap.genPending: // Snapshot generation succeeded @@ -594,12 +536,13 @@ func TestGenerateWithExtraAccounts(t *testing.T) { t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down stop := make(chan struct{}) snap.genAbort <- stop <-stop // If we now inspect the snap db, there should exist no extraneous storage items - if data := rawdb.ReadStorageSnapshot(diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil { + if data := rawdb.ReadStorageSnapshot(helper.triedb.DiskDB(), hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil { t.Fatalf("expected slot to be removed, got %v", string(data)) } } @@ -613,37 +556,36 @@ func TestGenerateWithManyExtraAccounts(t *testing.T) { if false { enableLogging() } - var ( - diskdb = memorydb.New() - triedb = trie.NewDatabase(diskdb) - stTrie = getStorageTrie(3, triedb) - ) - accTrie, _ := trie.NewSecure(common.Hash{}, triedb) - { // Account one in the trie - acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + helper := newHelper() + { + // Account one in the trie + stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), + []string{"key-1", "key-2", "key-3"}, + []string{"val-1", "val-2", "val-3"}, + true, + ) + acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()} val, _ := rlp.EncodeToBytes(acc) - accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + helper.accTrie.Update([]byte("acc-1"), val) // 0x547b07c3a71669c00eda14077d85c7fd14575b92d459572540b25b9a11914dcb + // Identical in the snap key := hashData([]byte("acc-1")) - rawdb.WriteAccountSnapshot(diskdb, key, val) - rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-1")), []byte("val-1")) - rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-2")), []byte("val-2")) - rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-3")), []byte("val-3")) + rawdb.WriteAccountSnapshot(helper.diskdb, key, val) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-1")), []byte("val-1")) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-2")), []byte("val-2")) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-3")), []byte("val-3")) } - { // 100 accounts exist only in snapshot + { + // 100 accounts exist only in snapshot for i := 0; i < 1000; i++ { //acc := &Account{Balance: big.NewInt(int64(i)), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} acc := &Account{Balance: big.NewInt(int64(i)), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} val, _ := rlp.EncodeToBytes(acc) key := hashData([]byte(fmt.Sprintf("acc-%d", i))) - rawdb.WriteAccountSnapshot(diskdb, key, val) + rawdb.WriteAccountSnapshot(helper.diskdb, key, val) } } - root, _, _ := accTrie.Commit(nil) - t.Logf("root: %x", root) - triedb.Commit(root, false, nil) - - snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), root, nil) + root, snap := helper.CommitAndGenerate() select { case <-snap.genPending: // Snapshot generation succeeded @@ -671,31 +613,22 @@ func TestGenerateWithExtraBeforeAndAfter(t *testing.T) { if false { enableLogging() } - var ( - diskdb = memorydb.New() - triedb = trie.NewDatabase(diskdb) - ) - accTrie, _ := trie.New(common.Hash{}, triedb) + helper := newHelper() { acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} val, _ := rlp.EncodeToBytes(acc) - accTrie.Update(common.HexToHash("0x03").Bytes(), val) - accTrie.Update(common.HexToHash("0x07").Bytes(), val) - - rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x01"), val) - rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x02"), val) - rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x03"), val) - rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x04"), val) - rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x05"), val) - rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x06"), val) - rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x07"), val) - } - - root, _, _ := accTrie.Commit(nil) - t.Logf("root: %x", root) - triedb.Commit(root, false, nil) - - snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), root, nil) + helper.accTrie.Update(common.HexToHash("0x03").Bytes(), val) + helper.accTrie.Update(common.HexToHash("0x07").Bytes(), val) + + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x01"), val) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x02"), val) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x03"), val) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x04"), val) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x05"), val) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x06"), val) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x07"), val) + } + root, snap := helper.CommitAndGenerate() select { case <-snap.genPending: // Snapshot generation succeeded @@ -716,29 +649,20 @@ func TestGenerateWithMalformedSnapdata(t *testing.T) { if false { enableLogging() } - var ( - diskdb = memorydb.New() - triedb = trie.NewDatabase(diskdb) - ) - accTrie, _ := trie.New(common.Hash{}, triedb) + helper := newHelper() { acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} val, _ := rlp.EncodeToBytes(acc) - accTrie.Update(common.HexToHash("0x03").Bytes(), val) + helper.accTrie.Update(common.HexToHash("0x03").Bytes(), val) junk := make([]byte, 100) copy(junk, []byte{0xde, 0xad}) - rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x02"), junk) - rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x03"), junk) - rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x04"), junk) - rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x05"), junk) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x02"), junk) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x03"), junk) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x04"), junk) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x05"), junk) } - - root, _, _ := accTrie.Commit(nil) - t.Logf("root: %x", root) - triedb.Commit(root, false, nil) - - snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xdeadbeef"), root, nil) + root, snap := helper.CommitAndGenerate() select { case <-snap.genPending: // Snapshot generation succeeded @@ -752,7 +676,7 @@ func TestGenerateWithMalformedSnapdata(t *testing.T) { snap.genAbort <- stop <-stop // If we now inspect the snap db, there should exist no extraneous storage items - if data := rawdb.ReadStorageSnapshot(diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil { + if data := rawdb.ReadStorageSnapshot(helper.diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil { t.Fatalf("expected slot to be removed, got %v", string(data)) } } @@ -760,14 +684,14 @@ func TestGenerateWithMalformedSnapdata(t *testing.T) { func TestGenerateFromEmptySnap(t *testing.T) { //enableLogging() helper := newHelper() - stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) // Add 1K accounts to the trie for i := 0; i < 400; i++ { + stRoot := helper.makeStorageTrie(hashData([]byte(fmt.Sprintf("acc-%d", i))), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addTrieAccount(fmt.Sprintf("acc-%d", i), &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) } - root, snap := helper.Generate() - t.Logf("Root: %#x\n", root) // Root: 0x6f7af6d2e1a1bf2b84a3beb3f8b64388465fbc1e274ca5d5d3fc787ca78f59e4 + root, snap := helper.CommitAndGenerate() + t.Logf("Root: %#x\n", root) // Root: 0x2609234ce43f5e471202c87e017ffb4dfecdb3163cfcbaa55de04baa59cad42d select { case <-snap.genPending: @@ -794,12 +718,12 @@ func TestGenerateWithIncompleteStorage(t *testing.T) { helper := newHelper() stKeys := []string{"1", "2", "3", "4", "5", "6", "7", "8"} stVals := []string{"v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8"} - stRoot := helper.makeStorageTrie(stKeys, stVals) // We add 8 accounts, each one is missing exactly one of the storage slots. This means // we don't have to order the keys and figure out exactly which hash-key winds up // on the sensitive spots at the boundaries for i := 0; i < 8; i++ { accKey := fmt.Sprintf("acc-%d", i) + stRoot := helper.makeStorageTrie(hashData([]byte(accKey)), stKeys, stVals, true) helper.addAccount(accKey, &Account{Balance: big.NewInt(int64(i)), Root: stRoot, CodeHash: emptyCode.Bytes()}) var moddedKeys []string var moddedVals []string @@ -811,9 +735,8 @@ func TestGenerateWithIncompleteStorage(t *testing.T) { } helper.addSnapStorage(accKey, moddedKeys, moddedVals) } - - root, snap := helper.Generate() - t.Logf("Root: %#x\n", root) // Root: 0xca73f6f05ba4ca3024ef340ef3dfca8fdabc1b677ff13f5a9571fd49c16e67ff + root, snap := helper.CommitAndGenerate() + t.Logf("Root: %#x\n", root) // Root: 0x90cb912ad55795de8c08a41bfadb79109d4067fb2004e7bc17c942a3e551904d select { case <-snap.genPending: @@ -828,3 +751,126 @@ func TestGenerateWithIncompleteStorage(t *testing.T) { snap.genAbort <- stop <-stop } + +func incKey(key []byte) []byte { + for i := len(key) - 1; i >= 0; i-- { + key[i]++ + if key[i] != 0x0 { + break + } + } + return key +} + +func decKey(key []byte) []byte { + for i := len(key) - 1; i >= 0; i-- { + key[i]-- + if key[i] != 0xff { + break + } + } + return key +} + +func populateDangling(disk ethdb.KeyValueStore) { + populate := func(accountHash common.Hash, keys []string, vals []string) { + for i, key := range keys { + rawdb.WriteStorageSnapshot(disk, accountHash, hashData([]byte(key)), []byte(vals[i])) + } + } + // Dangling storages of the "first" account + populate(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Dangling storages of the "last" account + populate(common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Dangling storages around the account 1 + hash := decKey(hashData([]byte("acc-1")).Bytes()) + populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + hash = incKey(hashData([]byte("acc-1")).Bytes()) + populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Dangling storages around the account 2 + hash = decKey(hashData([]byte("acc-2")).Bytes()) + populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + hash = incKey(hashData([]byte("acc-2")).Bytes()) + populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Dangling storages around the account 3 + hash = decKey(hashData([]byte("acc-3")).Bytes()) + populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + hash = incKey(hashData([]byte("acc-3")).Bytes()) + populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Dangling storages of the random account + populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) +} + +// Tests that snapshot generation with dangling storages. Dangling storage means +// the storage data is existent while the corresponding account data is missing. +// +// This test will populate some dangling storages to see if they can be cleaned up. +func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) { + var helper = newHelper() + + stRoot := helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) + + helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + + helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + populateDangling(helper.diskdb) + + root, snap := helper.CommitAndGenerate() + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + + // Signal abortion to the generator and wait for it to tear down + stop := make(chan struct{}) + snap.genAbort <- stop + <-stop +} + +// Tests that snapshot generation with dangling storages. Dangling storage means +// the storage data is existent while the corresponding account data is missing. +// +// This test will populate some dangling storages to see if they can be cleaned up. +func TestGenerateBrokenSnapshotWithDanglingStorage(t *testing.T) { + var helper = newHelper() + + stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) + + helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) + + populateDangling(helper.diskdb) + + root, snap := helper.CommitAndGenerate() + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + + // Signal abortion to the generator and wait for it to tear down + stop := make(chan struct{}) + snap.genAbort <- stop + <-stop +} diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go index 13309117c9..e214317935 100644 --- a/core/state/snapshot/iterator.go +++ b/core/state/snapshot/iterator.go @@ -31,9 +31,9 @@ import ( "fmt" "sort" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/ethdb" - "github.com/ethereum/go-ethereum/common" ) // Iterator is an iterator to step over all the accounts or the specific diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go index 1011da355c..bf0e3acdc9 100644 --- a/core/state/snapshot/iterator_fast.go +++ b/core/state/snapshot/iterator_fast.go @@ -328,7 +328,7 @@ func (fi *fastIterator) Slot() []byte { } // Release iterates over all the remaining live layer iterators and releases each -// of thme individually. +// of them individually. func (fi *fastIterator) Release() { for _, it := range fi.iterators { it.it.Release() @@ -336,7 +336,7 @@ func (fi *fastIterator) Release() { fi.iterators = nil } -// Debug is a convencience helper during testing +// Debug is a convenience helper during testing func (fi *fastIterator) Debug() { for _, it := range fi.iterators { fmt.Printf("[p=%v v=%v] ", it.priority, it.it.Hash()[0]) diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index ebc9338d06..91f6368092 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -33,8 +33,8 @@ import ( "math/rand" "testing" - "github.com/tenderly/coreth/core/rawdb" "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/core/rawdb" ) // TestAccountIteratorBasics tests some simple single-layer(diff and disk) iteration diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index f6ec85e9af..ddea7274ab 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -33,12 +33,12 @@ import ( "time" "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/rlp" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) // journalGenerator is a disk layer entry containing the generator progress marker. diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index b2c8feed5f..d2f4bbdfab 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -36,12 +36,12 @@ import ( "time" "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/metrics" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" ) const ( @@ -134,6 +134,12 @@ type Snapshot interface { // Storage directly retrieves the storage data associated with a particular hash, // within a particular account. Storage(accountHash, storageHash common.Hash) ([]byte, error) + + // AccountIterator creates an account iterator over the account trie given by the provided root hash. + AccountIterator(seek common.Hash) AccountIterator + + // StorageIterator creates a storage iterator over the storage trie given by the provided root hash. + StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool) } // snapshot is the internal version of the snapshot data layer that supports some @@ -159,12 +165,6 @@ type snapshot interface { // Stale return whether this layer has become stale (was flattened across) or // if it's still live. Stale() bool - - // AccountIterator creates an account iterator over an arbitrary layer. - AccountIterator(seek common.Hash) AccountIterator - - // StorageIterator creates a storage iterator over an arbitrary layer. - StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool) } // Tree is an Ethereum state snapshot tree. It consists of one persistent base @@ -616,20 +616,19 @@ func diffToDisk(bottom *diffLayer) (*diskLayer, bool, error) { it := rawdb.IterateStorageSnapshots(base.diskdb, hash) for it.Next() { - if key := it.Key(); len(key) == 65 { // TODO(karalabe): Yuck, we should move this into the iterator - batch.Delete(key) - base.cache.Del(key[1:]) - snapshotFlushStorageItemMeter.Mark(1) - - // Ensure we don't delete too much data blindly (contract can be - // huge). It's ok to flush, the root will go missing in case of a - // crash and we'll detect and regenerate the snapshot. - if batch.ValueSize() > ethdb.IdealBatchSize { - if err := batch.Write(); err != nil { - log.Crit("Failed to write storage deletions", "err", err) - } - batch.Reset() + key := it.Key() + batch.Delete(key) + base.cache.Del(key[1:]) + snapshotFlushStorageItemMeter.Mark(1) + + // Ensure we don't delete too much data blindly (contract can be + // huge). It's ok to flush, the root will go missing in case of a + // crash and we'll detect and regenerate the snapshot. + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Crit("Failed to write storage deletions", "err", err) } + batch.Reset() } } it.Release() @@ -753,7 +752,6 @@ func (t *Tree) Rebuild(blockHash, root common.Hash) { if stats := layer.genStats; stats != nil { wiper = stats.wiping } - } // Layer should be inactive now, mark it as stale layer.lock.Lock() @@ -927,6 +925,19 @@ func (t *Tree) DiskStorageIterator(account common.Hash, seek common.Hash) Storag return it } +// NewDiskLayer creates a diskLayer for direct access to the contents of the on-disk +// snapshot. Does not perform any validation. +func NewDiskLayer(diskdb ethdb.KeyValueStore) Snapshot { + return &diskLayer{ + diskdb: diskdb, + created: time.Now(), + + // state sync uses iterators to access data, so this cache is not used. + // initializing it out of caution. + cache: fastcache.New(32 * 1024), + } +} + // NewTestTree creates a *Tree with a pre-populated diskLayer func NewTestTree(diskdb ethdb.KeyValueStore, blockHash, root common.Hash) *Tree { base := &diskLayer{ diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index 2d4177a164..66eaa917dc 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -8,7 +8,7 @@ // // Much love to the original authors for their work. // ********** -// Copyright 2019 The go-ethereum Authors +// Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -33,9 +33,9 @@ import ( "testing" "time" - "github.com/tenderly/coreth/core/rawdb" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/rlp" ) // randomHash generates a random blob of data and returns it as a hash. @@ -246,7 +246,7 @@ func TestPostFlattenBasicDataAccess(t *testing.T) { snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xffa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil) snaps.Update(common.HexToHash("0xb3"), common.HexToHash("0xffb3"), common.HexToHash("0xb2"), nil, setAccount("0xb3"), nil) - // checkExist verifies if an account exiss in a snapshot + // checkExist verifies if an account exists in a snapshot checkExist := func(layer Snapshot, key string) error { if data, _ := layer.Account(common.HexToHash(key)); data == nil { return fmt.Errorf("expected %x to exist, got nil", common.HexToHash(key)) diff --git a/core/state/snapshot/utils.go b/core/state/snapshot/utils.go new file mode 100644 index 0000000000..4857636014 --- /dev/null +++ b/core/state/snapshot/utils.go @@ -0,0 +1,81 @@ +// (c) 2022, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" +) + +// CheckDanglingStorage iterates the snap storage data, and verifies that all +// storage also has corresponding account data. +func CheckDanglingStorage(chaindb ethdb.KeyValueStore) error { + if err := checkDanglingDiskStorage(chaindb); err != nil { + log.Error("Database check error", "err", err) + return err + } + return nil +} + +// checkDanglingDiskStorage checks if there is any 'dangling' storage data in the +// disk-backed snapshot layer. +func checkDanglingDiskStorage(chaindb ethdb.KeyValueStore) error { + var ( + lastReport = time.Now() + start = time.Now() + lastKey []byte + it = rawdb.NewKeyLengthIterator(chaindb.NewIterator(rawdb.SnapshotStoragePrefix, nil), 1+2*common.HashLength) + ) + log.Info("Checking dangling snapshot disk storage") + + defer it.Release() + for it.Next() { + k := it.Key() + accKey := k[1:33] + if bytes.Equal(accKey, lastKey) { + // No need to look up for every slot + continue + } + lastKey = common.CopyBytes(accKey) + if time.Since(lastReport) > time.Second*8 { + log.Info("Iterating snap storage", "at", fmt.Sprintf("%#x", accKey), "elapsed", common.PrettyDuration(time.Since(start))) + lastReport = time.Now() + } + if data := rawdb.ReadAccountSnapshot(chaindb, common.BytesToHash(accKey)); len(data) == 0 { + log.Warn("Dangling storage - missing account", "account", fmt.Sprintf("%#x", accKey), "storagekey", fmt.Sprintf("%#x", k)) + return fmt.Errorf("dangling snapshot storage account %#x", accKey) + } + } + log.Info("Verified the snapshot disk storage", "time", common.PrettyDuration(time.Since(start)), "err", it.Error()) + return nil +} diff --git a/core/state/snapshot/wipe.go b/core/state/snapshot/wipe.go index 611d52ac69..ad51de0c8f 100644 --- a/core/state/snapshot/wipe.go +++ b/core/state/snapshot/wipe.go @@ -30,10 +30,10 @@ import ( "bytes" "time" - "github.com/tenderly/coreth/core/rawdb" - "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" ) // WipeSnapshot starts a goroutine to iterate over the entire key-value database diff --git a/core/state/snapshot/wipe_test.go b/core/state/snapshot/wipe_test.go index 7d4828f1b6..0ee64a6df8 100644 --- a/core/state/snapshot/wipe_test.go +++ b/core/state/snapshot/wipe_test.go @@ -30,9 +30,9 @@ import ( "math/rand" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/ethdb/memorydb" - "github.com/ethereum/go-ethereum/common" ) // Tests that given a database with random data content, all parts of a snapshot diff --git a/core/state/state_object.go b/core/state/state_object.go index 98b46bf016..28cae34c38 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -34,11 +34,12 @@ import ( "sync" "time" - "github.com/tenderly/coreth/core/types" - "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/metrics" + "github.com/tenderly/coreth/rlp" + "github.com/tenderly/coreth/trie" ) var emptyCodeHash = crypto.Keccak256(nil) @@ -60,7 +61,7 @@ func (s Storage) String() (str string) { } func (s Storage) Copy() Storage { - cpy := make(Storage) + cpy := make(Storage, len(s)) for key, value := range s { cpy[key] = value } @@ -169,7 +170,7 @@ func (s *stateObject) getTrie(db Database) Trie { if s.data.Root != emptyRoot && s.db.prefetcher != nil { // When the miner is creating the pending state, there is no // prefetcher - s.trie = s.db.prefetcher.trie(s.data.Root) + s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root) } if s.trie == nil { var err error @@ -310,7 +311,7 @@ func (s *stateObject) finalise(prefetch bool) { } } if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != emptyRoot { - s.db.prefetcher.prefetch(s.data.Root, slotsToPrefetch) + s.db.prefetcher.prefetch(s.addrHash, s.data.Root, slotsToPrefetch) } if len(s.dirtyStorage) > 0 { s.dirtyStorage = make(Storage) @@ -367,7 +368,7 @@ func (s *stateObject) updateTrie(db Database) Trie { usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure } if s.db.prefetcher != nil { - s.db.prefetcher.used(s.data.Root, usedStorage) + s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage) } if len(s.pendingStorage) > 0 { s.pendingStorage = make(Storage) @@ -390,23 +391,23 @@ func (s *stateObject) updateRoot(db Database) { // CommitTrie the storage trie of the object to db. // This updates the trie root. -func (s *stateObject) CommitTrie(db Database) (int, error) { +func (s *stateObject) CommitTrie(db Database) (*trie.NodeSet, error) { // If nothing changed, don't bother with hashing anything if s.updateTrie(db) == nil { - return 0, nil + return nil, nil } if s.dbErr != nil { - return 0, s.dbErr + return nil, s.dbErr } // Track the amount of time wasted on committing the storage trie if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now()) } - root, committed, err := s.trie.Commit(nil) + root, nodes, err := s.trie.Commit(false) if err == nil { s.data.Root = root } - return committed, err + return nodes, err } // AddBalance adds amount to s's balance. diff --git a/core/state/state_test.go b/core/state/state_test.go index 1feb535dec..1ba519229f 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -27,9 +27,9 @@ package state import ( + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/ethdb" - "github.com/ethereum/go-ethereum/common" ) type stateTest struct { diff --git a/core/state/statedb.go b/core/state/statedb.go index 03b461b3a8..fb2b950d8d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -34,15 +34,15 @@ import ( "sort" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state/snapshot" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/metrics" + "github.com/tenderly/coreth/rlp" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) type revision struct { @@ -72,11 +72,14 @@ func (n *proofList) Delete(key []byte) error { // * Contracts // * Accounts type StateDB struct { - db Database - prefetcher *triePrefetcher - originalRoot common.Hash // The pre-state root, before any changes were made - trie Trie - hasher crypto.KeccakState + db Database + prefetcher *triePrefetcher + trie Trie + hasher crypto.KeccakState + + // originalRoot is the pre-state root, before any changes were made. + // It will be updated when the Commit is called. + originalRoot common.Hash snap snapshot.Snapshot snapDestructs map[common.Hash]struct{} @@ -549,7 +552,7 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { } // Delete the account from the trie addr := obj.Address() - if err := s.trie.TryDelete(addr[:]); err != nil { + if err := s.trie.TryDeleteAccount(addr[:]); err != nil { s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err)) } } @@ -603,20 +606,16 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { // If snapshot unavailable or reading from it failed, load from the database if data == nil { start := time.Now() - enc, err := s.trie.TryGet(addr.Bytes()) + var err error + data, err = s.trie.TryGetAccount(addr.Bytes()) if metrics.EnabledExpensive { s.AccountReads += time.Since(start) } if err != nil { - s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %v", addr.Bytes(), err)) + s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %w", addr.Bytes(), err)) return nil } - if len(enc) == 0 { - return nil - } - data = new(types.StateAccount) - if err := rlp.DecodeBytes(enc, data); err != nil { - log.Error("Failed to decode state object", "addr", addr, "err", err) + if data == nil { return nil } } @@ -670,8 +669,8 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) // CreateAccount is called during the EVM CREATE operation. The situation might arise that // a contract does the following: // -// 1. sends funds to sha(account ++ (nonce + 1)) -// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1) +// 1. sends funds to sha(account ++ (nonce + 1)) +// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1) // // Carrying over the balance ensures that Ether doesn't disappear. func (s *StateDB) CreateAccount(addr common.Address) { @@ -717,6 +716,7 @@ func (s *StateDB) Copy() *StateDB { state := &StateDB{ db: s.db, trie: s.db.CopyTrie(s.trie), + originalRoot: s.originalRoot, stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), @@ -838,7 +838,7 @@ func (s *StateDB) GetRefund() uint64 { return s.refund } -// Finalise finalises the state by removing the s destructed objects and clears +// Finalise finalises the state by removing the destructed objects and clears // the journal as well as the refunds. Finalise, however, will not push any updates // into the tries just yet. Only IntermediateRoot or Commit will do that. func (s *StateDB) Finalise(deleteEmptyObjects bool) { @@ -860,7 +860,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // If state snapshotting is active, also mark the destruction there. // Note, we can't do this only at the end of a block because multiple // transactions within the same block might self destruct and then - // ressurrect an account; but the snapshotter needs both events. + // resurrect an account; but the snapshotter needs both events. if s.snap != nil { s.snapDestructs[obj.addrHash] = struct{}{} // We need to maintain account deletions explicitly (will remain set indefinitely) delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a ressurrect) @@ -878,7 +878,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure } if s.prefetcher != nil && len(addressesToPrefetch) > 0 { - s.prefetcher.prefetch(s.originalRoot, addressesToPrefetch) + s.prefetcher.prefetch(common.Hash{}, s.originalRoot, addressesToPrefetch) } // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund() @@ -908,7 +908,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Although naively it makes sense to retrieve the account trie and then do // the contract storage and account updates sequentially, that short circuits // the account prefetcher. Instead, let's process all the storage updates - // first, giving the account prefeches just a few more milliseconds of time + // first, giving the account prefetches just a few more milliseconds of time // to pull useful data from disk. for addr := range s.stateObjectsPending { if obj := s.stateObjects[addr]; !obj.deleted { @@ -919,7 +919,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // _untouched_. We can check with the prefetcher, if it can give us a trie // which has the same root, but also has some content loaded into it. if prefetcher != nil { - if trie := prefetcher.trie(s.originalRoot); trie != nil { + if trie := prefetcher.trie(common.Hash{}, s.originalRoot); trie != nil { s.trie = trie } } @@ -935,7 +935,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure } if prefetcher != nil { - prefetcher.used(s.originalRoot, usedAddrs) + prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs) } if len(s.stateObjectsPending) > 0 { s.stateObjectsPending = make(map[common.Address]struct{}) @@ -952,7 +952,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { func (s *StateDB) Prepare(thash common.Hash, ti int) { s.thash = thash s.txIndex = ti - s.accessList = newAccessList() } func (s *StateDB) clearJournalAndRefund() { @@ -960,22 +959,22 @@ func (s *StateDB) clearJournalAndRefund() { s.journal = newJournal() s.refund = 0 } - s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entires + s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entries } // Commit writes the state to the underlying in-memory trie database. -func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { - return s.commit(deleteEmptyObjects, nil, common.Hash{}, common.Hash{}) +func (s *StateDB) Commit(deleteEmptyObjects bool, referenceRoot bool) (common.Hash, error) { + return s.commit(deleteEmptyObjects, nil, common.Hash{}, common.Hash{}, referenceRoot) } // CommitWithSnap writes the state to the underlying in-memory trie database and // generates a snapshot layer for the newly committed state. -func (s *StateDB) CommitWithSnap(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash) (common.Hash, error) { - return s.commit(deleteEmptyObjects, snaps, blockHash, parentHash) +func (s *StateDB) CommitWithSnap(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash, referenceRoot bool) (common.Hash, error) { + return s.commit(deleteEmptyObjects, snaps, blockHash, parentHash, referenceRoot) } // Commit writes the state to the underlying in-memory trie database. -func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash) (common.Hash, error) { +func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash, referenceRoot bool) (common.Hash, error) { if s.dbErr != nil { return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr) } @@ -983,7 +982,11 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas s.IntermediateRoot(deleteEmptyObjects) // Commit objects to the trie, measuring the elapsed time - var storageCommitted int + var ( + accountTrieNodes int + storageTrieNodes int + nodes = trie.NewMergedNodeSet() + ) codeWriter := s.db.TrieDB().DiskDB().NewBatch() for addr := range s.stateObjectsDirty { if obj := s.stateObjects[addr]; !obj.deleted { @@ -993,11 +996,17 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas obj.dirtyCode = false } // Write any storage changes in the state object to its storage trie - committed, err := obj.CommitTrie(s.db) + set, err := obj.CommitTrie(s.db) if err != nil { return common.Hash{}, err } - storageCommitted += committed + // Merge the dirty nodes of storage trie into global set + if set != nil { + if err := nodes.Merge(set); err != nil { + return common.Hash{}, err + } + storageTrieNodes += set.Len() + } } } if len(s.stateObjectsDirty) > 0 { @@ -1013,21 +1022,17 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas if metrics.EnabledExpensive { start = time.Now() } - // The onleaf func is called _serially_, so we can reuse the same account - // for unmarshalling every time. - var account types.StateAccount - root, accountCommitted, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash) error { - if err := rlp.DecodeBytes(leaf, &account); err != nil { - return nil - } - if account.Root != emptyRoot { - s.db.TrieDB().Reference(account.Root, parent) - } - return nil - }) + root, set, err := s.trie.Commit(true) if err != nil { return common.Hash{}, err } + // Merge the dirty nodes of account trie into global set + if set != nil { + if err := nodes.Merge(set); err != nil { + return common.Hash{}, err + } + accountTrieNodes = set.Len() + } if metrics.EnabledExpensive { s.AccountCommits += time.Since(start) @@ -1035,8 +1040,8 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas storageUpdatedMeter.Mark(int64(s.StorageUpdated)) accountDeletedMeter.Mark(int64(s.AccountDeleted)) storageDeletedMeter.Mark(int64(s.StorageDeleted)) - accountCommittedMeter.Mark(int64(accountCommitted)) - storageCommittedMeter.Mark(int64(storageCommitted)) + accountTrieCommittedMeter.Mark(int64(accountTrieNodes)) + storageTriesCommittedMeter.Mark(int64(storageTrieNodes)) s.AccountUpdated, s.AccountDeleted = 0, 0 s.StorageUpdated, s.StorageDeleted = 0, 0 } @@ -1053,7 +1058,16 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas } s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil } - + if referenceRoot { + if err := s.db.TrieDB().UpdateAndReferenceRoot(nodes, root); err != nil { + return common.Hash{}, err + } + } else { + if err := s.db.TrieDB().Update(nodes); err != nil { + return common.Hash{}, err + } + } + s.originalRoot = root return root, err } @@ -1067,6 +1081,9 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas // // This method should only be called if Berlin/ApricotPhase2/2929+2930 is applicable at the current number. func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { + // Clear out any leftover from previous executions + s.accessList = newAccessList() + s.AddAddressToAccessList(sender) if dst != nil { s.AddAddressToAccessList(*dst) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index ab21f1f0b7..0ad72a3e35 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -39,11 +39,11 @@ import ( "testing" "testing/quick" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state/snapshot" "github.com/tenderly/coreth/core/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" ) // Tests that updating a state trie does not leak any database writes prior to @@ -114,7 +114,7 @@ func TestIntermediateLeaks(t *testing.T) { } // Commit and cross check the databases. - transRoot, err := transState.Commit(false) + transRoot, err := transState.Commit(false, false) if err != nil { t.Fatalf("failed to commit transition state: %v", err) } @@ -122,7 +122,7 @@ func TestIntermediateLeaks(t *testing.T) { t.Errorf("can not commit trie %v to persistent database", transRoot.Hex()) } - finalRoot, err := finalState.Commit(false) + finalRoot, err := finalState.Commit(false, false) if err != nil { t.Fatalf("failed to commit final state: %v", err) } @@ -485,7 +485,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { func TestTouchDelete(t *testing.T) { s := newStateTest() s.state.GetOrNewStateObject(common.Address{}) - root, _ := s.state.Commit(false) + root, _ := s.state.Commit(false, false) s.state, _ = NewWithSnapshot(root, s.state.db, s.state.snap) snapshot := s.state.Snapshot() @@ -558,7 +558,7 @@ func TestCopyCommitCopy(t *testing.T) { t.Fatalf("first copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } - copyOne.Commit(false) + copyOne.Commit(false, false) if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("first copy post-commit balance mismatch: have %v, want %v", balance, 42) } @@ -643,7 +643,7 @@ func TestCopyCopyCommitCopy(t *testing.T) { if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("second copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } - copyTwo.Commit(false) + copyTwo.Commit(false, false) if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { t.Fatalf("second copy post-commit balance mismatch: have %v, want %v", balance, 42) } @@ -687,7 +687,7 @@ func TestDeleteCreateRevert(t *testing.T) { addr := common.BytesToAddress([]byte("so")) state.SetBalance(addr, big.NewInt(1)) - root, _ := state.Commit(false) + root, _ := state.Commit(false, false) state, _ = NewWithSnapshot(root, state.db, state.snap) // Simulate self-destructing in one transaction, then create-reverting in another @@ -699,7 +699,7 @@ func TestDeleteCreateRevert(t *testing.T) { state.RevertToSnapshot(id) // Commit the entire state and make sure we don't crash and have the correct state - root, _ = state.Commit(true) + root, _ = state.Commit(true, false) state, _ = NewWithSnapshot(root, state.db, state.snap) if state.getStateObject(addr) != nil { @@ -711,7 +711,6 @@ func TestDeleteCreateRevert(t *testing.T) { // the Commit operation fails with an error // If we are missing trie nodes, we should not continue writing to the trie func TestMissingTrieNodes(t *testing.T) { - // Create an initial state with a few accounts memDb := rawdb.NewMemoryDatabase() db := NewDatabase(memDb) @@ -724,7 +723,7 @@ func TestMissingTrieNodes(t *testing.T) { a2 := common.BytesToAddress([]byte("another")) state.SetBalance(a2, big.NewInt(100)) state.SetCode(a2, []byte{1, 2, 4}) - root, _ = state.Commit(false) + root, _ = state.Commit(false, false) t.Logf("root: %x", root) // force-flush state.Database().TrieDB().Cap(0) @@ -748,7 +747,7 @@ func TestMissingTrieNodes(t *testing.T) { } // Modify the state state.SetBalance(addr, big.NewInt(2)) - root, err := state.Commit(false) + root, err := state.Commit(false, false) if err == nil { t.Fatalf("expected error, got root :%x", root) } @@ -784,7 +783,7 @@ func TestStateDBAccessList(t *testing.T) { t.Fatalf("expected %x to be in access list", address) } } - // Check that only the expected addresses are present in the acesslist + // Check that only the expected addresses are present in the access list for address := range state.accessList.addresses { if _, exist := addressMap[address]; !exist { t.Fatalf("extra address %x in access list", address) @@ -934,7 +933,7 @@ func TestMultiCoinOperations(t *testing.T) { assetID := common.Hash{2} s.state.GetOrNewStateObject(addr) - root, _ := s.state.Commit(false) + root, _ := s.state.Commit(false, false) s.state, _ = NewWithSnapshot(root, s.state.db, s.state.snap) s.state.AddBalance(addr, new(big.Int)) @@ -991,14 +990,14 @@ func TestMultiCoinSnapshot(t *testing.T) { assertBalances(10, 0, 0) // Commit and get the new root - root, _ = stateDB.Commit(false) + root, _ = stateDB.Commit(false, false) assertBalances(10, 0, 0) // Create a new state from the latest root, add a multicoin balance, and // commit it to the tree. stateDB, _ = New(root, sdb, snapTree) stateDB.AddBalanceMultiCoin(addr, assetID1, big.NewInt(10)) - root, _ = stateDB.Commit(false) + root, _ = stateDB.Commit(false, false) assertBalances(10, 10, 0) // Add more layers than the cap and ensure the balances and layers are correct @@ -1006,7 +1005,7 @@ func TestMultiCoinSnapshot(t *testing.T) { stateDB, _ = New(root, sdb, snapTree) stateDB.AddBalanceMultiCoin(addr, assetID1, big.NewInt(1)) stateDB.AddBalanceMultiCoin(addr, assetID2, big.NewInt(2)) - root, _ = stateDB.Commit(false) + root, _ = stateDB.Commit(false, false) } assertBalances(10, 266, 512) @@ -1015,7 +1014,7 @@ func TestMultiCoinSnapshot(t *testing.T) { stateDB, _ = New(root, sdb, snapTree) stateDB.AddBalance(addr, big.NewInt(1)) stateDB.AddBalanceMultiCoin(addr, assetID1, big.NewInt(1)) - _, _ = stateDB.Commit(false) + _, _ = stateDB.Commit(false, false) assertBalances(11, 267, 512) } @@ -1036,7 +1035,7 @@ func TestGenerateMultiCoinAccounts(t *testing.T) { t.Fatal(err) } stateDB.SetBalanceMultiCoin(addr, assetID, assetBalance) - root, err := stateDB.Commit(false) + root, err := stateDB.Commit(false, false) if err != nil { t.Fatal(err) } @@ -1073,3 +1072,43 @@ func TestGenerateMultiCoinAccounts(t *testing.T) { t.Fatalf("Expected asset balance: %v, found %v", assetBalance, actualAssetBalance) } } + +// Tests that account and storage tries are flushed in the correct order and that +// no data loss occurs. +func TestFlushOrderDataLoss(t *testing.T) { + // Create a state trie with many accounts and slots + var ( + memdb = rawdb.NewMemoryDatabase() + statedb = NewDatabase(memdb) + state, _ = New(common.Hash{}, statedb, nil) + ) + for a := byte(0); a < 10; a++ { + state.CreateAccount(common.Address{a}) + for s := byte(0); s < 10; s++ { + state.SetState(common.Address{a}, common.Hash{a, s}, common.Hash{a, s}) + } + } + root, err := state.Commit(false, false) + if err != nil { + t.Fatalf("failed to commit state trie: %v", err) + } + statedb.TrieDB().Reference(root, common.Hash{}) + if err := statedb.TrieDB().Cap(1024); err != nil { + t.Fatalf("failed to cap trie dirty cache: %v", err) + } + if err := statedb.TrieDB().Commit(root, false, nil); err != nil { + t.Fatalf("failed to commit state trie: %v", err) + } + // Reopen the state trie from flushed disk and verify it + state, err = New(root, NewDatabase(memdb), nil) + if err != nil { + t.Fatalf("failed to reopen state trie: %v", err) + } + for a := byte(0); a < 10; a++ { + for s := byte(0); s < 10; s++ { + if have := state.GetState(common.Address{a}, common.Hash{a, s}); have != (common.Hash{a, s}) { + t.Errorf("account %d: slot %d: state mismatch: have %x, want %x", a, s, have, common.Hash{a, s}) + } + } + } +} diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 4957c0d15f..18e987fb11 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -29,13 +29,13 @@ package state import ( "sync" - "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/tenderly/coreth/metrics" ) var ( - // triePrefetchMetricsPrefix is the prefix under which to publis the metrics. + // triePrefetchMetricsPrefix is the prefix under which to publish the metrics. triePrefetchMetricsPrefix = "trie/prefetch/" ) @@ -45,10 +45,10 @@ var ( // // Note, the prefetcher's API is not thread safe. type triePrefetcher struct { - db Database // Database to fetch trie nodes through - root common.Hash // Root hash of theaccount trie for metrics - fetches map[common.Hash]Trie // Partially or fully fetcher tries - fetchers map[common.Hash]*subfetcher // Subfetchers for each trie + db Database // Database to fetch trie nodes through + root common.Hash // Root hash of the account trie for metrics + fetches map[string]Trie // Partially or fully fetcher tries + fetchers map[string]*subfetcher // Subfetchers for each trie deliveryMissMeter metrics.Meter accountLoadMeter metrics.Meter @@ -61,13 +61,12 @@ type triePrefetcher struct { storageWasteMeter metrics.Meter } -// newTriePrefetcher func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher { prefix := triePrefetchMetricsPrefix + namespace p := &triePrefetcher{ db: db, root: root, - fetchers: make(map[common.Hash]*subfetcher), // Active prefetchers use the fetchers map + fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil), accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil), @@ -122,7 +121,7 @@ func (p *triePrefetcher) copy() *triePrefetcher { copy := &triePrefetcher{ db: p.db, root: p.root, - fetches: make(map[common.Hash]Trie), // Active prefetchers use the fetches map + fetches: make(map[string]Trie), // Active prefetchers use the fetches map deliveryMissMeter: p.deliveryMissMeter, accountLoadMeter: p.accountLoadMeter, @@ -142,33 +141,35 @@ func (p *triePrefetcher) copy() *triePrefetcher { return copy } // Otherwise we're copying an active fetcher, retrieve the current states - for root, fetcher := range p.fetchers { - copy.fetches[root] = fetcher.peek() + for id, fetcher := range p.fetchers { + copy.fetches[id] = fetcher.peek() } return copy } // prefetch schedules a batch of trie items to prefetch. -func (p *triePrefetcher) prefetch(root common.Hash, keys [][]byte) { +func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, keys [][]byte) { // If the prefetcher is an inactive one, bail out if p.fetches != nil { return } // Active fetcher, schedule the retrievals - fetcher := p.fetchers[root] + id := p.trieID(owner, root) + fetcher := p.fetchers[id] if fetcher == nil { - fetcher = newSubfetcher(p.db, root) - p.fetchers[root] = fetcher + fetcher = newSubfetcher(p.db, owner, root) + p.fetchers[id] = fetcher } fetcher.schedule(keys) } // trie returns the trie matching the root hash, or nil if the prefetcher doesn't // have it. -func (p *triePrefetcher) trie(root common.Hash) Trie { +func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie { // If the prefetcher is inactive, return from existing deep copies + id := p.trieID(owner, root) if p.fetches != nil { - trie := p.fetches[root] + trie := p.fetches[id] if trie == nil { p.deliveryMissMeter.Mark(1) return nil @@ -176,7 +177,7 @@ func (p *triePrefetcher) trie(root common.Hash) Trie { return p.db.CopyTrie(trie) } // Otherwise the prefetcher is active, bail if no trie was prefetched for this root - fetcher := p.fetchers[root] + fetcher := p.fetchers[id] if fetcher == nil { p.deliveryMissMeter.Mark(1) return nil @@ -195,27 +196,33 @@ func (p *triePrefetcher) trie(root common.Hash) Trie { // used marks a batch of state items used to allow creating statistics as to // how useful or wasteful the prefetcher is. -func (p *triePrefetcher) used(root common.Hash, used [][]byte) { - if fetcher := p.fetchers[root]; fetcher != nil { +func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) { + if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil { fetcher.used = used } } +// trieID returns an unique trie identifier consists the trie owner and root hash. +func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string { + return string(append(owner.Bytes(), root.Bytes()...)) +} + // subfetcher is a trie fetcher goroutine responsible for pulling entries for a // single trie. It is spawned when a new root is encountered and lives until the // main prefetcher is paused and either all requested items are processed or if // the trie being worked on is retrieved from the prefetcher. type subfetcher struct { - db Database // Database to load trie nodes through - root common.Hash // Root hash of the trie to prefetch - trie Trie // Trie being populated with nodes + db Database // Database to load trie nodes through + owner common.Hash // Owner of the trie, usually account hash + root common.Hash // Root hash of the trie to prefetch + trie Trie // Trie being populated with nodes tasks [][]byte // Items queued up for retrieval lock sync.Mutex // Lock protecting the task queue wake chan struct{} // Wake channel if a new task is scheduled stop chan struct{} // Channel to interrupt processing - term chan struct{} // Channel to signal iterruption + term chan struct{} // Channel to signal interruption copy chan chan Trie // Channel to request a copy of the current trie seen map[string]struct{} // Tracks the entries already loaded @@ -225,15 +232,16 @@ type subfetcher struct { // newSubfetcher creates a goroutine to prefetch state items belonging to a // particular root hash. -func newSubfetcher(db Database, root common.Hash) *subfetcher { +func newSubfetcher(db Database, owner common.Hash, root common.Hash) *subfetcher { sf := &subfetcher{ - db: db, - root: root, - wake: make(chan struct{}, 1), - stop: make(chan struct{}), - term: make(chan struct{}), - copy: make(chan chan Trie), - seen: make(map[string]struct{}), + db: db, + owner: owner, + root: root, + wake: make(chan struct{}, 1), + stop: make(chan struct{}), + term: make(chan struct{}), + copy: make(chan chan Trie), + seen: make(map[string]struct{}), } go sf.loop() return sf @@ -289,12 +297,21 @@ func (sf *subfetcher) loop() { defer close(sf.term) // Start by opening the trie and stop processing if it fails - trie, err := sf.db.OpenTrie(sf.root) - if err != nil { - log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) - return + if sf.owner == (common.Hash{}) { + trie, err := sf.db.OpenTrie(sf.root) + if err != nil { + log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) + return + } + sf.trie = trie + } else { + trie, err := sf.db.OpenStorageTrie(sf.owner, sf.root) + if err != nil { + log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) + return + } + sf.trie = trie } - sf.trie = trie // Trie opened successfully, keep prefetching items for { @@ -325,7 +342,15 @@ func (sf *subfetcher) loop() { if _, ok := sf.seen[string(task)]; ok { sf.dups++ } else { - sf.trie.TryGet(task) + var err error + if len(task) == len(common.Address{}) { + _, err = sf.trie.TryGetAccount(task) + } else { + _, err = sf.trie.TryGet(task) + } + if err != nil { + log.Error("Trie prefetcher failed fetching", "root", sf.root, "err", err) + } sf.seen[string(task)] = struct{}{} } } diff --git a/core/state/trie_prefetcher_test.go b/core/state/trie_prefetcher_test.go index 503911a84b..fcb58a0e44 100644 --- a/core/state/trie_prefetcher_test.go +++ b/core/state/trie_prefetcher_test.go @@ -31,8 +31,8 @@ import ( "testing" "time" - "github.com/tenderly/coreth/core/rawdb" "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/core/rawdb" ) func filledStateDB() *StateDB { @@ -57,20 +57,20 @@ func TestCopyAndClose(t *testing.T) { db := filledStateDB() prefetcher := newTriePrefetcher(db.db, db.originalRoot, "") skey := common.HexToHash("aaa") - prefetcher.prefetch(db.originalRoot, [][]byte{skey.Bytes()}) - prefetcher.prefetch(db.originalRoot, [][]byte{skey.Bytes()}) + prefetcher.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()}) + prefetcher.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()}) time.Sleep(1 * time.Second) - a := prefetcher.trie(db.originalRoot) - prefetcher.prefetch(db.originalRoot, [][]byte{skey.Bytes()}) - b := prefetcher.trie(db.originalRoot) + a := prefetcher.trie(common.Hash{}, db.originalRoot) + prefetcher.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()}) + b := prefetcher.trie(common.Hash{}, db.originalRoot) cpy := prefetcher.copy() - cpy.prefetch(db.originalRoot, [][]byte{skey.Bytes()}) - cpy.prefetch(db.originalRoot, [][]byte{skey.Bytes()}) - c := cpy.trie(db.originalRoot) + cpy.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()}) + cpy.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()}) + c := cpy.trie(common.Hash{}, db.originalRoot) prefetcher.close() cpy2 := cpy.copy() - cpy2.prefetch(db.originalRoot, [][]byte{skey.Bytes()}) - d := cpy2.trie(db.originalRoot) + cpy2.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()}) + d := cpy2.trie(common.Hash{}, db.originalRoot) cpy.close() cpy2.close() if a.Hash() != b.Hash() || a.Hash() != c.Hash() || a.Hash() != d.Hash() { @@ -82,10 +82,10 @@ func TestUseAfterClose(t *testing.T) { db := filledStateDB() prefetcher := newTriePrefetcher(db.db, db.originalRoot, "") skey := common.HexToHash("aaa") - prefetcher.prefetch(db.originalRoot, [][]byte{skey.Bytes()}) - a := prefetcher.trie(db.originalRoot) + prefetcher.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()}) + a := prefetcher.trie(common.Hash{}, db.originalRoot) prefetcher.close() - b := prefetcher.trie(db.originalRoot) + b := prefetcher.trie(common.Hash{}, db.originalRoot) if a == nil { t.Fatal("Prefetching before close should not return nil") } @@ -98,13 +98,13 @@ func TestCopyClose(t *testing.T) { db := filledStateDB() prefetcher := newTriePrefetcher(db.db, db.originalRoot, "") skey := common.HexToHash("aaa") - prefetcher.prefetch(db.originalRoot, [][]byte{skey.Bytes()}) + prefetcher.prefetch(common.Hash{}, db.originalRoot, [][]byte{skey.Bytes()}) cpy := prefetcher.copy() - a := prefetcher.trie(db.originalRoot) - b := cpy.trie(db.originalRoot) + a := prefetcher.trie(common.Hash{}, db.originalRoot) + b := cpy.trie(common.Hash{}, db.originalRoot) prefetcher.close() - c := prefetcher.trie(db.originalRoot) - d := cpy.trie(db.originalRoot) + c := prefetcher.trie(common.Hash{}, db.originalRoot) + d := cpy.trie(common.Hash{}, db.originalRoot) if a == nil { t.Fatal("Prefetching before close should not return nil") } diff --git a/core/state_manager.go b/core/state_manager.go index d2903abd3a..b66d8181de 100644 --- a/core/state_manager.go +++ b/core/state_manager.go @@ -31,9 +31,9 @@ import ( "math/rand" "time" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" - "github.com/ethereum/go-ethereum/common" ) func init() { @@ -58,14 +58,13 @@ const ( ) type TrieWriter interface { - InsertTrie(block *types.Block) error // Insert reference to trie [root] + InsertTrie(block *types.Block) error // Handle inserted trie reference of [root] AcceptTrie(block *types.Block) error // Mark [root] as part of an accepted block RejectTrie(block *types.Block) error // Notify TrieWriter that the block containing [root] has been rejected Shutdown() error } type TrieDB interface { - Reference(child common.Hash, parent common.Hash) Dereference(root common.Hash) Commit(root common.Hash, report bool, callback func(common.Hash)) error Size() (common.StorageSize, common.StorageSize) @@ -98,7 +97,6 @@ type noPruningTrieWriter struct { func (np *noPruningTrieWriter) InsertTrie(block *types.Block) error { // We don't attempt to [Cap] here because we should never have // a significant amount of [TrieDB.Dirties] (we commit each block). - np.TrieDB.Reference(block.Root(), common.Hash{}) return nil } @@ -127,8 +125,6 @@ type cappedMemoryTrieWriter struct { } func (cm *cappedMemoryTrieWriter) InsertTrie(block *types.Block) error { - cm.TrieDB.Reference(block.Root(), common.Hash{}) - // The use of [Cap] in [InsertTrie] prevents exceeding the configured memory // limit (and OOM) in case there is a large backlog of processing (unaccepted) blocks. nodes, imgs := cm.TrieDB.Size() diff --git a/core/state_manager_test.go b/core/state_manager_test.go index 5ce7b237ac..92d722fdd5 100644 --- a/core/state_manager_test.go +++ b/core/state_manager_test.go @@ -14,14 +14,10 @@ import ( ) type MockTrieDB struct { - LastReference common.Hash LastDereference common.Hash LastCommit common.Hash } -func (t *MockTrieDB) Reference(child common.Hash, parent common.Hash) { - t.LastReference = child -} func (t *MockTrieDB) Dereference(root common.Hash) { t.LastDereference = root } @@ -52,13 +48,10 @@ func TestCappedMemoryTrieWriter(t *testing.T) { ) assert.NoError(w.InsertTrie(block)) - assert.Equal(block.Root(), m.LastReference, "should have referenced block on insert") assert.Equal(common.Hash{}, m.LastDereference, "should not have dereferenced block on insert") assert.Equal(common.Hash{}, m.LastCommit, "should not have committed block on insert") - m.LastReference = common.Hash{} w.AcceptTrie(block) - assert.Equal(common.Hash{}, m.LastReference, "should not have referenced block on accept") if i <= tipBufferSize { assert.Equal(common.Hash{}, m.LastDereference, "should not have dereferenced block on accept") } else { @@ -73,7 +66,6 @@ func TestCappedMemoryTrieWriter(t *testing.T) { } w.RejectTrie(block) - assert.Equal(common.Hash{}, m.LastReference, "should not have referenced block on reject") assert.Equal(block.Root(), m.LastDereference, "should have dereferenced block on reject") assert.Equal(common.Hash{}, m.LastCommit, "should not have committed block on reject") m.LastDereference = common.Hash{} @@ -95,19 +87,15 @@ func TestNoPruningTrieWriter(t *testing.T) { ) assert.NoError(w.InsertTrie(block)) - assert.Equal(block.Root(), m.LastReference, "should have referenced block on insert") assert.Equal(common.Hash{}, m.LastDereference, "should not have dereferenced block on insert") assert.Equal(common.Hash{}, m.LastCommit, "should not have committed block on insert") - m.LastReference = common.Hash{} w.AcceptTrie(block) - assert.Equal(common.Hash{}, m.LastReference, "should not have referenced block on accept") assert.Equal(common.Hash{}, m.LastDereference, "should not have dereferenced block on accept") assert.Equal(block.Root(), m.LastCommit, "should have committed block on accept") m.LastCommit = common.Hash{} w.RejectTrie(block) - assert.Equal(common.Hash{}, m.LastReference, "should not have referenced block on reject") assert.Equal(block.Root(), m.LastDereference, "should have dereferenced block on reject") assert.Equal(common.Hash{}, m.LastCommit, "should not have committed block on reject") m.LastDereference = common.Hash{} diff --git a/core/state_processor.go b/core/state_processor.go index 6756eb926b..7afb5411ca 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -30,14 +30,14 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/tenderly/coreth/consensus" "github.com/tenderly/coreth/consensus/misc" "github.com/tenderly/coreth/core/state" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -94,7 +94,7 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } statedb.Prepare(tx.Hash(), i) - receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) + receipt, err := applyTransaction(msg, p.config, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) if err != nil { return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } @@ -109,7 +109,7 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state return receipts, allLogs, *usedGas, nil } -func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { +func applyTransaction(msg types.Message, config *params.ChainConfig, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb) @@ -166,5 +166,5 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo // Create a new context to be used in the EVM environment blockContext := NewEVMBlockContext(header, bc, author) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) - return applyTransaction(msg, config, bc, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) + return applyTransaction(msg, config, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index cfd5eded74..e553cf64bc 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -30,15 +30,15 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/tenderly/coreth/consensus" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/trie" + "github.com/tenderly/coreth/trie" "golang.org/x/crypto/sha3" ) @@ -48,24 +48,7 @@ import ( // contain invalid transactions func TestStateProcessorErrors(t *testing.T) { var ( - config = ¶ms.ChainConfig{ - ChainID: big.NewInt(1), - HomesteadBlock: big.NewInt(0), - EIP150Block: big.NewInt(0), - EIP150Hash: common.Hash{}, - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - ApricotPhase1BlockTimestamp: big.NewInt(0), - ApricotPhase2BlockTimestamp: big.NewInt(0), - ApricotPhase3BlockTimestamp: big.NewInt(0), - ApricotPhase4BlockTimestamp: big.NewInt(0), - ApricotPhase5BlockTimestamp: big.NewInt(0), - } + config = params.TestChainConfig signer = types.LatestSigner(config) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") ) diff --git a/core/state_transition.go b/core/state_transition.go index b5537c877c..820ae2dab0 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -34,11 +34,11 @@ import ( "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/params" "github.com/tenderly/coreth/vmerrs" - "github.com/ethereum/go-ethereum/common" ) var emptyCodeHash = crypto.Keccak256Hash(nil) @@ -303,16 +303,23 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if err := st.preCheck(); err != nil { return nil, err } - msg := st.msg - sender := vm.AccountRef(msg.From()) - homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber) - istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber) - apricotPhase1 := st.evm.ChainConfig().IsApricotPhase1(st.evm.Context.Time) - contractCreation := msg.To() == nil + if st.evm.Config.Debug { + st.evm.Config.Tracer.CaptureTxStart(st.initialGas) + defer func() { + st.evm.Config.Tracer.CaptureTxEnd(st.gas) + }() + } + + var ( + msg = st.msg + sender = vm.AccountRef(msg.From()) + rules = st.evm.ChainConfig().AvalancheRules(st.evm.Context.BlockNumber, st.evm.Context.Time) + contractCreation = msg.To() == nil + ) // Check clauses 4-5, subtract intrinsic gas if everything is correct - gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, istanbul) + gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul) if err != nil { return nil, err } @@ -327,7 +334,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } // Set up the initial access list. - if rules := st.evm.ChainConfig().AvalancheRules(st.evm.Context.BlockNumber, st.evm.Context.Time); rules.IsApricotPhase2 { + if rules.IsApricotPhase2 { st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) } var ( @@ -341,14 +348,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) } - if errors.Is(vmerr, vmerrs.ErrToAddrProhibitedSoft) { + if errors.Is(vmerr, vmerrs.ErrToAddrProhibitedSoft) { // Only invalidate soft error here return &ExecutionResult{ UsedGas: st.gasUsed(), Err: vmerr, ReturnData: ret, }, vmerr } - st.refundGas(apricotPhase1) + st.refundGas(rules.IsApricotPhase1) st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) return &ExecutionResult{ diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go new file mode 100644 index 0000000000..06a28e3a07 --- /dev/null +++ b/core/stateful_precompile_test.go @@ -0,0 +1,43 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package core + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/precompile" +) + +var ( + _ precompile.BlockContext = &mockBlockContext{} + _ precompile.PrecompileAccessibleState = &mockAccessibleState{} +) + +type mockBlockContext struct { + blockNumber *big.Int + timestamp uint64 +} + +func (mb *mockBlockContext) Number() *big.Int { return mb.blockNumber } +func (mb *mockBlockContext) Timestamp() *big.Int { return new(big.Int).SetUint64(mb.timestamp) } + +type mockAccessibleState struct { + state *state.StateDB + blockContext *mockBlockContext + + // NativeAssetCall return values + ret []byte + remainingGas uint64 + err error +} + +func (m *mockAccessibleState) GetStateDB() precompile.StateDB { return m.state } + +func (m *mockAccessibleState) GetBlockContext() precompile.BlockContext { return m.blockContext } + +func (m *mockAccessibleState) NativeAssetCall(common.Address, []byte, uint64, uint64, bool) ([]byte, uint64, error) { + return m.ret, m.remainingGas, m.err +} diff --git a/core/test_blockchain.go b/core/test_blockchain.go index dcdf6010d5..91b7706733 100644 --- a/core/test_blockchain.go +++ b/core/test_blockchain.go @@ -9,14 +9,14 @@ import ( "strings" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" ) type ChainTest struct { @@ -88,7 +88,7 @@ func copyMemDB(db ethdb.Database) (ethdb.Database, error) { } // checkBlockChainState creates a new BlockChain instance and checks that exporting each block from -// genesis to last acceptd from the original instance yields the same last accepted block and state +// genesis to last accepted from the original instance yields the same last accepted block and state // root. // Additionally, create another BlockChain instance from [originalDB] to ensure that BlockChain is // persisted correctly through a restart. diff --git a/core/tx_journal.go b/core/tx_journal.go index 3628d22391..4e06253691 100644 --- a/core/tx_journal.go +++ b/core/tx_journal.go @@ -29,12 +29,13 @@ package core import ( "errors" "io" + "io/fs" "os" - "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" + "github.com/tenderly/coreth/core/types" ) // errNoActiveJournal is returned if a transaction is attempted to be inserted @@ -67,12 +68,12 @@ func newTxJournal(path string) *txJournal { // load parses a transaction journal dump from disk, loading its contents into // the specified pool. func (journal *txJournal) load(add func([]*types.Transaction) []error) error { - // Skip the parsing if the journal file doesn't exist at all - if _, err := os.Stat(journal.path); os.IsNotExist(err) { - return nil - } // Open the journal for loading any past transactions input, err := os.Open(journal.path) + if errors.Is(err, fs.ErrNotExist) { + // Skip the parsing if the journal file doesn't exist at all + return nil + } if err != nil { return err } diff --git a/core/tx_list.go b/core/tx_list.go index be1cf88ef8..fce544a931 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -35,8 +35,8 @@ import ( "sync/atomic" "time" - "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/core/types" ) // nonceHeap is a heap.Interface implementation over 64bit unsigned integers for diff --git a/core/tx_list_test.go b/core/tx_list_test.go index 1022432dfa..fa528022d3 100644 --- a/core/tx_list_test.go +++ b/core/tx_list_test.go @@ -31,8 +31,8 @@ import ( "math/rand" "testing" - "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/tenderly/coreth/core/types" ) // Tests that transactions can be added to strict lists and list contents and diff --git a/core/tx_noncer.go b/core/tx_noncer.go index dfb9b18c8f..04f6493d25 100644 --- a/core/tx_noncer.go +++ b/core/tx_noncer.go @@ -29,8 +29,8 @@ package core import ( "sync" - "github.com/tenderly/coreth/core/state" "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/core/state" ) // txNoncer is a tiny virtual state database to manage the executable nonces of diff --git a/core/tx_pool.go b/core/tx_pool.go index a5a4355d33..3bda676b7d 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -36,15 +36,15 @@ import ( "sync/atomic" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/prque" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core/state" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/metrics" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/prque" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/log" ) const ( @@ -608,6 +608,16 @@ func (pool *TxPool) Pending(enforceTips bool) map[common.Address]types.Transacti return pending } +// PendingSize returns the number of pending txs in the tx pool. +func (pool *TxPool) PendingSize() int { + pending := pool.Pending(true) + count := 0 + for _, txs := range pending { + count += len(txs) + } + return count +} + // Locals retrieves the accounts currently considered local by the pool. func (pool *TxPool) Locals() []common.Address { pool.mu.Lock() @@ -1559,7 +1569,7 @@ func (pool *TxPool) truncatePending() { pendingRateLimitMeter.Mark(int64(pendingBeforeCap - pending)) } -// truncateQueue drops the oldes transactions in the queue if the pool is above the global queue limit. +// truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit. func (pool *TxPool) truncateQueue() { queued := uint64(0) for _, list := range pool.queue { @@ -1576,7 +1586,7 @@ func (pool *TxPool) truncateQueue() { addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]}) } } - sort.Sort(addresses) + sort.Sort(sort.Reverse(addresses)) // Drop transactions until the total is below the limit or only locals remain for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; { @@ -1754,10 +1764,6 @@ func (as *accountSet) contains(addr common.Address) bool { return exist } -func (as *accountSet) empty() bool { - return len(as.accounts) == 0 -} - // containsTx checks if the sender of a given tx is within the set. If the sender // cannot be derived, this method returns false. func (as *accountSet) containsTx(tx *types.Transaction) bool { diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 453825b139..8c819fb05d 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -30,7 +30,6 @@ import ( "crypto/ecdsa" "errors" "fmt" - "io/ioutil" "math/big" "math/rand" "os" @@ -39,14 +38,14 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/params" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/event" ) var ( @@ -713,7 +712,6 @@ func TestTransactionPostponing(t *testing.T) { // Add a batch consecutive pending transactions for validation txs := []*types.Transaction{} for i, key := range keys { - for j := 0; j < 100; j++ { var tx *types.Transaction if (i+j)%2 == 0 { @@ -2288,7 +2286,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { t.Parallel() // Create a temporary file for the journal - file, err := ioutil.TempFile("", "") + file, err := os.CreateTemp("", "") if err != nil { t.Fatalf("failed to create temporary journal: %v", err) } diff --git a/core/types/access_list_tx.go b/core/types/access_list_tx.go index 5e1aaec7e1..51a6bd0100 100644 --- a/core/types/access_list_tx.go +++ b/core/types/access_list_tx.go @@ -8,7 +8,7 @@ // // Much love to the original authors for their work. // ********** -// Copyright 2020 The go-ethereum Authors +// Copyright 2021 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -32,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/common" ) -//go:generate gencodec -type AccessTuple -out gen_access_tuple.go +//go:generate go run github.com/fjl/gencodec -type AccessTuple -out gen_access_tuple.go // AccessList is an EIP-2930 access list. type AccessList []AccessTuple diff --git a/core/types/block.go b/core/types/block.go index 7224122506..5748ba794e 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -38,7 +38,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) var ( @@ -75,7 +75,8 @@ func (n *BlockNonce) UnmarshalText(input []byte) error { return hexutil.UnmarshalFixedText("BlockNonce", input, n[:]) } -//go:generate gencodec -type Header -field-override headerMarshaling -out gen_header_json.go +//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go +//go:generate go run github.com/tenderly/coreth/rlp/rlpgen -type Header -out gen_header_rlp.go // Header represents a block header in the Ethereum blockchain. type Header struct { @@ -109,6 +110,9 @@ type Header struct { // BlockGasCost was added by Apricot Phase 4 and is ignored in legacy // headers. BlockGasCost *big.Int `json:"blockGasCost" rlp:"optional"` + + // ExtraStateRoot root was added by Cortina and is ignored in legacy headers. + ExtraStateRoot common.Hash `json:"extraStateRoot" rlp:"optional"` } // field type overrides for gencodec @@ -374,7 +378,7 @@ func (b *Block) Header() *Header { return CopyHeader(b.header) } func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles, b.version, b.extdata} } // Size returns the true RLP encoded storage size of the block, either by encoding -// and returning it, or returning a previsouly cached value. +// and returning it, or returning a previously cached value. func (b *Block) Size() common.StorageSize { if size := b.size.Load(); size != nil { return size.(common.StorageSize) diff --git a/core/types/block_test.go b/core/types/block_test.go index 034db8f7bc..1be0d747de 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -33,11 +33,11 @@ import ( "reflect" "testing" - "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rlp" "golang.org/x/crypto/sha3" ) diff --git a/core/types/bloom9.go b/core/types/bloom9.go index 872fa85fa2..aa172a0b1b 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -164,7 +164,7 @@ func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byt return i1, v1, i2, v2, i3, v3 } -// BloomLookup is a convenience-method to check presence int he bloom filter +// BloomLookup is a convenience-method to check presence in the bloom filter func BloomLookup(bin Bloom, topic bytesBacked) bool { return bin.Test(topic.Bytes()) } diff --git a/core/types/bloom9_test.go b/core/types/bloom9_test.go index 85edb990a4..e758b9cd45 100644 --- a/core/types/bloom9_test.go +++ b/core/types/bloom9_test.go @@ -102,7 +102,6 @@ func BenchmarkBloom9Lookup(b *testing.B) { } func BenchmarkCreateBloom(b *testing.B) { - var txs = Transactions{ NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil), NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil), diff --git a/core/types/gen_account_rlp.go b/core/types/gen_account_rlp.go new file mode 100644 index 0000000000..c49d23a9ea --- /dev/null +++ b/core/types/gen_account_rlp.go @@ -0,0 +1,31 @@ +// Code generated by rlpgen. DO NOT EDIT. + +//go:build !norlpgen +// +build !norlpgen + +package types + +import ( + "io" + + "github.com/tenderly/coreth/rlp" +) + +func (obj *StateAccount) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.WriteUint64(obj.Nonce) + if obj.Balance == nil { + w.Write(rlp.EmptyString) + } else { + if obj.Balance.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.Balance) + } + w.WriteBytes(obj.Root[:]) + w.WriteBytes(obj.CodeHash) + w.WriteBool(obj.IsMultiCoin) + w.ListEnd(_tmp0) + return w.Flush() +} diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index 26f934c5f4..7036c7b51c 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -35,6 +35,7 @@ func (h Header) MarshalJSON() ([]byte, error) { BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` ExtDataGasUsed *hexutil.Big `json:"extDataGasUsed" rlp:"optional"` BlockGasCost *hexutil.Big `json:"blockGasCost" rlp:"optional"` + ExtraStateRoot common.Hash `json:"extraStateRoot" rlp:"optional"` Hash common.Hash `json:"hash"` } var enc Header @@ -57,6 +58,7 @@ func (h Header) MarshalJSON() ([]byte, error) { enc.BaseFee = (*hexutil.Big)(h.BaseFee) enc.ExtDataGasUsed = (*hexutil.Big)(h.ExtDataGasUsed) enc.BlockGasCost = (*hexutil.Big)(h.BlockGasCost) + enc.ExtraStateRoot = h.ExtraStateRoot enc.Hash = h.Hash() return json.Marshal(&enc) } @@ -83,6 +85,7 @@ func (h *Header) UnmarshalJSON(input []byte) error { BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` ExtDataGasUsed *hexutil.Big `json:"extDataGasUsed" rlp:"optional"` BlockGasCost *hexutil.Big `json:"blockGasCost" rlp:"optional"` + ExtraStateRoot *common.Hash `json:"extraStateRoot" rlp:"optional"` } var dec Header if err := json.Unmarshal(input, &dec); err != nil { @@ -159,5 +162,8 @@ func (h *Header) UnmarshalJSON(input []byte) error { if dec.BlockGasCost != nil { h.BlockGasCost = (*big.Int)(dec.BlockGasCost) } + if dec.ExtraStateRoot != nil { + h.ExtraStateRoot = *dec.ExtraStateRoot + } return nil } diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go new file mode 100644 index 0000000000..ba542d2199 --- /dev/null +++ b/core/types/gen_header_rlp.go @@ -0,0 +1,84 @@ +// Code generated by rlpgen. DO NOT EDIT. + +//go:build !norlpgen +// +build !norlpgen + +package types + +import "github.com/ethereum/go-ethereum/common" +import "github.com/tenderly/coreth/rlp" +import "io" + +func (obj *Header) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.WriteBytes(obj.ParentHash[:]) + w.WriteBytes(obj.UncleHash[:]) + w.WriteBytes(obj.Coinbase[:]) + w.WriteBytes(obj.Root[:]) + w.WriteBytes(obj.TxHash[:]) + w.WriteBytes(obj.ReceiptHash[:]) + w.WriteBytes(obj.Bloom[:]) + if obj.Difficulty == nil { + w.Write(rlp.EmptyString) + } else { + if obj.Difficulty.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.Difficulty) + } + if obj.Number == nil { + w.Write(rlp.EmptyString) + } else { + if obj.Number.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.Number) + } + w.WriteUint64(obj.GasLimit) + w.WriteUint64(obj.GasUsed) + w.WriteUint64(obj.Time) + w.WriteBytes(obj.Extra) + w.WriteBytes(obj.MixDigest[:]) + w.WriteBytes(obj.Nonce[:]) + w.WriteBytes(obj.ExtDataHash[:]) + _tmp1 := obj.BaseFee != nil + _tmp2 := obj.ExtDataGasUsed != nil + _tmp3 := obj.BlockGasCost != nil + _tmp4 := obj.ExtraStateRoot != (common.Hash{}) + if _tmp1 || _tmp2 || _tmp3 || _tmp4 { + if obj.BaseFee == nil { + w.Write(rlp.EmptyString) + } else { + if obj.BaseFee.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.BaseFee) + } + } + if _tmp2 || _tmp3 || _tmp4 { + if obj.ExtDataGasUsed == nil { + w.Write(rlp.EmptyString) + } else { + if obj.ExtDataGasUsed.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.ExtDataGasUsed) + } + } + if _tmp3 || _tmp4 { + if obj.BlockGasCost == nil { + w.Write(rlp.EmptyString) + } else { + if obj.BlockGasCost.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.BlockGasCost) + } + } + if _tmp4 { + w.WriteBytes(obj.ExtraStateRoot[:]) + } + w.ListEnd(_tmp0) + return w.Flush() +} diff --git a/core/types/gen_log_rlp.go b/core/types/gen_log_rlp.go new file mode 100644 index 0000000000..62d7e83c6f --- /dev/null +++ b/core/types/gen_log_rlp.go @@ -0,0 +1,26 @@ +// Code generated by rlpgen. DO NOT EDIT. + +//go:build !norlpgen +// +build !norlpgen + +package types + +import ( + "io" + + "github.com/tenderly/coreth/rlp" +) + +func (obj *rlpLog) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.WriteBytes(obj.Address[:]) + _tmp1 := w.List() + for _, _tmp2 := range obj.Topics { + w.WriteBytes(_tmp2[:]) + } + w.ListEnd(_tmp1) + w.WriteBytes(obj.Data) + w.ListEnd(_tmp0) + return w.Flush() +} diff --git a/core/types/hashing.go b/core/types/hashing.go index 6f876f33ad..f75282d958 100644 --- a/core/types/hashing.go +++ b/core/types/hashing.go @@ -8,7 +8,7 @@ // // Much love to the original authors for their work. // ********** -// Copyright 2014 The go-ethereum Authors +// Copyright 2021 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -32,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" "golang.org/x/crypto/sha3" ) @@ -41,7 +41,7 @@ var hasherPool = sync.Pool{ New: func() interface{} { return sha3.NewLegacyKeccak256() }, } -// deriveBufferPool holds temporary encoder buffers for DeriveSha and TX encoding. +// encodeBufferPool holds temporary encoder buffers for DeriveSha and TX encoding. var encodeBufferPool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go index a239db9d8a..df40439792 100644 --- a/core/types/hashing_test.go +++ b/core/types/hashing_test.go @@ -34,12 +34,13 @@ import ( mrand "math/rand" "testing" - "github.com/tenderly/coreth/core/types" - "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/rlp" + "github.com/tenderly/coreth/trie" ) func TestDeriveSha(t *testing.T) { @@ -48,7 +49,7 @@ func TestDeriveSha(t *testing.T) { t.Fatal(err) } for len(txs) < 1000 { - exp := types.DeriveSha(txs, new(trie.Trie)) + exp := types.DeriveSha(txs, trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase()))) got := types.DeriveSha(txs, trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) @@ -95,7 +96,7 @@ func BenchmarkDeriveSha200(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - exp = types.DeriveSha(txs, new(trie.Trie)) + exp = types.DeriveSha(txs, trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase()))) } }) @@ -116,7 +117,7 @@ func TestFuzzDeriveSha(t *testing.T) { rndSeed := mrand.Int() for i := 0; i < 10; i++ { seed := rndSeed + i - exp := types.DeriveSha(newDummy(i), new(trie.Trie)) + exp := types.DeriveSha(newDummy(i), trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase()))) got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { printList(newDummy(seed)) @@ -144,7 +145,7 @@ func TestDerivableList(t *testing.T) { }, } for i, tc := range tcs[1:] { - exp := types.DeriveSha(flatList(tc), new(trie.Trie)) + exp := types.DeriveSha(flatList(tc), trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase()))) got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { t.Fatalf("case %d: got %x exp %x", i, got, exp) @@ -206,7 +207,7 @@ func printList(l types.DerivableList) { for i := 0; i < l.Len(); i++ { var buf bytes.Buffer l.EncodeIndex(i, &buf) - fmt.Printf("\"0x%x\",\n", buf.Bytes()) + fmt.Printf("\"%#x\",\n", buf.Bytes()) } fmt.Printf("},\n") } diff --git a/core/types/legacy_tx.go b/core/types/legacy_tx.go index 2c1aecae31..a32340cbdc 100644 --- a/core/types/legacy_tx.go +++ b/core/types/legacy_tx.go @@ -8,7 +8,7 @@ // // Much love to the original authors for their work. // ********** -// Copyright 2020 The go-ethereum Authors +// Copyright 2021 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify diff --git a/core/types/log.go b/core/types/log.go index 15ab9af155..f173f2cc54 100644 --- a/core/types/log.go +++ b/core/types/log.go @@ -31,10 +31,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) -//go:generate gencodec -type Log -field-override logMarshaling -out gen_log_json.go +//go:generate go run github.com/fjl/gencodec -type Log -field-override logMarshaling -out gen_log_json.go // Log represents a contract log event. These events are generated by the LOG opcode and // stored/indexed by the node. @@ -72,15 +72,14 @@ type logMarshaling struct { Index hexutil.Uint } +//go:generate go run github.com/tenderly/coreth/rlp/rlpgen -type rlpLog -out gen_log_rlp.go + type rlpLog struct { Address common.Address Topics []common.Hash Data []byte } -// rlpStorageLog is the storage encoding of a log. -type rlpStorageLog rlpLog - // legacyRlpStorageLog is the previous storage encoding of a log including some redundant fields. type legacyRlpStorageLog struct { Address common.Address @@ -95,7 +94,8 @@ type legacyRlpStorageLog struct { // EncodeRLP implements rlp.Encoder. func (l *Log) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data}) + rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data} + return rlp.Encode(w, &rl) } // DecodeRLP implements rlp.Decoder. @@ -108,17 +108,14 @@ func (l *Log) DecodeRLP(s *rlp.Stream) error { return err } -// LogForStorage is a wrapper around a Log that flattens and parses the entire content of -// a log including non-consensus fields. +// LogForStorage is a wrapper around a Log that handles +// backward compatibility with prior storage formats. type LogForStorage Log // EncodeRLP implements rlp.Encoder. func (l *LogForStorage) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, rlpStorageLog{ - Address: l.Address, - Topics: l.Topics, - Data: l.Data, - }) + rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data} + return rlp.Encode(w, &rl) } // DecodeRLP implements rlp.Decoder. @@ -129,7 +126,7 @@ func (l *LogForStorage) DecodeRLP(s *rlp.Stream) error { if err != nil { return err } - var dec rlpStorageLog + var dec rlpLog err = rlp.DecodeBytes(blob, &dec) if err == nil { *l = LogForStorage{ diff --git a/core/types/receipt.go b/core/types/receipt.go index 970c90a400..659fa82ff1 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -34,22 +34,21 @@ import ( "math/big" "unsafe" - "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rlp" ) -//go:generate gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go +//go:generate go run github.com/fjl/gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go var ( receiptStatusFailedRLP = []byte{} receiptStatusSuccessfulRLP = []byte{0x01} ) -// This error is returned when a typed receipt is decoded, but the string is empty. -var errEmptyTypedReceipt = errors.New("empty typed receipt bytes") +var errShortTypedReceipt = errors.New("typed receipt too short") const ( // ReceiptStatusFailed is the status code of a transaction if execution failed. @@ -192,26 +191,13 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error { } r.Type = LegacyTxType return r.setFromRLP(dec) - case kind == rlp.String: + default: // It's an EIP-2718 typed tx receipt. b, err := s.Bytes() if err != nil { return err } - if len(b) == 0 { - return errEmptyTypedReceipt - } - r.Type = b[0] - if r.Type == AccessListTxType || r.Type == DynamicFeeTxType { - var dec receiptRLP - if err := rlp.DecodeBytes(b[1:], &dec); err != nil { - return err - } - return r.setFromRLP(dec) - } - return ErrTxTypeNotSupported - default: - return rlp.ErrExpectedList + return r.decodeTyped(b) } } @@ -234,8 +220,8 @@ func (r *Receipt) UnmarshalBinary(b []byte) error { // decodeTyped decodes a typed receipt from the canonical format. func (r *Receipt) decodeTyped(b []byte) error { - if len(b) == 0 { - return errEmptyTypedReceipt + if len(b) <= 1 { + return errShortTypedReceipt } switch b[0] { case DynamicFeeTxType, AccessListTxType: @@ -291,22 +277,26 @@ func (r *Receipt) Size() common.StorageSize { return size } -// ReceiptForStorage is a wrapper around a Receipt that flattens and parses the -// entire content of a receipt, as opposed to only the consensus fields originally. +// ReceiptForStorage is a wrapper around a Receipt with RLP serialization +// that omits the Bloom field and deserialization that re-computes it. type ReceiptForStorage Receipt // EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt // into an RLP stream. -func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { - enc := &storedReceiptRLP{ - PostStateOrStatus: (*Receipt)(r).statusEncoding(), - CumulativeGasUsed: r.CumulativeGasUsed, - Logs: make([]*LogForStorage, len(r.Logs)), - } - for i, log := range r.Logs { - enc.Logs[i] = (*LogForStorage)(log) +func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + outerList := w.List() + w.WriteBytes((*Receipt)(r).statusEncoding()) + w.WriteUint64(r.CumulativeGasUsed) + logList := w.List() + for _, log := range r.Logs { + if err := rlp.Encode(w, log); err != nil { + return err + } } - return rlp.Encode(w, enc) + w.ListEnd(logList) + w.ListEnd(outerList) + return w.Flush() } // DecodeRLP implements rlp.Decoder, and loads both consensus and implementation diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 9c0bad4fd5..bfeb8b6d59 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -33,10 +33,10 @@ import ( "reflect" "testing" - "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rlp" ) var ( @@ -96,7 +96,7 @@ func TestDecodeEmptyTypedReceipt(t *testing.T) { input := []byte{0x80} var r Receipt err := rlp.DecodeBytes(input, &r) - if err != errEmptyTypedReceipt { + if err != errShortTypedReceipt { t.Fatal("wrong error:", err) } } diff --git a/core/types/state_account.go b/core/types/state_account.go index 4e6651ab69..1b91cbabbe 100644 --- a/core/types/state_account.go +++ b/core/types/state_account.go @@ -32,6 +32,8 @@ import ( "github.com/ethereum/go-ethereum/common" ) +//go:generate go run github.com/tenderly/coreth/rlp/rlpgen -type StateAccount -out gen_account_rlp.go + // StateAccount is the Ethereum consensus representation of accounts. // These objects are stored in the main account trie. type StateAccount struct { diff --git a/core/types/transaction.go b/core/types/transaction.go index 32fcd6e437..2e3837b4f4 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -38,7 +38,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) var ( @@ -47,7 +47,7 @@ var ( ErrInvalidTxType = errors.New("transaction type not valid in this context") ErrTxTypeNotSupported = errors.New("transaction type not supported") ErrGasFeeCapTooLow = errors.New("fee cap less than base fee") - errEmptyTypedTx = errors.New("empty typed transaction bytes") + errShortTypedTx = errors.New("typed transaction too short") ) // Transaction types. @@ -144,7 +144,7 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { tx.setDecoded(&inner, int(rlp.ListSize(size))) } return err - case kind == rlp.String: + default: // It's an EIP-2718 typed TX envelope. var b []byte if b, err = s.Bytes(); err != nil { @@ -155,8 +155,6 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { tx.setDecoded(inner, len(b)) } return err - default: - return rlp.ErrExpectedList } } @@ -184,8 +182,8 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error { // decodeTyped decodes a typed transaction from the canonical format. func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { - if len(b) == 0 { - return nil, errEmptyTypedTx + if len(b) <= 1 { + return nil, errShortTypedTx } switch b[0] { case AccessListTxType: @@ -454,6 +452,24 @@ func TxDifference(a, b Transactions) Transactions { return keep } +// HashDifference returns a new set which is the difference between a and b. +func HashDifference(a, b []common.Hash) []common.Hash { + keep := make([]common.Hash, 0, len(a)) + + remove := make(map[common.Hash]struct{}) + for _, hash := range b { + remove[hash] = struct{}{} + } + + for _, hash := range a { + if _, ok := remove[hash]; !ok { + keep = append(keep, hash) + } + } + + return keep +} + // TxByNonce implements the sort interface to allow sorting a list of transactions // by their nonces. This is usually only useful for sorting transactions from a // single account, otherwise a nonce comparison doesn't make much sense. diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 1297fe2ea7..6641bd0eb5 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -32,9 +32,9 @@ import ( "fmt" "math/big" - "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/tenderly/coreth/params" ) var ErrInvalidChainId = errors.New("invalid chain id for signer") diff --git a/core/types/transaction_signing_test.go b/core/types/transaction_signing_test.go index 4720b9bfb7..eebcae3a88 100644 --- a/core/types/transaction_signing_test.go +++ b/core/types/transaction_signing_test.go @@ -32,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) func TestEIP155Signing(t *testing.T) { @@ -121,7 +121,6 @@ func TestEIP155SigningVitalik(t *testing.T) { if from != addr { t.Errorf("%d: expected %x got %x", i, addr, from) } - } } diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 762c85d683..1989d575ac 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -39,7 +39,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) // The values in those tests are from the Transaction Tests @@ -86,7 +86,7 @@ func TestDecodeEmptyTypedTx(t *testing.T) { input := []byte{0x80} var tx Transaction err := rlp.DecodeBytes(input, &tx) - if err != errEmptyTypedTx { + if err != errShortTypedTx { t.Fatal("wrong error:", err) } } @@ -124,7 +124,6 @@ func TestEIP2718TransactionSigHash(t *testing.T) { // This test checks signature operations on access list transactions. func TestEIP2930Signer(t *testing.T) { - var ( key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") keyAddr = crypto.PubkeyToAddress(key.PublicKey) @@ -487,14 +486,18 @@ func TestTransactionCoding(t *testing.T) { if err != nil { t.Fatal(err) } - assertEqual(parsedTx, tx) + if err := assertEqual(parsedTx, tx); err != nil { + t.Fatal(err) + } // JSON parsedTx, err = encodeDecodeJSON(tx) if err != nil { t.Fatal(err) } - assertEqual(parsedTx, tx) + if err := assertEqual(parsedTx, tx); err != nil { + t.Fatal(err) + } } } diff --git a/core/types/types_test.go b/core/types/types_test.go index 7b68db9b4b..73443e90eb 100644 --- a/core/types/types_test.go +++ b/core/types/types_test.go @@ -32,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) type devnull struct{ len int } diff --git a/core/vm/analysis.go b/core/vm/analysis.go index cfbf0e7f19..cd1dc542fa 100644 --- a/core/vm/analysis.go +++ b/core/vm/analysis.go @@ -86,7 +86,7 @@ func codeBitmapInternal(code, bits bitvec) bitvec { for pc := uint64(0); pc < uint64(len(code)); { op := OpCode(code[pc]) pc++ - if op < PUSH1 || op > PUSH32 { + if int8(op) < int8(PUSH1) { // If not PUSH (the int8(op) > int(PUSH32) is always false). continue } numbits := op - PUSH1 + 1 diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 302e4a0efc..52c30bc2c5 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -33,18 +33,16 @@ import ( "fmt" "math/big" - "github.com/tenderly/coreth/constants" - "github.com/tenderly/coreth/params" - "github.com/tenderly/coreth/precompile" - "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/blake2b" "github.com/ethereum/go-ethereum/crypto/bls12381" "github.com/ethereum/go-ethereum/crypto/bn256" - - //lint:ignore SA1019 Needed for precompile + "github.com/tenderly/coreth/constants" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/precompile" + "github.com/tenderly/coreth/vmerrs" "golang.org/x/crypto/ripemd160" ) @@ -109,9 +107,9 @@ var PrecompiledContractsApricotPhase2 = map[common.Address]precompile.StatefulPr NativeAssetCallAddr: &nativeAssetCall{gasCost: params.AssetCallApricot}, } -// PrecompiledContractsApricotPhase6 contains the default set of pre-compiled Ethereum -// contracts used in the Apricot Phase 6 release. -var PrecompiledContractsApricotPhase6 = map[common.Address]precompile.StatefulPrecompiledContract{ +// PrecompiledContractsBanff contains the default set of pre-compiled Ethereum +// contracts used in the Banff release. +var PrecompiledContractsBanff = map[common.Address]precompile.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -127,7 +125,7 @@ var PrecompiledContractsApricotPhase6 = map[common.Address]precompile.StatefulPr } var ( - PrecompiledAddressesApricotPhase6 []common.Address + PrecompiledAddressesBanff []common.Address PrecompiledAddressesApricotPhase2 []common.Address PrecompiledAddressesIstanbul []common.Address PrecompiledAddressesByzantium []common.Address @@ -148,16 +146,17 @@ func init() { for k := range PrecompiledContractsApricotPhase2 { PrecompiledAddressesApricotPhase2 = append(PrecompiledAddressesApricotPhase2, k) } - for k := range PrecompiledContractsApricotPhase6 { - PrecompiledAddressesApricotPhase6 = append(PrecompiledAddressesApricotPhase6, k) + for k := range PrecompiledContractsBanff { + PrecompiledAddressesBanff = append(PrecompiledAddressesBanff, k) } + // Set of all native precompile addresses that are in use // Note: this will repeat some addresses, but this is cheap and makes the code clearer. PrecompileAllNativeAddresses = make(map[common.Address]struct{}) addrsList := append(PrecompiledAddressesHomestead, PrecompiledAddressesByzantium...) addrsList = append(addrsList, PrecompiledAddressesIstanbul...) addrsList = append(addrsList, PrecompiledAddressesApricotPhase2...) - addrsList = append(addrsList, PrecompiledAddressesApricotPhase6...) + addrsList = append(addrsList, PrecompiledAddressesBanff...) for _, k := range addrsList { PrecompileAllNativeAddresses[k] = struct{}{} } @@ -189,8 +188,8 @@ func init() { // ActivePrecompiles returns the precompiles enabled with the current configuration. func ActivePrecompiles(rules params.Rules) []common.Address { switch { - case rules.IsApricotPhase6: - return PrecompiledAddressesApricotPhase6 + case rules.IsBanff: + return PrecompiledAddressesBanff case rules.IsApricotPhase2: return PrecompiledAddressesApricotPhase2 case rules.IsIstanbul: @@ -651,7 +650,7 @@ func (c *blake2F) Run(input []byte) ([]byte, error) { // Parse the input into the Blake2b call parameters var ( rounds = binary.BigEndian.Uint32(input[0:4]) - final = (input[212] == blake2FFinalBlockBytes) + final = input[212] == blake2FFinalBlockBytes h [8]uint64 m [16]uint64 diff --git a/core/vm/contracts_stateful.go b/core/vm/contracts_stateful.go index 10927c8ce9..4b9b1b5aae 100644 --- a/core/vm/contracts_stateful.go +++ b/core/vm/contracts_stateful.go @@ -7,10 +7,10 @@ import ( "fmt" "math/big" - "github.com/tenderly/coreth/precompile" - "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" + "github.com/tenderly/coreth/precompile" + "github.com/tenderly/coreth/vmerrs" ) // PrecompiledContractsApricot contains the default set of pre-compiled Ethereum diff --git a/core/vm/contracts_stateful_test.go b/core/vm/contracts_stateful_test.go index 8d503a7710..c4167ffdd0 100644 --- a/core/vm/contracts_stateful_test.go +++ b/core/vm/contracts_stateful_test.go @@ -7,12 +7,13 @@ import ( "math/big" "testing" - "github.com/tenderly/coreth/core/rawdb" - "github.com/tenderly/coreth/core/state" - "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/assert" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/state" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/vmerrs" ) func TestPrecompiledContractSpendsGas(t *testing.T) { @@ -195,7 +196,7 @@ func TestStatefulPrecompile(t *testing.T) { value: big0, gasInput: params.AssetBalanceApricot, expectedGasRemaining: 0, - expectedErr: ErrExecutionReverted, + expectedErr: vmerrs.ErrExecutionReverted, expectedResult: nil, name: "native asset balance: invalid input data reverts", }, @@ -213,7 +214,7 @@ func TestStatefulPrecompile(t *testing.T) { value: big0, gasInput: params.AssetBalanceApricot - 1, expectedGasRemaining: 0, - expectedErr: ErrOutOfGas, + expectedErr: vmerrs.ErrOutOfGas, expectedResult: nil, name: "native asset balance: insufficient gas errors", }, @@ -231,7 +232,7 @@ func TestStatefulPrecompile(t *testing.T) { value: bigHundred, gasInput: params.AssetBalanceApricot, expectedGasRemaining: params.AssetBalanceApricot, - expectedErr: ErrInsufficientBalance, + expectedErr: vmerrs.ErrInsufficientBalance, expectedResult: nil, name: "native asset balance: non-zero value with insufficient funds reverts before running pre-compile", }, @@ -320,7 +321,7 @@ func TestStatefulPrecompile(t *testing.T) { value: big.NewInt(50), gasInput: params.AssetCallApricot, expectedGasRemaining: 0, - expectedErr: ErrInsufficientBalance, + expectedErr: vmerrs.ErrInsufficientBalance, expectedResult: nil, name: "native asset call: insufficient multicoin funds", stateDBCheck: func(t *testing.T, stateDB StateDB) { @@ -352,7 +353,7 @@ func TestStatefulPrecompile(t *testing.T) { value: big.NewInt(51), gasInput: params.AssetCallApricot, expectedGasRemaining: params.AssetCallApricot, - expectedErr: ErrInsufficientBalance, + expectedErr: vmerrs.ErrInsufficientBalance, expectedResult: nil, name: "native asset call: insufficient funds", stateDBCheck: func(t *testing.T, stateDB StateDB) { @@ -384,7 +385,7 @@ func TestStatefulPrecompile(t *testing.T) { value: big.NewInt(50), gasInput: params.AssetCallApricot - 1, expectedGasRemaining: 0, - expectedErr: ErrOutOfGas, + expectedErr: vmerrs.ErrOutOfGas, expectedResult: nil, name: "native asset call: insufficient gas for native asset call", }, @@ -405,7 +406,7 @@ func TestStatefulPrecompile(t *testing.T) { value: big.NewInt(50), gasInput: params.AssetCallApricot + params.CallNewAccountGas - 1, expectedGasRemaining: 0, - expectedErr: ErrOutOfGas, + expectedErr: vmerrs.ErrOutOfGas, expectedResult: nil, name: "native asset call: insufficient gas to create new account", stateDBCheck: func(t *testing.T, stateDB StateDB) { @@ -437,7 +438,7 @@ func TestStatefulPrecompile(t *testing.T) { value: big.NewInt(50), gasInput: params.AssetCallApricot + params.CallNewAccountGas, expectedGasRemaining: params.CallNewAccountGas, - expectedErr: ErrExecutionReverted, + expectedErr: vmerrs.ErrExecutionReverted, expectedResult: nil, name: "native asset call: invalid input", }, @@ -458,7 +459,7 @@ func TestStatefulPrecompile(t *testing.T) { value: big0, gasInput: params.AssetCallApricot + params.CallNewAccountGas, expectedGasRemaining: params.AssetCallApricot + params.CallNewAccountGas, - expectedErr: ErrExecutionReverted, + expectedErr: vmerrs.ErrExecutionReverted, expectedResult: nil, name: "deprecated contract", }, @@ -467,7 +468,7 @@ func TestStatefulPrecompile(t *testing.T) { t.Run(test.name, func(t *testing.T) { stateDB := test.setupStateDB() // Create EVM with BlockNumber and Time initialized to 0 to enable Apricot Rules. - evm := NewEVM(vmCtx, TxContext{}, stateDB, params.TestChainConfig, Config{}) + evm := NewEVM(vmCtx, TxContext{}, stateDB, params.TestApricotPhase5Config, Config{}) // Use ApricotPhase5Config because these precompiles are deprecated in ApricotPhase6. ret, gasRemaining, err := evm.Call(AccountRef(test.from), test.precompileAddr, test.input, test.gasInput, test.value) // Place gas remaining check before error check, so that it is not skipped when there is an error assert.Equal(t, test.expectedGasRemaining, gasRemaining, "unexpected gas remaining") diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 3e333fc4cd..e4114a046e 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -30,7 +30,7 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" + "os" "testing" "time" @@ -344,7 +344,7 @@ func TestPrecompiledBLS12381MapG1Fail(t *testing.T) { testJsonFail("blsMapG func TestPrecompiledBLS12381MapG2Fail(t *testing.T) { testJsonFail("blsMapG2", "12", t) } func loadJson(name string) ([]precompiledTest, error) { - data, err := ioutil.ReadFile(fmt.Sprintf("testdata/precompiles/%v.json", name)) + data, err := os.ReadFile(fmt.Sprintf("testdata/precompiles/%v.json", name)) if err != nil { return nil, err } @@ -354,7 +354,7 @@ func loadJson(name string) ([]precompiledTest, error) { } func loadJsonFail(name string) ([]precompiledFailureTest, error) { - data, err := ioutil.ReadFile(fmt.Sprintf("testdata/precompiles/fail-%v.json", name)) + data, err := os.ReadFile(fmt.Sprintf("testdata/precompiles/fail-%v.json", name)) if err != nil { return nil, err } diff --git a/core/vm/eips.go b/core/vm/eips.go index 966f13dbe0..426286b9d3 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -30,11 +30,12 @@ import ( "fmt" "sort" - "github.com/tenderly/coreth/params" "github.com/holiman/uint256" + "github.com/tenderly/coreth/params" ) var activators = map[int]func(*JumpTable){ + 3855: enable3855, 3198: enable3198, 2929: enable2929, 2200: enable2200, @@ -188,3 +189,20 @@ func opBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] scope.Stack.push(baseFee) return nil, nil } + +// enable3855 applies EIP-3855 (PUSH0 opcode) +func enable3855(jt *JumpTable) { + // New opcode + jt[PUSH0] = &operation{ + execute: opPush0, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} + +// opPush0 implements the PUSH0 opcode +func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int)) + return nil, nil +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 57638b03db..428f92d942 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -31,13 +31,13 @@ import ( "sync/atomic" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/holiman/uint256" "github.com/tenderly/coreth/constants" "github.com/tenderly/coreth/params" "github.com/tenderly/coreth/precompile" "github.com/tenderly/coreth/vmerrs" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/holiman/uint256" ) var ( @@ -59,6 +59,7 @@ func IsProhibited(addr common.Address) bool { return false } +// TODO: deprecate after Banff activation. func (evm *EVM) isProhibitedWithTimestamp(addr common.Address) error { if addr != NativeAssetCallAddr { return nil @@ -66,6 +67,8 @@ func (evm *EVM) isProhibitedWithTimestamp(addr common.Address) error { // Return error depending on the phase switch { + case evm.chainRules.IsBanff: // Disable the soft fork as of Banff + return nil case evm.chainRules.IsApricotPhasePost6: // If we are in the soft fork, return the soft error return vmerrs.ErrToAddrProhibitedSoft case evm.chainRules.IsApricotPhase6: // If we are in Phase6, return nil @@ -96,8 +99,8 @@ type ( func (evm *EVM) precompile(addr common.Address) (precompile.StatefulPrecompiledContract, bool) { var precompiles map[common.Address]precompile.StatefulPrecompiledContract switch { - case evm.chainRules.IsApricotPhase6: // This stayed the same - precompiles = PrecompiledContractsApricotPhase2 + case evm.chainRules.IsBanff: + precompiles = PrecompiledContractsBanff case evm.chainRules.IsApricotPhase2: precompiles = PrecompiledContractsApricotPhase2 case evm.chainRules.IsIstanbul: diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go new file mode 100644 index 0000000000..021dc5700c --- /dev/null +++ b/core/vm/evm_test.go @@ -0,0 +1,24 @@ +// (c) 2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package vm + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +func TestIsProhibited(t *testing.T) { + // reserved addresses + assert.True(t, IsProhibited(common.HexToAddress("0x0100000000000000000000000000000000000000"))) + assert.True(t, IsProhibited(common.HexToAddress("0x0100000000000000000000000000000000000010"))) + assert.True(t, IsProhibited(common.HexToAddress("0x01000000000000000000000000000000000000f0"))) + assert.True(t, IsProhibited(common.HexToAddress("0x01000000000000000000000000000000000000ff"))) + + // allowed for use + assert.False(t, IsProhibited(common.HexToAddress("0x00000000000000000000000000000000000000ff"))) + assert.False(t, IsProhibited(common.HexToAddress("0x0100000000000000000000000000000000000100"))) + assert.False(t, IsProhibited(common.HexToAddress("0x0200000000000000000000000000000000000000"))) +} diff --git a/core/vm/gas.go b/core/vm/gas.go index dbae956489..1c4f2cb157 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -27,8 +27,8 @@ package vm import ( - "github.com/tenderly/coreth/vmerrs" "github.com/holiman/uint256" + "github.com/tenderly/coreth/vmerrs" ) // Gas costs diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 23a2cf01e1..babfc2c95b 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -29,10 +29,10 @@ package vm import ( "errors" - "github.com/tenderly/coreth/params" - "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/vmerrs" ) // memoryGasCost calculates the quadratic gas for memory expansion. It does so diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index bb00859b5f..3238a20ee0 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -31,11 +31,12 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/tenderly/coreth/vmerrs" ) func TestMemoryGasCost(t *testing.T) { @@ -49,8 +50,8 @@ func TestMemoryGasCost(t *testing.T) { } for i, tt := range tests { v, err := memoryGasCost(&Memory{}, tt.size) - if (err == ErrGasUintOverflow) != tt.overflow { - t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == ErrGasUintOverflow, tt.overflow) + if (err == vmerrs.ErrGasUintOverflow) != tt.overflow { + t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == vmerrs.ErrGasUintOverflow, tt.overflow) } if v != tt.cost { t.Errorf("test %d: gas cost mismatch: have %v, want %v", i, v, tt.cost) @@ -83,7 +84,7 @@ var eip2200Tests = []struct { {1, math.MaxUint64, "0x60016000556001600055", 1612, 0, nil}, // 1 -> 1 -> 1 {0, math.MaxUint64, "0x600160005560006000556001600055", 40818, 19200, nil}, // 0 -> 1 -> 0 -> 1 {1, math.MaxUint64, "0x600060005560016000556000600055", 10818, 19200, nil}, // 1 -> 0 -> 1 -> 0 - {1, 2306, "0x6001600055", 2306, 0, ErrOutOfGas}, // 1 -> 1 (2300 sentry + 2xPUSH) + {1, 2306, "0x6001600055", 2306, 0, vmerrs.ErrOutOfGas}, // 1 -> 1 (2300 sentry + 2xPUSH) {1, 2307, "0x6001600055", 806, 0, nil}, // 1 -> 1 (2301 sentry + 2xPUSH) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index d61e4c9e75..13e6db4655 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -30,11 +30,11 @@ import ( "errors" "sync/atomic" + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/params" "github.com/tenderly/coreth/vmerrs" - "github.com/ethereum/go-ethereum/common" - "github.com/holiman/uint256" "golang.org/x/crypto/sha3" ) @@ -618,7 +618,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, bigVal) - // Special case the error in the op code + // Special case the error in the op code. TODO remove. if errors.Is(suberr, vmerrs.ErrToAddrProhibitedSoft) { return nil, suberr } @@ -668,7 +668,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] } res, addr, returnGas, suberr := interpreter.evm.Create2(scope.Contract, input, gas, bigEndowment, &salt) - // Special case the error in the op code + // Special case the error in the op code. TODO remove. if errors.Is(suberr, vmerrs.ErrToAddrProhibitedSoft) { return nil, suberr } @@ -714,7 +714,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal) - // Special case the error in the op code + // Special case the error in the op code. TODO remove. if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) { return nil, err } @@ -768,7 +768,7 @@ func opCallExpert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } ret, returnGas, err := interpreter.evm.CallExpert(scope.Contract, toAddr, args, gas, bigVal, coinID, bigVal2) - // Special case the error in the op code + // Special case the error in the op code. TODO remove. if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) { return nil, err } @@ -807,7 +807,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ } ret, returnGas, err := interpreter.evm.CallCode(scope.Contract, toAddr, args, gas, bigVal) - // Special case the error in the op code + // Special case the error in the op code. TODO remove. if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) { return nil, err } @@ -840,7 +840,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas) - // Special case the error in the op code + // Special case the error in the op code. TODO remove. if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) { return nil, err } @@ -873,7 +873,7 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) ret, returnGas, err := interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas) - // Special case the error in the op code + // Special case the error in the op code. TODO remove. if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) { return nil, err } diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index b607213379..7e7becc15c 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -30,14 +30,14 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" "math/big" + "os" "testing" - "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/holiman/uint256" + "github.com/tenderly/coreth/params" ) type TwoOperandTestcase struct { @@ -56,7 +56,6 @@ var commonParams []*twoOperandParams var twoOpMethods map[string]executionFunc func init() { - // Params is a list of common edgecases that should be used for some common tests params := []string{ "0000000000000000000000000000000000000000000000000000000000000000", // 0 @@ -102,7 +101,6 @@ func init() { } func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) { - var ( env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() @@ -239,38 +237,38 @@ func TestAddMod(t *testing.T) { } } -// getResult is a convenience function to generate the expected values -func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase { - var ( - env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) - stack = newstack() - pc = uint64(0) - interpreter = env.interpreter - ) - result := make([]TwoOperandTestcase, len(args)) - for i, param := range args { - x := new(uint256.Int).SetBytes(common.Hex2Bytes(param.x)) - y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) - stack.push(x) - stack.push(y) - opFn(&pc, interpreter, &ScopeContext{nil, stack, nil}) - actual := stack.pop() - result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} - } - return result -} - // utility function to fill the json-file with testcases // Enable this test to generate the 'testcases_xx.json' files func TestWriteExpectedValues(t *testing.T) { t.Skip("Enable this test to create json test cases.") + // getResult is a convenience function to generate the expected values + getResult := func(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase { + var ( + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + interpreter = env.interpreter + ) + result := make([]TwoOperandTestcase, len(args)) + for i, param := range args { + x := new(uint256.Int).SetBytes(common.Hex2Bytes(param.x)) + y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) + stack.push(x) + stack.push(y) + opFn(&pc, interpreter, &ScopeContext{nil, stack, nil}) + actual := stack.pop() + result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} + } + return result + } + for name, method := range twoOpMethods { data, err := json.Marshal(getResult(commonParams, method)) if err != nil { t.Fatal(err) } - _ = ioutil.WriteFile(fmt.Sprintf("testdata/testcases_%v.json", name), data, 0644) + _ = os.WriteFile(fmt.Sprintf("testdata/testcases_%v.json", name), data, 0644) if err != nil { t.Fatal(err) } @@ -280,7 +278,7 @@ func TestWriteExpectedValues(t *testing.T) { // TestJsonTestcases runs through all the testcases defined as json-files func TestJsonTestcases(t *testing.T) { for name := range twoOpMethods { - data, err := ioutil.ReadFile(fmt.Sprintf("testdata/testcases_%v.json", name)) + data, err := os.ReadFile(fmt.Sprintf("testdata/testcases_%v.json", name)) if err != nil { t.Fatal("Failed to read file", err) } @@ -294,26 +292,33 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() + scope = &ScopeContext{nil, stack, nil} evmInterpreter = NewEVMInterpreter(env, env.Config) ) env.interpreter = evmInterpreter // convert args - byteArgs := make([][]byte, len(args)) + intArgs := make([]*uint256.Int, len(args)) for i, arg := range args { - byteArgs[i] = common.Hex2Bytes(arg) + intArgs[i] = new(uint256.Int).SetBytes(common.Hex2Bytes(arg)) } pc := uint64(0) bench.ResetTimer() for i := 0; i < bench.N; i++ { - for _, arg := range byteArgs { - a := new(uint256.Int) - a.SetBytes(arg) - stack.push(a) + for _, arg := range intArgs { + stack.push(arg) } - op(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + op(&pc, evmInterpreter, scope) stack.pop() } + bench.StopTimer() + + for i, arg := range args { + want := new(uint256.Int).SetBytes(common.Hex2Bytes(arg)) + if have := intArgs[i]; !want.Eq(have) { + bench.Fatalf("input #%d mutated, have %x want %x", i, have, want) + } + } } func BenchmarkOpAdd64(b *testing.B) { @@ -644,7 +649,6 @@ func TestCreate2Addreses(t *testing.T) { expected: "0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0", }, } { - origin := common.BytesToAddress(common.FromHex(tt.origin)) salt := common.BytesToHash(common.FromHex(tt.salt)) code := common.FromHex(tt.code) diff --git a/core/vm/interface.go b/core/vm/interface.go index 375c786a24..cc7efcc1a2 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -29,8 +29,8 @@ package vm import ( "math/big" - "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/core/types" ) // StateDB is an EVM database for full state querying. diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 4878878b29..f3e3fe534e 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -29,10 +29,10 @@ package vm import ( "hash" - "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/log" + "github.com/tenderly/coreth/vmerrs" ) var ( @@ -190,7 +190,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( res []byte // result of the opcode execution function ) - // Don't move this deferrred function, it's placed before the capturestate-deferred method, + // Don't move this deferred function, it's placed before the capturestate-deferred method, // so that it get's executed _after_: the capturestate needs the stacks before // they are returned to the pools defer func() { @@ -259,11 +259,15 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( if err != nil || !contract.UseGas(dynamicCost) { return nil, vmerrs.ErrOutOfGas } + // Do tracing before memory expansion + if in.cfg.Debug { + in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) + logged = true + } if memorySize > 0 { mem.Resize(memorySize) } - } - if in.cfg.Debug { + } else if in.cfg.Debug { in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) logged = true } diff --git a/core/vm/interpreter_test.go b/core/vm/interpreter_test.go index 5b4c42f8b6..4923bf89a4 100644 --- a/core/vm/interpreter_test.go +++ b/core/vm/interpreter_test.go @@ -31,11 +31,11 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" ) var loopInterruptTests = []string{ @@ -83,5 +83,4 @@ func TestLoopInterrupt(t *testing.T) { } } } - } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 6c44615b56..a891315416 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -73,7 +73,7 @@ type JumpTable [256]*operation func validate(jt JumpTable) JumpTable { for i, op := range jt { if op == nil { - panic(fmt.Sprintf("op 0x%x is not set", i)) + panic(fmt.Sprintf("op %#x is not set", i)) } // The interpreter has an assumption that if the memorySize function is // set, then the dynamicGas function is also set. This is a somewhat @@ -211,7 +211,6 @@ func newSpuriousDragonInstructionSet() JumpTable { instructionSet := newTangerineWhistleInstructionSet() instructionSet[EXP].dynamicGas = gasExpEIP158 return validate(instructionSet) - } // EIP 150 a.k.a Tangerine Whistle diff --git a/core/vm/logger.go b/core/vm/logger.go index ba962f7773..b376739c2e 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -39,10 +39,16 @@ import ( // Note that reference types are actual VM data structures; make copies // if you need to retain them beyond the current call. type EVMLogger interface { + // Transaction level + CaptureTxStart(gasLimit uint64) + CaptureTxEnd(restGas uint64) + // Top call frame CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) - CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) + CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) + // Rest of call frames CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) CaptureExit(output []byte, gasUsed uint64, err error) + // Opcode level + CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) - CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) } diff --git a/core/vm/memory.go b/core/vm/memory.go index 51bf0a5b83..eb6bc89078 100644 --- a/core/vm/memory.go +++ b/core/vm/memory.go @@ -27,8 +27,6 @@ package vm import ( - "fmt" - "github.com/holiman/uint256" ) @@ -65,10 +63,9 @@ func (m *Memory) Set32(offset uint64, val *uint256.Int) { if offset+32 > uint64(len(m.store)) { panic("invalid memory: store empty") } - // Zero the memory area - copy(m.store[offset:offset+32], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) // Fill in relevant bits - val.WriteToSlice(m.store[offset:]) + b32 := val.Bytes32() + copy(m.store[offset:], b32[:]) } // Resize resizes the memory to size @@ -78,7 +75,7 @@ func (m *Memory) Resize(size uint64) { } } -// Get returns offset + size as a new slice +// GetCopy returns offset + size as a new slice func (m *Memory) GetCopy(offset, size int64) (cpy []byte) { if size == 0 { return nil @@ -116,18 +113,3 @@ func (m *Memory) Len() int { func (m *Memory) Data() []byte { return m.store } - -// Print dumps the content of the memory. -func (m *Memory) Print() { - fmt.Printf("### mem %d bytes ###\n", len(m.store)) - if len(m.store) > 0 { - addr := 0 - for i := 0; i+32 <= len(m.store); i += 32 { - fmt.Printf("%03d: % x\n", addr, m.store[i:i+32]) - addr++ - } - } else { - fmt.Println("-- empty --") - } - fmt.Println("####################") -} diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index c8739d5b4d..82edbf3866 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -74,7 +74,10 @@ const ( SHL OpCode = 0x1b SHR OpCode = 0x1c SAR OpCode = 0x1d +) +// 0x20 range - crypto. +const ( KECCAK256 OpCode = 0x20 ) @@ -125,6 +128,7 @@ const ( MSIZE OpCode = 0x59 GAS OpCode = 0x5a JUMPDEST OpCode = 0x5b + PUSH0 OpCode = 0x5f ) // 0x60 range - pushes. @@ -312,6 +316,7 @@ var opCodeToString = map[OpCode]string{ MSIZE: "MSIZE", GAS: "GAS", JUMPDEST: "JUMPDEST", + PUSH0: "PUSH0", // 0x60 range - push. PUSH1: "PUSH1", @@ -403,7 +408,7 @@ var opCodeToString = map[OpCode]string{ func (op OpCode) String() string { str := opCodeToString[op] if len(str) == 0 { - return fmt.Sprintf("opcode 0x%x not defined", int(op)) + return fmt.Sprintf("opcode %#x not defined", int(op)) } return str @@ -477,6 +482,7 @@ var stringToOp = map[string]OpCode{ "MSIZE": MSIZE, "GAS": GAS, "JUMPDEST": JUMPDEST, + "PUSH0": PUSH0, "PUSH1": PUSH1, "PUSH2": PUSH2, "PUSH3": PUSH3, diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 3355455a9c..9db77be88f 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -29,10 +29,10 @@ package vm import ( "errors" - "github.com/tenderly/coreth/params" - "github.com/tenderly/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/vmerrs" ) // gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929 diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 04dac84343..71fa987784 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -31,12 +31,12 @@ import ( "math/big" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" ) // Config is a basic type specifying certain configuration flags for running diff --git a/core/vm/runtime/runtime_example_test.go b/core/vm/runtime/runtime_example_test.go index 8fa1217255..22fdf1becc 100644 --- a/core/vm/runtime/runtime_example_test.go +++ b/core/vm/runtime/runtime_example_test.go @@ -29,8 +29,8 @@ package runtime_test import ( "fmt" - "github.com/tenderly/coreth/core/vm/runtime" "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/core/vm/runtime" ) func ExampleExecute() { diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 429d89bf61..f06b1552b5 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -32,8 +32,9 @@ import ( "os" "strings" "testing" - "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/asm" "github.com/tenderly/coreth/accounts/abi" "github.com/tenderly/coreth/consensus" "github.com/tenderly/coreth/core" @@ -44,8 +45,6 @@ import ( "github.com/tenderly/coreth/eth/tracers" "github.com/tenderly/coreth/eth/tracers/logger" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/asm" // force-load native tracers to trigger registration _ "github.com/tenderly/coreth/eth/tracers/native" @@ -336,25 +335,6 @@ func TestBlockhash(t *testing.T) { } } -type stepCounter struct { - inner *logger.JSONLogger - steps int -} - -func (s *stepCounter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { -} - -func (s *stepCounter) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { -} - -func (s *stepCounter) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {} - -func (s *stepCounter) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - s.steps++ - // Enable this for more output - //s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err) -} - // benchmarkNonModifyingCode benchmarks code, but if the code modifies the // state, this should not be used, since it does not reset the state between runs. func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode string, b *testing.B) { @@ -363,7 +343,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) cfg.GasLimit = gas if len(tracerCode) > 0 { - tracer, err := tracers.New(tracerCode, new(tracers.Context)) + tracer, err := tracers.New(tracerCode, new(tracers.Context), nil) if err != nil { b.Fatal(err) } @@ -409,7 +389,6 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode // BenchmarkSimpleLoop test a pretty simple loop which loops until OOG // 55 ms func BenchmarkSimpleLoop(b *testing.B) { - staticCallIdentity := []byte{ byte(vm.JUMPDEST), // [ count ] // push args for the call @@ -488,7 +467,7 @@ func BenchmarkSimpleLoop(b *testing.B) { byte(vm.JUMP), } - calllRevertingContractWithInput := []byte{ + callRevertingContractWithInput := []byte{ byte(vm.JUMPDEST), // // push args for the call byte(vm.PUSH1), 0, // out size @@ -516,7 +495,7 @@ func BenchmarkSimpleLoop(b *testing.B) { benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", "", b) benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", "", b) benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", "", b) - benchmarkNonModifyingCode(100000000, calllRevertingContractWithInput, "call-reverting-100M", "", b) + benchmarkNonModifyingCode(100000000, callRevertingContractWithInput, "call-reverting-100M", "", b) //benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b) //benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b) @@ -528,12 +507,11 @@ func TestEip2929Cases(t *testing.T) { t.Skip("Test only useful for generating documentation") id := 1 prettyPrint := func(comment string, code []byte) { - instrs := make([]string, 0) it := asm.NewInstructionIterator(code) for it.Next() { if it.Arg() != nil && 0 < len(it.Arg()) { - instrs = append(instrs, fmt.Sprintf("%v 0x%x", it.Op(), it.Arg())) + instrs = append(instrs, fmt.Sprintf("%v %#x", it.Op(), it.Arg())) } else { instrs = append(instrs, fmt.Sprintf("%v", it.Op())) } @@ -541,7 +519,7 @@ func TestEip2929Cases(t *testing.T) { ops := strings.Join(instrs, ", ") fmt.Printf("### Case %d\n\n", id) id++ - fmt.Printf("%v\n\nBytecode: \n```\n0x%x\n```\nOperations: \n```\n%v\n```\n\n", + fmt.Printf("%v\n\nBytecode: \n```\n%#x\n```\nOperations: \n```\n%v\n```\n\n", comment, code, ops) Execute(code, nil, &Config{ diff --git a/core/vm/stack.go b/core/vm/stack.go index 7ff708c62d..5463b2d75a 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -27,7 +27,6 @@ package vm import ( - "fmt" "sync" "github.com/holiman/uint256" @@ -91,16 +90,3 @@ func (st *Stack) peek() *uint256.Int { func (st *Stack) Back(n int) *uint256.Int { return &st.data[st.len()-n-1] } - -// Print dumps the content of the stack -func (st *Stack) Print() { - fmt.Println("### stack ###") - if len(st.data) > 0 { - for i, val := range st.data { - fmt.Printf("%-3d %s\n", i, val.String()) - } - } else { - fmt.Println("-- empty --") - } - fmt.Println("#############") -} diff --git a/ethdb/batch.go b/ethdb/batch.go index dda29529b0..be4f52d8c1 100644 --- a/ethdb/batch.go +++ b/ethdb/batch.go @@ -53,6 +53,9 @@ type Batcher interface { // NewBatch creates a write-only database that buffers changes to its host db // until a final write is called. NewBatch() Batch + + // NewBatchWithSize creates a write-only database batch with pre-allocated buffer. + NewBatchWithSize(size int) Batch } // HookedBatch wraps an arbitrary batch where each operation may be hooked into diff --git a/ethdb/dbtest/testsuite.go b/ethdb/dbtest/testsuite.go index 6fd215b44b..e6a083d36f 100644 --- a/ethdb/dbtest/testsuite.go +++ b/ethdb/dbtest/testsuite.go @@ -322,7 +322,6 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { t.Errorf("got: %s; want: %s", got, want) } }) - } func iterateKeys(it ethdb.Iterator) []string { diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index 28f7fc51fc..ed8d43d16f 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -37,8 +37,6 @@ import ( "sync" "time" - "github.com/tenderly/coreth/ethdb" - "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/syndtr/goleveldb/leveldb" @@ -46,6 +44,8 @@ import ( "github.com/syndtr/goleveldb/leveldb/filter" "github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/util" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/metrics" ) const ( @@ -223,6 +223,14 @@ func (db *Database) NewBatch() ethdb.Batch { } } +// NewBatchWithSize creates a write-only database batch with pre-allocated buffer. +func (db *Database) NewBatchWithSize(size int) ethdb.Batch { + return &batch{ + db: db.db, + b: leveldb.MakeBatch(size), + } +} + // NewIterator creates a binary-alphabetical iterator over a subset // of database content with a particular key prefix, starting at a particular // initial key (or after, if it does not exist). diff --git a/ethdb/leveldb/leveldb_test.go b/ethdb/leveldb/leveldb_test.go index 7de97878dc..6a3c8e0062 100644 --- a/ethdb/leveldb/leveldb_test.go +++ b/ethdb/leveldb/leveldb_test.go @@ -29,10 +29,10 @@ package leveldb import ( "testing" - "github.com/tenderly/coreth/ethdb" - "github.com/tenderly/coreth/ethdb/dbtest" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/storage" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/dbtest" ) func TestLevelDB(t *testing.T) { diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index a75ebb555e..9e3dc970a5 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -33,8 +33,8 @@ import ( "strings" "sync" - "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/ethdb" ) var ( @@ -63,7 +63,7 @@ func New() *Database { } } -// NewWithCap returns a wrapped map pre-allocated to the provided capcity with +// NewWithCap returns a wrapped map pre-allocated to the provided capacity with // all the required database interface methods implemented. func NewWithCap(size int) *Database { return &Database{ @@ -72,7 +72,7 @@ func NewWithCap(size int) *Database { } // Close deallocates the internal map and ensures any consecutive data access op -// failes with an error. +// fails with an error. func (db *Database) Close() error { db.lock.Lock() defer db.lock.Unlock() @@ -139,6 +139,13 @@ func (db *Database) NewBatch() ethdb.Batch { } } +// NewBatchWithSize creates a write-only database batch with pre-allocated buffer. +func (db *Database) NewBatchWithSize(size int) ethdb.Batch { + return &batch{ + db: db, + } +} + // NewIterator creates a binary-alphabetical iterator over a subset // of database content with a particular key prefix, starting at a particular // initial key (or after, if it does not exist). @@ -168,6 +175,7 @@ func (db *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { values = append(values, db.db[key]) } return &iterator{ + index: -1, keys: keys, values: values, } @@ -271,7 +279,7 @@ func (b *batch) Replay(w ethdb.KeyValueWriter) error { // value store. Internally it is a deep copy of the entire iterated state, // sorted by keys. type iterator struct { - inited bool + index int keys []string values [][]byte } @@ -279,17 +287,12 @@ type iterator struct { // Next moves the iterator to the next key/value pair. It returns whether the // iterator is exhausted. func (it *iterator) Next() bool { - // If the iterator was not yet initialized, do it now - if !it.inited { - it.inited = true - return len(it.keys) > 0 - } - // Iterator already initialize, advance it - if len(it.keys) > 0 { - it.keys = it.keys[1:] - it.values = it.values[1:] + // Short circuit if iterator is already exhausted in the forward direction. + if it.index >= len(it.keys) { + return false } - return len(it.keys) > 0 + it.index += 1 + return it.index < len(it.keys) } // Error returns any accumulated error. Exhausting all the key/value pairs @@ -302,24 +305,26 @@ func (it *iterator) Error() error { // should not modify the contents of the returned slice, and its contents may // change on the next call to Next. func (it *iterator) Key() []byte { - if len(it.keys) > 0 { - return []byte(it.keys[0]) + // Short circuit if iterator is not in a valid position + if it.index < 0 || it.index >= len(it.keys) { + return nil } - return nil + return []byte(it.keys[it.index]) } // Value returns the value of the current key/value pair, or nil if done. The // caller should not modify the contents of the returned slice, and its contents // may change on the next call to Next. func (it *iterator) Value() []byte { - if len(it.values) > 0 { - return it.values[0] + // Short circuit if iterator is not in a valid position + if it.index < 0 || it.index >= len(it.keys) { + return nil } - return nil + return it.values[it.index] } // Release releases associated resources. Release should always succeed and can // be called multiple times without causing error. func (it *iterator) Release() { - it.keys, it.values = nil, nil + it.index, it.keys, it.values = -1, nil, nil } diff --git a/params/avalanche_params.go b/params/avalanche_params.go index 70b51d5483..526ebbb870 100644 --- a/params/avalanche_params.go +++ b/params/avalanche_params.go @@ -35,6 +35,11 @@ const ( AtomicTxBaseCost uint64 = 10_000 ) +// Constants for message sizes +const ( + MaxCodeHashesPerRequest = 5 +) + var ( // The atomic gas limit specifies the maximum amount of gas that can be consumed by the atomic // transactions included in a block and is enforced as of ApricotPhase5. Prior to ApricotPhase5, diff --git a/params/config.go b/params/config.go index 9ff2a776d5..7e37ae7146 100644 --- a/params/config.go +++ b/params/config.go @@ -32,9 +32,9 @@ import ( "math/big" "time" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/precompile" "github.com/tenderly/coreth/utils" - "github.com/ethereum/go-ethereum/common" ) // Avalanche ChainIDs @@ -52,90 +52,97 @@ var ( var ( // AvalancheMainnetChainConfig is the configuration for Avalanche Main Network AvalancheMainnetChainConfig = &ChainConfig{ - ChainID: AvalancheMainnetChainID, - HomesteadBlock: big.NewInt(0), - DAOForkBlock: big.NewInt(0), - DAOForkSupport: true, - EIP150Block: big.NewInt(0), - EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - ApricotPhase1BlockTimestamp: big.NewInt(time.Date(2021, time.March, 31, 14, 0, 0, 0, time.UTC).Unix()), - ApricotPhase2BlockTimestamp: big.NewInt(time.Date(2021, time.May, 10, 11, 0, 0, 0, time.UTC).Unix()), - ApricotPhase3BlockTimestamp: big.NewInt(time.Date(2021, time.August, 24, 14, 0, 0, 0, time.UTC).Unix()), - ApricotPhase4BlockTimestamp: big.NewInt(time.Date(2021, time.September, 22, 21, 0, 0, 0, time.UTC).Unix()), - ApricotPhase5BlockTimestamp: big.NewInt(time.Date(2021, time.December, 2, 18, 0, 0, 0, time.UTC).Unix()), - ApricotPhasePre6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 5, 1, 30, 0, 0, time.UTC).Unix()), - ApricotPhase6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()), + ChainID: AvalancheMainnetChainID, + HomesteadBlock: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + ApricotPhase1BlockTimestamp: big.NewInt(time.Date(2021, time.March, 31, 14, 0, 0, 0, time.UTC).Unix()), + ApricotPhase2BlockTimestamp: big.NewInt(time.Date(2021, time.May, 10, 11, 0, 0, 0, time.UTC).Unix()), + ApricotPhase3BlockTimestamp: big.NewInt(time.Date(2021, time.August, 24, 14, 0, 0, 0, time.UTC).Unix()), + ApricotPhase4BlockTimestamp: big.NewInt(time.Date(2021, time.September, 22, 21, 0, 0, 0, time.UTC).Unix()), + ApricotPhase5BlockTimestamp: big.NewInt(time.Date(2021, time.December, 2, 18, 0, 0, 0, time.UTC).Unix()), + ApricotPhasePre6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 5, 1, 30, 0, 0, time.UTC).Unix()), + ApricotPhase6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()), ApricotPhasePost6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 7, 3, 0, 0, 0, time.UTC).Unix()), + BanffBlockTimestamp: big.NewInt(time.Date(2022, time.October, 18, 16, 0, 0, 0, time.UTC).Unix()), + // TODO Add Banff and Cortina timestamps } // AvalancheFujiChainConfig is the configuration for the Fuji Test Network AvalancheFujiChainConfig = &ChainConfig{ - ChainID: AvalancheFujiChainID, - HomesteadBlock: big.NewInt(0), - DAOForkBlock: big.NewInt(0), - DAOForkSupport: true, - EIP150Block: big.NewInt(0), - EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - ApricotPhase1BlockTimestamp: big.NewInt(time.Date(2021, time.March, 26, 14, 0, 0, 0, time.UTC).Unix()), - ApricotPhase2BlockTimestamp: big.NewInt(time.Date(2021, time.May, 5, 14, 0, 0, 0, time.UTC).Unix()), - ApricotPhase3BlockTimestamp: big.NewInt(time.Date(2021, time.August, 16, 19, 0, 0, 0, time.UTC).Unix()), - ApricotPhase4BlockTimestamp: big.NewInt(time.Date(2021, time.September, 16, 21, 0, 0, 0, time.UTC).Unix()), - ApricotPhase5BlockTimestamp: big.NewInt(time.Date(2021, time.November, 24, 15, 0, 0, 0, time.UTC).Unix()), - ApricotPhasePre6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()), - ApricotPhase6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()), + ChainID: AvalancheFujiChainID, + HomesteadBlock: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + ApricotPhase1BlockTimestamp: big.NewInt(time.Date(2021, time.March, 26, 14, 0, 0, 0, time.UTC).Unix()), + ApricotPhase2BlockTimestamp: big.NewInt(time.Date(2021, time.May, 5, 14, 0, 0, 0, time.UTC).Unix()), + ApricotPhase3BlockTimestamp: big.NewInt(time.Date(2021, time.August, 16, 19, 0, 0, 0, time.UTC).Unix()), + ApricotPhase4BlockTimestamp: big.NewInt(time.Date(2021, time.September, 16, 21, 0, 0, 0, time.UTC).Unix()), + ApricotPhase5BlockTimestamp: big.NewInt(time.Date(2021, time.November, 24, 15, 0, 0, 0, time.UTC).Unix()), + ApricotPhasePre6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()), + ApricotPhase6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()), ApricotPhasePost6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 7, 6, 0, 0, 0, time.UTC).Unix()), + BanffBlockTimestamp: big.NewInt(time.Date(2022, time.October, 3, 14, 0, 0, 0, time.UTC).Unix()), + // TODO add Cortina timestamp } // AvalancheLocalChainConfig is the configuration for the Avalanche Local Network AvalancheLocalChainConfig = &ChainConfig{ - ChainID: AvalancheLocalChainID, - HomesteadBlock: big.NewInt(0), - DAOForkBlock: big.NewInt(0), - DAOForkSupport: true, - EIP150Block: big.NewInt(0), - EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - ApricotPhase1BlockTimestamp: big.NewInt(0), - ApricotPhase2BlockTimestamp: big.NewInt(0), - ApricotPhase3BlockTimestamp: big.NewInt(0), - ApricotPhase4BlockTimestamp: big.NewInt(0), - ApricotPhase5BlockTimestamp: big.NewInt(0), - ApricotPhasePre6BlockTimestamp: big.NewInt(0), - ApricotPhase6BlockTimestamp: big.NewInt(0), + ChainID: AvalancheLocalChainID, + HomesteadBlock: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + ApricotPhase1BlockTimestamp: big.NewInt(0), + ApricotPhase2BlockTimestamp: big.NewInt(0), + ApricotPhase3BlockTimestamp: big.NewInt(0), + ApricotPhase4BlockTimestamp: big.NewInt(0), + ApricotPhase5BlockTimestamp: big.NewInt(0), + ApricotPhasePre6BlockTimestamp: big.NewInt(0), + ApricotPhase6BlockTimestamp: big.NewInt(0), ApricotPhasePost6BlockTimestamp: big.NewInt(0), - } - - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil} - TestLaunchConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil, nil} - TestApricotPhase1Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil} - TestApricotPhase2Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil} - TestApricotPhase3Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil} - TestApricotPhase4Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil} - TestApricotPhase5Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil} - TestApricotPhasePre6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil} - TestApricotPhase6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil} - TestBlueberryChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil} - TestRules = TestChainConfig.AvalancheRules(new(big.Int), new(big.Int)) + BanffBlockTimestamp: big.NewInt(0), + } + + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)} + TestLaunchConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil} + TestApricotPhase1Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil, nil, nil} + TestApricotPhase2Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil, nil} + TestApricotPhase3Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil} + TestApricotPhase4Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil} + TestApricotPhase5Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil} + TestApricotPhasePre6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil} + TestApricotPhase6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil} + TestApricotPhasePost6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil} + TestBanffChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil} + TestCortinaChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)} + TestRules = TestChainConfig.AvalancheRules(new(big.Int), new(big.Int)) ) // ChainConfig is the core config which determines the blockchain settings. @@ -181,29 +188,49 @@ type ChainConfig struct { ApricotPhase6BlockTimestamp *big.Int `json:"apricotPhase6BlockTimestamp,omitempty"` // Apricot Phase Post-6 deprecates the NativeAssetCall precompile (soft). (nil = no fork, 0 = already activated) ApricotPhasePost6BlockTimestamp *big.Int `json:"apricotPhasePost6BlockTimestamp,omitempty"` + // Banff TODO comment. (nil = no fork, 0 = already activated) + BanffBlockTimestamp *big.Int `json:"banffBlockTimestamp,omitempty"` + // Cortina TODO comment. (nil = no fork, 0 = already activated) + CortinaBlockTimestamp *big.Int `json:"cortinaBlockTimestamp,omitempty"` } // String implements the fmt.Stringer interface. func (c *ChainConfig) String() string { - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Apricot Phase 1: %v, Apricot Phase 2: %v, Apricot Phase 3: %v, Apricot Phase 4: %v, Apricot Phase 5: %v, Engine: Dummy Consensus Engine}", - c.ChainID, - c.HomesteadBlock, - c.DAOForkBlock, - c.DAOForkSupport, - c.EIP150Block, - c.EIP155Block, - c.EIP158Block, - c.ByzantiumBlock, - c.ConstantinopleBlock, - c.PetersburgBlock, - c.IstanbulBlock, - c.MuirGlacierBlock, - c.ApricotPhase1BlockTimestamp, - c.ApricotPhase2BlockTimestamp, - c.ApricotPhase3BlockTimestamp, - c.ApricotPhase4BlockTimestamp, - c.ApricotPhase5BlockTimestamp, - ) + var banner string + + banner += fmt.Sprintf("Chain ID: %v\n", c.ChainID) + banner += "Consensus: Dummy Consensus Engine\n\n" + + // Create a list of forks with a short description of them. Forks that only + // makes sense for mainnet should be optional at printing to avoid bloating + // the output for testnets and private networks. + banner += "Hard Forks:\n" + banner += fmt.Sprintf(" - Homestead: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/homestead.md)\n", c.HomesteadBlock) + if c.DAOForkBlock != nil { + banner += fmt.Sprintf(" - DAO Fork: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/dao-fork.md)\n", c.DAOForkBlock) + } + banner += fmt.Sprintf(" - Tangerine Whistle (EIP 150): %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/tangerine-whistle.md)\n", c.EIP150Block) + banner += fmt.Sprintf(" - Spurious Dragon/1 (EIP 155): %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/spurious-dragon.md)\n", c.EIP155Block) + banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/spurious-dragon.md)\n", c.EIP155Block) + banner += fmt.Sprintf(" - Byzantium: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/byzantium.md)\n", c.ByzantiumBlock) + banner += fmt.Sprintf(" - Constantinople: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/constantinople.md)\n", c.ConstantinopleBlock) + banner += fmt.Sprintf(" - Petersburg: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/petersburg.md)\n", c.PetersburgBlock) + banner += fmt.Sprintf(" - Istanbul: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/istanbul.md)\n", c.IstanbulBlock) + if c.MuirGlacierBlock != nil { + banner += fmt.Sprintf(" - Muir Glacier: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/muir-glacier.md)\n", c.MuirGlacierBlock) + } + banner += fmt.Sprintf(" - Apricot Phase 1 Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.3.0)\n", c.ApricotPhase1BlockTimestamp) + banner += fmt.Sprintf(" - Apricot Phase 2 Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.4.0)\n", c.ApricotPhase2BlockTimestamp) + banner += fmt.Sprintf(" - Apricot Phase 3 Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.5.0)\n", c.ApricotPhase3BlockTimestamp) + banner += fmt.Sprintf(" - Apricot Phase 4 Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.6.0)\n", c.ApricotPhase4BlockTimestamp) + banner += fmt.Sprintf(" - Apricot Phase 5 Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.7.0)\n", c.ApricotPhase5BlockTimestamp) + banner += fmt.Sprintf(" - Apricot Phase P6 Timestamp %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.8.0)\n", c.ApricotPhasePre6BlockTimestamp) + banner += fmt.Sprintf(" - Apricot Phase 6 Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.8.0)\n", c.ApricotPhase6BlockTimestamp) + banner += fmt.Sprintf(" - Apricot Phase Post-6 Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.8.0\n", c.ApricotPhasePost6BlockTimestamp) + banner += fmt.Sprintf(" - Banff Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.9.0)\n", c.BanffBlockTimestamp) + banner += fmt.Sprintf(" - Cortina Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.10.0)\n", c.CortinaBlockTimestamp) + banner += "\n" + return banner } // IsHomestead returns whether num is either equal to the homestead block or greater. @@ -308,21 +335,33 @@ func (c *ChainConfig) IsApricotPhasePost6(blockTimestamp *big.Int) bool { return utils.IsForked(c.ApricotPhasePost6BlockTimestamp, blockTimestamp) } +// IsBanff returns whether [blockTimestamp] represents a block +// with a timestamp after the Banff upgrade time. +func (c *ChainConfig) IsBanff(blockTimestamp *big.Int) bool { + return utils.IsForked(c.BanffBlockTimestamp, blockTimestamp) +} + +// IsCortina returns whether [blockTimestamp] represents a block +// with a timestamp after the Cortina upgrade time. +func (c *ChainConfig) IsCortina(blockTimestamp *big.Int) bool { + return utils.IsForked(c.CortinaBlockTimestamp, blockTimestamp) +} + // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, timestamp uint64) *ConfigCompatError { - bhead := new(big.Int).SetUint64(height) - bheadTimestamp := new(big.Int).SetUint64(timestamp) + bNumber := new(big.Int).SetUint64(height) + bTimestamp := new(big.Int).SetUint64(timestamp) // Iterate checkCompatible to find the lowest conflict. var lasterr *ConfigCompatError for { - err := c.checkCompatible(newcfg, bhead, bheadTimestamp) + err := c.checkCompatible(newcfg, bNumber, bTimestamp) if err == nil || (lasterr != nil && err.RewindTo == lasterr.RewindTo) { break } lasterr = err - bhead.SetUint64(err.RewindTo) + bNumber.SetUint64(err.RewindTo) } return lasterr } @@ -386,6 +425,8 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "apricotPhasePre6BlockTimestamp", block: c.ApricotPhasePre6BlockTimestamp}, {name: "apricotPhase6BlockTimestamp", block: c.ApricotPhase6BlockTimestamp}, {name: "apricotPhasePost6BlockTimestamp", block: c.ApricotPhasePost6BlockTimestamp}, + {name: "banffBlockTimestamp", block: c.BanffBlockTimestamp}, + {name: "cortinaBlockTimestamp", block: c.CortinaBlockTimestamp}, } { if lastFork.name != "" { // Next one must be higher number @@ -412,69 +453,71 @@ func (c *ChainConfig) CheckConfigForkOrder() error { return nil } -func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headHeight *big.Int, headTimestamp *big.Int) *ConfigCompatError { - if isForkIncompatible(c.HomesteadBlock, newcfg.HomesteadBlock, headHeight) { +func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, lastHeight *big.Int, lastTimestamp *big.Int) *ConfigCompatError { + if isForkIncompatible(c.HomesteadBlock, newcfg.HomesteadBlock, lastHeight) { return newCompatError("Homestead fork block", c.HomesteadBlock, newcfg.HomesteadBlock) } - if isForkIncompatible(c.DAOForkBlock, newcfg.DAOForkBlock, headHeight) { + if isForkIncompatible(c.DAOForkBlock, newcfg.DAOForkBlock, lastHeight) { return newCompatError("DAO fork block", c.DAOForkBlock, newcfg.DAOForkBlock) } - if c.IsDAOFork(headHeight) && c.DAOForkSupport != newcfg.DAOForkSupport { + if c.IsDAOFork(lastHeight) && c.DAOForkSupport != newcfg.DAOForkSupport { return newCompatError("DAO fork support flag", c.DAOForkBlock, newcfg.DAOForkBlock) } - if isForkIncompatible(c.EIP150Block, newcfg.EIP150Block, headHeight) { + if isForkIncompatible(c.EIP150Block, newcfg.EIP150Block, lastHeight) { return newCompatError("EIP150 fork block", c.EIP150Block, newcfg.EIP150Block) } - if isForkIncompatible(c.EIP155Block, newcfg.EIP155Block, headHeight) { + if isForkIncompatible(c.EIP155Block, newcfg.EIP155Block, lastHeight) { return newCompatError("EIP155 fork block", c.EIP155Block, newcfg.EIP155Block) } - if isForkIncompatible(c.EIP158Block, newcfg.EIP158Block, headHeight) { + if isForkIncompatible(c.EIP158Block, newcfg.EIP158Block, lastHeight) { return newCompatError("EIP158 fork block", c.EIP158Block, newcfg.EIP158Block) } - if c.IsEIP158(headHeight) && !configNumEqual(c.ChainID, newcfg.ChainID) { + if c.IsEIP158(lastHeight) && !configNumEqual(c.ChainID, newcfg.ChainID) { return newCompatError("EIP158 chain ID", c.EIP158Block, newcfg.EIP158Block) } - if isForkIncompatible(c.ByzantiumBlock, newcfg.ByzantiumBlock, headHeight) { + if isForkIncompatible(c.ByzantiumBlock, newcfg.ByzantiumBlock, lastHeight) { return newCompatError("Byzantium fork block", c.ByzantiumBlock, newcfg.ByzantiumBlock) } - if isForkIncompatible(c.ConstantinopleBlock, newcfg.ConstantinopleBlock, headHeight) { + if isForkIncompatible(c.ConstantinopleBlock, newcfg.ConstantinopleBlock, lastHeight) { return newCompatError("Constantinople fork block", c.ConstantinopleBlock, newcfg.ConstantinopleBlock) } - if isForkIncompatible(c.PetersburgBlock, newcfg.PetersburgBlock, headHeight) { + if isForkIncompatible(c.PetersburgBlock, newcfg.PetersburgBlock, lastHeight) { // the only case where we allow Petersburg to be set in the past is if it is equal to Constantinople // mainly to satisfy fork ordering requirements which state that Petersburg fork be set if Constantinople fork is set - if isForkIncompatible(c.ConstantinopleBlock, newcfg.PetersburgBlock, headHeight) { + if isForkIncompatible(c.ConstantinopleBlock, newcfg.PetersburgBlock, lastHeight) { return newCompatError("Petersburg fork block", c.PetersburgBlock, newcfg.PetersburgBlock) } } - if isForkIncompatible(c.IstanbulBlock, newcfg.IstanbulBlock, headHeight) { + if isForkIncompatible(c.IstanbulBlock, newcfg.IstanbulBlock, lastHeight) { return newCompatError("Istanbul fork block", c.IstanbulBlock, newcfg.IstanbulBlock) } - if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, headHeight) { + if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, lastHeight) { return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock) } - if isForkIncompatible(c.ApricotPhase1BlockTimestamp, newcfg.ApricotPhase1BlockTimestamp, headTimestamp) { + if isForkIncompatible(c.ApricotPhase1BlockTimestamp, newcfg.ApricotPhase1BlockTimestamp, lastTimestamp) { return newCompatError("ApricotPhase1 fork block timestamp", c.ApricotPhase1BlockTimestamp, newcfg.ApricotPhase1BlockTimestamp) } - if isForkIncompatible(c.ApricotPhase2BlockTimestamp, newcfg.ApricotPhase2BlockTimestamp, headTimestamp) { + if isForkIncompatible(c.ApricotPhase2BlockTimestamp, newcfg.ApricotPhase2BlockTimestamp, lastTimestamp) { return newCompatError("ApricotPhase2 fork block timestamp", c.ApricotPhase2BlockTimestamp, newcfg.ApricotPhase2BlockTimestamp) } - if isForkIncompatible(c.ApricotPhase3BlockTimestamp, newcfg.ApricotPhase3BlockTimestamp, headTimestamp) { + if isForkIncompatible(c.ApricotPhase3BlockTimestamp, newcfg.ApricotPhase3BlockTimestamp, lastTimestamp) { return newCompatError("ApricotPhase3 fork block timestamp", c.ApricotPhase3BlockTimestamp, newcfg.ApricotPhase3BlockTimestamp) } - if isForkIncompatible(c.ApricotPhase4BlockTimestamp, newcfg.ApricotPhase4BlockTimestamp, headTimestamp) { + if isForkIncompatible(c.ApricotPhase4BlockTimestamp, newcfg.ApricotPhase4BlockTimestamp, lastTimestamp) { return newCompatError("ApricotPhase4 fork block timestamp", c.ApricotPhase4BlockTimestamp, newcfg.ApricotPhase4BlockTimestamp) } - if isForkIncompatible(c.ApricotPhase5BlockTimestamp, newcfg.ApricotPhase5BlockTimestamp, headTimestamp) { + if isForkIncompatible(c.ApricotPhase5BlockTimestamp, newcfg.ApricotPhase5BlockTimestamp, lastTimestamp) { return newCompatError("ApricotPhase5 fork block timestamp", c.ApricotPhase5BlockTimestamp, newcfg.ApricotPhase5BlockTimestamp) } - if isForkIncompatible(c.ApricotPhasePre6BlockTimestamp, newcfg.ApricotPhasePre6BlockTimestamp, headTimestamp) { - return newCompatError("ApricotPhasePre6 fork block timestamp", c.ApricotPhasePre6BlockTimestamp, newcfg.ApricotPhasePre6BlockTimestamp) - } - if isForkIncompatible(c.ApricotPhase6BlockTimestamp, newcfg.ApricotPhase6BlockTimestamp, headTimestamp) { - return newCompatError("ApricotPhase6 fork block timestamp", c.ApricotPhase6BlockTimestamp, newcfg.ApricotPhase6BlockTimestamp) - } + // TODO: add Phase 6 checks + // TODO activate isForkIncompatible checks + // if isForkIncompatible(c.BanffBlockTimestamp, newcfg.BanffBlockTimestamp, lastTimestamp) { + // return newCompatError("Banff fork block timestamp", c.BanffBlockTimestamp, newcfg.BanffBlockTimestamp) + // } + // if isForkIncompatible(c.CortinaBlockTimestamp, newcfg.CortinaBlockTimestamp, lastTimestamp) { + // return newCompatError("Cortina fork block timestamp", c.CortinaBlockTimestamp, newcfg.CortinaBlockTimestamp) + // } return nil } @@ -538,6 +581,8 @@ type Rules struct { // Rules for Avalanche releases IsApricotPhase1, IsApricotPhase2, IsApricotPhase3, IsApricotPhase4, IsApricotPhase5 bool IsApricotPhasePre6, IsApricotPhase6, IsApricotPhasePost6 bool + IsBanff bool + IsCortina bool // Precompiles maps addresses to stateful precompiled contracts that are enabled // for this rule set. @@ -578,6 +623,8 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { rules.IsApricotPhasePre6 = c.IsApricotPhasePre6(blockTimestamp) rules.IsApricotPhase6 = c.IsApricotPhase6(blockTimestamp) rules.IsApricotPhasePost6 = c.IsApricotPhasePost6(blockTimestamp) + rules.IsBanff = c.IsBanff(blockTimestamp) + rules.IsCortina = c.IsCortina(blockTimestamp) // Initialize the stateful precompiles that should be enabled at [blockTimestamp]. rules.Precompiles = make(map[common.Address]precompile.StatefulPrecompiledContract) @@ -612,4 +659,3 @@ func (c *ChainConfig) CheckConfigurePrecompiles(parentTimestamp *big.Int, blockC precompile.CheckConfigure(c, parentTimestamp, blockContext, config, statedb) } } - diff --git a/params/config_test.go b/params/config_test.go index 62fea4beb2..0f242adc1c 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -34,25 +34,25 @@ import ( func TestCheckCompatible(t *testing.T) { type test struct { - stored, new *ChainConfig - headHeight, headTimestamp uint64 - wantErr *ConfigCompatError + stored, new *ChainConfig + blockHeight, blockTimestamp uint64 + wantErr *ConfigCompatError } tests := []test{ - {stored: TestChainConfig, new: TestChainConfig, headHeight: 0, headTimestamp: 0, wantErr: nil}, - {stored: TestChainConfig, new: TestChainConfig, headHeight: 100, headTimestamp: 1000, wantErr: nil}, + {stored: TestChainConfig, new: TestChainConfig, blockHeight: 0, blockTimestamp: 0, wantErr: nil}, + {stored: TestChainConfig, new: TestChainConfig, blockHeight: 100, blockTimestamp: 1000, wantErr: nil}, { - stored: &ChainConfig{EIP150Block: big.NewInt(10)}, - new: &ChainConfig{EIP150Block: big.NewInt(20)}, - headHeight: 9, - headTimestamp: 90, - wantErr: nil, + stored: &ChainConfig{EIP150Block: big.NewInt(10)}, + new: &ChainConfig{EIP150Block: big.NewInt(20)}, + blockHeight: 9, + blockTimestamp: 90, + wantErr: nil, }, { - stored: TestChainConfig, - new: &ChainConfig{HomesteadBlock: nil}, - headHeight: 3, - headTimestamp: 30, + stored: TestChainConfig, + new: &ChainConfig{HomesteadBlock: nil}, + blockHeight: 3, + blockTimestamp: 30, wantErr: &ConfigCompatError{ What: "Homestead fork block", StoredConfig: big.NewInt(0), @@ -61,10 +61,10 @@ func TestCheckCompatible(t *testing.T) { }, }, { - stored: TestChainConfig, - new: &ChainConfig{HomesteadBlock: big.NewInt(1)}, - headHeight: 3, - headTimestamp: 30, + stored: TestChainConfig, + new: &ChainConfig{HomesteadBlock: big.NewInt(1)}, + blockHeight: 3, + blockTimestamp: 30, wantErr: &ConfigCompatError{ What: "Homestead fork block", StoredConfig: big.NewInt(0), @@ -73,10 +73,10 @@ func TestCheckCompatible(t *testing.T) { }, }, { - stored: &ChainConfig{HomesteadBlock: big.NewInt(30), EIP150Block: big.NewInt(10)}, - new: &ChainConfig{HomesteadBlock: big.NewInt(25), EIP150Block: big.NewInt(20)}, - headHeight: 25, - headTimestamp: 250, + stored: &ChainConfig{HomesteadBlock: big.NewInt(30), EIP150Block: big.NewInt(10)}, + new: &ChainConfig{HomesteadBlock: big.NewInt(25), EIP150Block: big.NewInt(20)}, + blockHeight: 25, + blockTimestamp: 250, wantErr: &ConfigCompatError{ What: "EIP150 fork block", StoredConfig: big.NewInt(10), @@ -85,17 +85,17 @@ func TestCheckCompatible(t *testing.T) { }, }, { - stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)}, - new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(30)}, - headHeight: 40, - headTimestamp: 400, - wantErr: nil, + stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)}, + new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(30)}, + blockHeight: 40, + blockTimestamp: 400, + wantErr: nil, }, { - stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)}, - new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(31)}, - headHeight: 40, - headTimestamp: 400, + stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)}, + new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(31)}, + blockHeight: 40, + blockTimestamp: 400, wantErr: &ConfigCompatError{ What: "Petersburg fork block", StoredConfig: nil, @@ -104,10 +104,10 @@ func TestCheckCompatible(t *testing.T) { }, }, { - stored: TestChainConfig, - new: TestApricotPhase4Config, - headHeight: 0, - headTimestamp: 0, + stored: TestChainConfig, + new: TestApricotPhase4Config, + blockHeight: 0, + blockTimestamp: 0, wantErr: &ConfigCompatError{ What: "ApricotPhase5 fork block timestamp", StoredConfig: big.NewInt(0), @@ -116,10 +116,10 @@ func TestCheckCompatible(t *testing.T) { }, }, { - stored: TestChainConfig, - new: TestApricotPhase4Config, - headHeight: 10, - headTimestamp: 100, + stored: TestChainConfig, + new: TestApricotPhase4Config, + blockHeight: 10, + blockTimestamp: 100, wantErr: &ConfigCompatError{ What: "ApricotPhase5 fork block timestamp", StoredConfig: big.NewInt(0), @@ -130,9 +130,9 @@ func TestCheckCompatible(t *testing.T) { } for _, test := range tests { - err := test.stored.CheckCompatible(test.new, test.headHeight, test.headTimestamp) + err := test.stored.CheckCompatible(test.new, test.blockHeight, test.blockTimestamp) if !reflect.DeepEqual(err, test.wantErr) { - t.Errorf("error mismatch:\nstored: %v\nnew: %v\nheadHeight: %v\nerr: %v\nwant: %v", test.stored, test.new, test.headHeight, err, test.wantErr) + t.Errorf("error mismatch:\nstored: %v\nnew: %v\nblockHeight: %v\nerr: %v\nwant: %v", test.stored, test.new, test.blockHeight, err, test.wantErr) } } } diff --git a/params/version.go b/params/version.go index 6e4d6e3398..ff5c122afb 100644 --- a/params/version.go +++ b/params/version.go @@ -33,7 +33,7 @@ import ( const ( VersionMajor = 1 // Major version component of the current release VersionMinor = 10 // Minor version component of the current release - VersionPatch = 16 // Patch version component of the current release + VersionPatch = 25 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string ) @@ -50,3 +50,11 @@ var VersionWithMeta = func() string { } return v }() + +func VersionWithCommit(gitCommit, gitDate string) string { + vsn := VersionWithMeta + if len(gitCommit) >= 8 { + vsn += "-" + gitCommit[:8] + } + return vsn +} diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 596efe5f57..6ba3d197bc 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/params" diff --git a/plugin/evm/gossiper.go b/plugin/evm/gossiper.go index 8d305177ef..43306eb72c 100644 --- a/plugin/evm/gossiper.go +++ b/plugin/evm/gossiper.go @@ -20,7 +20,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" "github.com/tenderly/coreth/core" "github.com/tenderly/coreth/core/state" diff --git a/plugin/evm/gossiper_eth_gossiping_test.go b/plugin/evm/gossiper_eth_gossiping_test.go index d364bb6cb3..3b3ad44086 100644 --- a/plugin/evm/gossiper_eth_gossiping_test.go +++ b/plugin/evm/gossiper_eth_gossiping_test.go @@ -16,7 +16,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" "github.com/stretchr/testify/assert" diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index af68021ef1..1c2958b886 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -23,6 +23,8 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto" "github.com/ava-labs/avalanchego/utils/units" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/accounts/keystore" coreth "github.com/tenderly/coreth/chain" "github.com/tenderly/coreth/consensus/dummy" @@ -32,12 +34,10 @@ import ( "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/metrics" "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/rlp" statesyncclient "github.com/tenderly/coreth/sync/client" "github.com/tenderly/coreth/sync/statesync" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) func TestSkipStateSync(t *testing.T) { diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 7d1695546e..51d96772ce 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -47,10 +47,10 @@ import ( // inside of cmd/geth. _ "github.com/tenderly/coreth/eth/tracers/native" - "github.com/tenderly/coreth/metrics" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/metrics" + "github.com/tenderly/coreth/rlp" avalancheRPC "github.com/gorilla/rpc/v2" diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index a429d9c273..c03dbbe40a 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -17,10 +17,10 @@ import ( "testing" "time" - "github.com/tenderly/coreth/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" + "github.com/tenderly/coreth/trie" "github.com/stretchr/testify/assert" diff --git a/rlp/decode.go b/rlp/decode.go new file mode 100644 index 0000000000..02277ba51e --- /dev/null +++ b/rlp/decode.go @@ -0,0 +1,1120 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp + +import ( + "bufio" + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + "reflect" + "strings" + "sync" + + "github.com/tenderly/coreth/rlp/internal/rlpstruct" +) + +//lint:ignore ST1012 EOL is not an error. + +// EOL is returned when the end of the current list +// has been reached during streaming. +var EOL = errors.New("rlp: end of list") + +var ( + ErrExpectedString = errors.New("rlp: expected String or Byte") + ErrExpectedList = errors.New("rlp: expected List") + ErrCanonInt = errors.New("rlp: non-canonical integer format") + ErrCanonSize = errors.New("rlp: non-canonical size information") + ErrElemTooLarge = errors.New("rlp: element is larger than containing list") + ErrValueTooLarge = errors.New("rlp: value size exceeds available input length") + ErrMoreThanOneValue = errors.New("rlp: input contains more than one value") + + // internal errors + errNotInList = errors.New("rlp: call of ListEnd outside of any list") + errNotAtEOL = errors.New("rlp: call of ListEnd not positioned at EOL") + errUintOverflow = errors.New("rlp: uint overflow") + errNoPointer = errors.New("rlp: interface given to Decode must be a pointer") + errDecodeIntoNil = errors.New("rlp: pointer given to Decode must not be nil") + + streamPool = sync.Pool{ + New: func() interface{} { return new(Stream) }, + } +) + +// Decoder is implemented by types that require custom RLP decoding rules or need to decode +// into private fields. +// +// The DecodeRLP method should read one value from the given Stream. It is not forbidden to +// read less or more, but it might be confusing. +type Decoder interface { + DecodeRLP(*Stream) error +} + +// Decode parses RLP-encoded data from r and stores the result in the value pointed to by +// val. Please see package-level documentation for the decoding rules. Val must be a +// non-nil pointer. +// +// If r does not implement ByteReader, Decode will do its own buffering. +// +// Note that Decode does not set an input limit for all readers and may be vulnerable to +// panics cause by huge value sizes. If you need an input limit, use +// +// NewStream(r, limit).Decode(val) +func Decode(r io.Reader, val interface{}) error { + stream := streamPool.Get().(*Stream) + defer streamPool.Put(stream) + + stream.Reset(r, 0) + return stream.Decode(val) +} + +// DecodeBytes parses RLP data from b into val. Please see package-level documentation for +// the decoding rules. The input must contain exactly one value and no trailing data. +func DecodeBytes(b []byte, val interface{}) error { + r := bytes.NewReader(b) + + stream := streamPool.Get().(*Stream) + defer streamPool.Put(stream) + + stream.Reset(r, uint64(len(b))) + if err := stream.Decode(val); err != nil { + return err + } + if r.Len() > 0 { + return ErrMoreThanOneValue + } + return nil +} + +type decodeError struct { + msg string + typ reflect.Type + ctx []string +} + +func (err *decodeError) Error() string { + ctx := "" + if len(err.ctx) > 0 { + ctx = ", decoding into " + for i := len(err.ctx) - 1; i >= 0; i-- { + ctx += err.ctx[i] + } + } + return fmt.Sprintf("rlp: %s for %v%s", err.msg, err.typ, ctx) +} + +func wrapStreamError(err error, typ reflect.Type) error { + switch err { + case ErrCanonInt: + return &decodeError{msg: "non-canonical integer (leading zero bytes)", typ: typ} + case ErrCanonSize: + return &decodeError{msg: "non-canonical size information", typ: typ} + case ErrExpectedList: + return &decodeError{msg: "expected input list", typ: typ} + case ErrExpectedString: + return &decodeError{msg: "expected input string or byte", typ: typ} + case errUintOverflow: + return &decodeError{msg: "input string too long", typ: typ} + case errNotAtEOL: + return &decodeError{msg: "input list has too many elements", typ: typ} + } + return err +} + +func addErrorContext(err error, ctx string) error { + if decErr, ok := err.(*decodeError); ok { + decErr.ctx = append(decErr.ctx, ctx) + } + return err +} + +var ( + decoderInterface = reflect.TypeOf(new(Decoder)).Elem() + bigInt = reflect.TypeOf(big.Int{}) +) + +func makeDecoder(typ reflect.Type, tags rlpstruct.Tags) (dec decoder, err error) { + kind := typ.Kind() + switch { + case typ == rawValueType: + return decodeRawValue, nil + case typ.AssignableTo(reflect.PtrTo(bigInt)): + return decodeBigInt, nil + case typ.AssignableTo(bigInt): + return decodeBigIntNoPtr, nil + case kind == reflect.Ptr: + return makePtrDecoder(typ, tags) + case reflect.PtrTo(typ).Implements(decoderInterface): + return decodeDecoder, nil + case isUint(kind): + return decodeUint, nil + case kind == reflect.Bool: + return decodeBool, nil + case kind == reflect.String: + return decodeString, nil + case kind == reflect.Slice || kind == reflect.Array: + return makeListDecoder(typ, tags) + case kind == reflect.Struct: + return makeStructDecoder(typ) + case kind == reflect.Interface: + return decodeInterface, nil + default: + return nil, fmt.Errorf("rlp: type %v is not RLP-serializable", typ) + } +} + +func decodeRawValue(s *Stream, val reflect.Value) error { + r, err := s.Raw() + if err != nil { + return err + } + val.SetBytes(r) + return nil +} + +func decodeUint(s *Stream, val reflect.Value) error { + typ := val.Type() + num, err := s.uint(typ.Bits()) + if err != nil { + return wrapStreamError(err, val.Type()) + } + val.SetUint(num) + return nil +} + +func decodeBool(s *Stream, val reflect.Value) error { + b, err := s.Bool() + if err != nil { + return wrapStreamError(err, val.Type()) + } + val.SetBool(b) + return nil +} + +func decodeString(s *Stream, val reflect.Value) error { + b, err := s.Bytes() + if err != nil { + return wrapStreamError(err, val.Type()) + } + val.SetString(string(b)) + return nil +} + +func decodeBigIntNoPtr(s *Stream, val reflect.Value) error { + return decodeBigInt(s, val.Addr()) +} + +func decodeBigInt(s *Stream, val reflect.Value) error { + i := val.Interface().(*big.Int) + if i == nil { + i = new(big.Int) + val.Set(reflect.ValueOf(i)) + } + + err := s.decodeBigInt(i) + if err != nil { + return wrapStreamError(err, val.Type()) + } + return nil +} + +func makeListDecoder(typ reflect.Type, tag rlpstruct.Tags) (decoder, error) { + etype := typ.Elem() + if etype.Kind() == reflect.Uint8 && !reflect.PtrTo(etype).Implements(decoderInterface) { + if typ.Kind() == reflect.Array { + return decodeByteArray, nil + } + return decodeByteSlice, nil + } + etypeinfo := theTC.infoWhileGenerating(etype, rlpstruct.Tags{}) + if etypeinfo.decoderErr != nil { + return nil, etypeinfo.decoderErr + } + var dec decoder + switch { + case typ.Kind() == reflect.Array: + dec = func(s *Stream, val reflect.Value) error { + return decodeListArray(s, val, etypeinfo.decoder) + } + case tag.Tail: + // A slice with "tail" tag can occur as the last field + // of a struct and is supposed to swallow all remaining + // list elements. The struct decoder already called s.List, + // proceed directly to decoding the elements. + dec = func(s *Stream, val reflect.Value) error { + return decodeSliceElems(s, val, etypeinfo.decoder) + } + default: + dec = func(s *Stream, val reflect.Value) error { + return decodeListSlice(s, val, etypeinfo.decoder) + } + } + return dec, nil +} + +func decodeListSlice(s *Stream, val reflect.Value, elemdec decoder) error { + size, err := s.List() + if err != nil { + return wrapStreamError(err, val.Type()) + } + if size == 0 { + val.Set(reflect.MakeSlice(val.Type(), 0, 0)) + return s.ListEnd() + } + if err := decodeSliceElems(s, val, elemdec); err != nil { + return err + } + return s.ListEnd() +} + +func decodeSliceElems(s *Stream, val reflect.Value, elemdec decoder) error { + i := 0 + for ; ; i++ { + // grow slice if necessary + if i >= val.Cap() { + newcap := val.Cap() + val.Cap()/2 + if newcap < 4 { + newcap = 4 + } + newv := reflect.MakeSlice(val.Type(), val.Len(), newcap) + reflect.Copy(newv, val) + val.Set(newv) + } + if i >= val.Len() { + val.SetLen(i + 1) + } + // decode into element + if err := elemdec(s, val.Index(i)); err == EOL { + break + } else if err != nil { + return addErrorContext(err, fmt.Sprint("[", i, "]")) + } + } + if i < val.Len() { + val.SetLen(i) + } + return nil +} + +func decodeListArray(s *Stream, val reflect.Value, elemdec decoder) error { + if _, err := s.List(); err != nil { + return wrapStreamError(err, val.Type()) + } + vlen := val.Len() + i := 0 + for ; i < vlen; i++ { + if err := elemdec(s, val.Index(i)); err == EOL { + break + } else if err != nil { + return addErrorContext(err, fmt.Sprint("[", i, "]")) + } + } + if i < vlen { + return &decodeError{msg: "input list has too few elements", typ: val.Type()} + } + return wrapStreamError(s.ListEnd(), val.Type()) +} + +func decodeByteSlice(s *Stream, val reflect.Value) error { + b, err := s.Bytes() + if err != nil { + return wrapStreamError(err, val.Type()) + } + val.SetBytes(b) + return nil +} + +func decodeByteArray(s *Stream, val reflect.Value) error { + kind, size, err := s.Kind() + if err != nil { + return err + } + slice := byteArrayBytes(val, val.Len()) + switch kind { + case Byte: + if len(slice) == 0 { + return &decodeError{msg: "input string too long", typ: val.Type()} + } else if len(slice) > 1 { + return &decodeError{msg: "input string too short", typ: val.Type()} + } + slice[0] = s.byteval + s.kind = -1 + case String: + if uint64(len(slice)) < size { + return &decodeError{msg: "input string too long", typ: val.Type()} + } + if uint64(len(slice)) > size { + return &decodeError{msg: "input string too short", typ: val.Type()} + } + if err := s.readFull(slice); err != nil { + return err + } + // Reject cases where single byte encoding should have been used. + if size == 1 && slice[0] < 128 { + return wrapStreamError(ErrCanonSize, val.Type()) + } + case List: + return wrapStreamError(ErrExpectedString, val.Type()) + } + return nil +} + +func makeStructDecoder(typ reflect.Type) (decoder, error) { + fields, err := structFields(typ) + if err != nil { + return nil, err + } + for _, f := range fields { + if f.info.decoderErr != nil { + return nil, structFieldError{typ, f.index, f.info.decoderErr} + } + } + dec := func(s *Stream, val reflect.Value) (err error) { + if _, err := s.List(); err != nil { + return wrapStreamError(err, typ) + } + for i, f := range fields { + err := f.info.decoder(s, val.Field(f.index)) + if err == EOL { + if f.optional { + // The field is optional, so reaching the end of the list before + // reaching the last field is acceptable. All remaining undecoded + // fields are zeroed. + zeroFields(val, fields[i:]) + break + } + return &decodeError{msg: "too few elements", typ: typ} + } else if err != nil { + return addErrorContext(err, "."+typ.Field(f.index).Name) + } + } + return wrapStreamError(s.ListEnd(), typ) + } + return dec, nil +} + +func zeroFields(structval reflect.Value, fields []field) { + for _, f := range fields { + fv := structval.Field(f.index) + fv.Set(reflect.Zero(fv.Type())) + } +} + +// makePtrDecoder creates a decoder that decodes into the pointer's element type. +func makePtrDecoder(typ reflect.Type, tag rlpstruct.Tags) (decoder, error) { + etype := typ.Elem() + etypeinfo := theTC.infoWhileGenerating(etype, rlpstruct.Tags{}) + switch { + case etypeinfo.decoderErr != nil: + return nil, etypeinfo.decoderErr + case !tag.NilOK: + return makeSimplePtrDecoder(etype, etypeinfo), nil + default: + return makeNilPtrDecoder(etype, etypeinfo, tag), nil + } +} + +func makeSimplePtrDecoder(etype reflect.Type, etypeinfo *typeinfo) decoder { + return func(s *Stream, val reflect.Value) (err error) { + newval := val + if val.IsNil() { + newval = reflect.New(etype) + } + if err = etypeinfo.decoder(s, newval.Elem()); err == nil { + val.Set(newval) + } + return err + } +} + +// makeNilPtrDecoder creates a decoder that decodes empty values as nil. Non-empty +// values are decoded into a value of the element type, just like makePtrDecoder does. +// +// This decoder is used for pointer-typed struct fields with struct tag "nil". +func makeNilPtrDecoder(etype reflect.Type, etypeinfo *typeinfo, ts rlpstruct.Tags) decoder { + typ := reflect.PtrTo(etype) + nilPtr := reflect.Zero(typ) + + // Determine the value kind that results in nil pointer. + nilKind := typeNilKind(etype, ts) + + return func(s *Stream, val reflect.Value) (err error) { + kind, size, err := s.Kind() + if err != nil { + val.Set(nilPtr) + return wrapStreamError(err, typ) + } + // Handle empty values as a nil pointer. + if kind != Byte && size == 0 { + if kind != nilKind { + return &decodeError{ + msg: fmt.Sprintf("wrong kind of empty value (got %v, want %v)", kind, nilKind), + typ: typ, + } + } + // rearm s.Kind. This is important because the input + // position must advance to the next value even though + // we don't read anything. + s.kind = -1 + val.Set(nilPtr) + return nil + } + newval := val + if val.IsNil() { + newval = reflect.New(etype) + } + if err = etypeinfo.decoder(s, newval.Elem()); err == nil { + val.Set(newval) + } + return err + } +} + +var ifsliceType = reflect.TypeOf([]interface{}{}) + +func decodeInterface(s *Stream, val reflect.Value) error { + if val.Type().NumMethod() != 0 { + return fmt.Errorf("rlp: type %v is not RLP-serializable", val.Type()) + } + kind, _, err := s.Kind() + if err != nil { + return err + } + if kind == List { + slice := reflect.New(ifsliceType).Elem() + if err := decodeListSlice(s, slice, decodeInterface); err != nil { + return err + } + val.Set(slice) + } else { + b, err := s.Bytes() + if err != nil { + return err + } + val.Set(reflect.ValueOf(b)) + } + return nil +} + +func decodeDecoder(s *Stream, val reflect.Value) error { + return val.Addr().Interface().(Decoder).DecodeRLP(s) +} + +// Kind represents the kind of value contained in an RLP stream. +type Kind int8 + +const ( + Byte Kind = iota + String + List +) + +func (k Kind) String() string { + switch k { + case Byte: + return "Byte" + case String: + return "String" + case List: + return "List" + default: + return fmt.Sprintf("Unknown(%d)", k) + } +} + +// ByteReader must be implemented by any input reader for a Stream. It +// is implemented by e.g. bufio.Reader and bytes.Reader. +type ByteReader interface { + io.Reader + io.ByteReader +} + +// Stream can be used for piecemeal decoding of an input stream. This +// is useful if the input is very large or if the decoding rules for a +// type depend on the input structure. Stream does not keep an +// internal buffer. After decoding a value, the input reader will be +// positioned just before the type information for the next value. +// +// When decoding a list and the input position reaches the declared +// length of the list, all operations will return error EOL. +// The end of the list must be acknowledged using ListEnd to continue +// reading the enclosing list. +// +// Stream is not safe for concurrent use. +type Stream struct { + r ByteReader + + remaining uint64 // number of bytes remaining to be read from r + size uint64 // size of value ahead + kinderr error // error from last readKind + stack []uint64 // list sizes + uintbuf [32]byte // auxiliary buffer for integer decoding + kind Kind // kind of value ahead + byteval byte // value of single byte in type tag + limited bool // true if input limit is in effect +} + +// NewStream creates a new decoding stream reading from r. +// +// If r implements the ByteReader interface, Stream will +// not introduce any buffering. +// +// For non-toplevel values, Stream returns ErrElemTooLarge +// for values that do not fit into the enclosing list. +// +// Stream supports an optional input limit. If a limit is set, the +// size of any toplevel value will be checked against the remaining +// input length. Stream operations that encounter a value exceeding +// the remaining input length will return ErrValueTooLarge. The limit +// can be set by passing a non-zero value for inputLimit. +// +// If r is a bytes.Reader or strings.Reader, the input limit is set to +// the length of r's underlying data unless an explicit limit is +// provided. +func NewStream(r io.Reader, inputLimit uint64) *Stream { + s := new(Stream) + s.Reset(r, inputLimit) + return s +} + +// NewListStream creates a new stream that pretends to be positioned +// at an encoded list of the given length. +func NewListStream(r io.Reader, len uint64) *Stream { + s := new(Stream) + s.Reset(r, len) + s.kind = List + s.size = len + return s +} + +// Bytes reads an RLP string and returns its contents as a byte slice. +// If the input does not contain an RLP string, the returned +// error will be ErrExpectedString. +func (s *Stream) Bytes() ([]byte, error) { + kind, size, err := s.Kind() + if err != nil { + return nil, err + } + switch kind { + case Byte: + s.kind = -1 // rearm Kind + return []byte{s.byteval}, nil + case String: + b := make([]byte, size) + if err = s.readFull(b); err != nil { + return nil, err + } + if size == 1 && b[0] < 128 { + return nil, ErrCanonSize + } + return b, nil + default: + return nil, ErrExpectedString + } +} + +// ReadBytes decodes the next RLP value and stores the result in b. +// The value size must match len(b) exactly. +func (s *Stream) ReadBytes(b []byte) error { + kind, size, err := s.Kind() + if err != nil { + return err + } + switch kind { + case Byte: + if len(b) != 1 { + return fmt.Errorf("input value has wrong size 1, want %d", len(b)) + } + b[0] = s.byteval + s.kind = -1 // rearm Kind + return nil + case String: + if uint64(len(b)) != size { + return fmt.Errorf("input value has wrong size %d, want %d", size, len(b)) + } + if err = s.readFull(b); err != nil { + return err + } + if size == 1 && b[0] < 128 { + return ErrCanonSize + } + return nil + default: + return ErrExpectedString + } +} + +// Raw reads a raw encoded value including RLP type information. +func (s *Stream) Raw() ([]byte, error) { + kind, size, err := s.Kind() + if err != nil { + return nil, err + } + if kind == Byte { + s.kind = -1 // rearm Kind + return []byte{s.byteval}, nil + } + // The original header has already been read and is no longer + // available. Read content and put a new header in front of it. + start := headsize(size) + buf := make([]byte, uint64(start)+size) + if err := s.readFull(buf[start:]); err != nil { + return nil, err + } + if kind == String { + puthead(buf, 0x80, 0xB7, size) + } else { + puthead(buf, 0xC0, 0xF7, size) + } + return buf, nil +} + +// Uint reads an RLP string of up to 8 bytes and returns its contents +// as an unsigned integer. If the input does not contain an RLP string, the +// returned error will be ErrExpectedString. +// +// Deprecated: use s.Uint64 instead. +func (s *Stream) Uint() (uint64, error) { + return s.uint(64) +} + +func (s *Stream) Uint64() (uint64, error) { + return s.uint(64) +} + +func (s *Stream) Uint32() (uint32, error) { + i, err := s.uint(32) + return uint32(i), err +} + +func (s *Stream) Uint16() (uint16, error) { + i, err := s.uint(16) + return uint16(i), err +} + +func (s *Stream) Uint8() (uint8, error) { + i, err := s.uint(8) + return uint8(i), err +} + +func (s *Stream) uint(maxbits int) (uint64, error) { + kind, size, err := s.Kind() + if err != nil { + return 0, err + } + switch kind { + case Byte: + if s.byteval == 0 { + return 0, ErrCanonInt + } + s.kind = -1 // rearm Kind + return uint64(s.byteval), nil + case String: + if size > uint64(maxbits/8) { + return 0, errUintOverflow + } + v, err := s.readUint(byte(size)) + switch { + case err == ErrCanonSize: + // Adjust error because we're not reading a size right now. + return 0, ErrCanonInt + case err != nil: + return 0, err + case size > 0 && v < 128: + return 0, ErrCanonSize + default: + return v, nil + } + default: + return 0, ErrExpectedString + } +} + +// Bool reads an RLP string of up to 1 byte and returns its contents +// as a boolean. If the input does not contain an RLP string, the +// returned error will be ErrExpectedString. +func (s *Stream) Bool() (bool, error) { + num, err := s.uint(8) + if err != nil { + return false, err + } + switch num { + case 0: + return false, nil + case 1: + return true, nil + default: + return false, fmt.Errorf("rlp: invalid boolean value: %d", num) + } +} + +// List starts decoding an RLP list. If the input does not contain a +// list, the returned error will be ErrExpectedList. When the list's +// end has been reached, any Stream operation will return EOL. +func (s *Stream) List() (size uint64, err error) { + kind, size, err := s.Kind() + if err != nil { + return 0, err + } + if kind != List { + return 0, ErrExpectedList + } + + // Remove size of inner list from outer list before pushing the new size + // onto the stack. This ensures that the remaining outer list size will + // be correct after the matching call to ListEnd. + if inList, limit := s.listLimit(); inList { + s.stack[len(s.stack)-1] = limit - size + } + s.stack = append(s.stack, size) + s.kind = -1 + s.size = 0 + return size, nil +} + +// ListEnd returns to the enclosing list. +// The input reader must be positioned at the end of a list. +func (s *Stream) ListEnd() error { + // Ensure that no more data is remaining in the current list. + if inList, listLimit := s.listLimit(); !inList { + return errNotInList + } else if listLimit > 0 { + return errNotAtEOL + } + s.stack = s.stack[:len(s.stack)-1] // pop + s.kind = -1 + s.size = 0 + return nil +} + +// MoreDataInList reports whether the current list context contains +// more data to be read. +func (s *Stream) MoreDataInList() bool { + _, listLimit := s.listLimit() + return listLimit > 0 +} + +// BigInt decodes an arbitrary-size integer value. +func (s *Stream) BigInt() (*big.Int, error) { + i := new(big.Int) + if err := s.decodeBigInt(i); err != nil { + return nil, err + } + return i, nil +} + +func (s *Stream) decodeBigInt(dst *big.Int) error { + var buffer []byte + kind, size, err := s.Kind() + switch { + case err != nil: + return err + case kind == List: + return ErrExpectedString + case kind == Byte: + buffer = s.uintbuf[:1] + buffer[0] = s.byteval + s.kind = -1 // re-arm Kind + case size == 0: + // Avoid zero-length read. + s.kind = -1 + case size <= uint64(len(s.uintbuf)): + // For integers smaller than s.uintbuf, allocating a buffer + // can be avoided. + buffer = s.uintbuf[:size] + if err := s.readFull(buffer); err != nil { + return err + } + // Reject inputs where single byte encoding should have been used. + if size == 1 && buffer[0] < 128 { + return ErrCanonSize + } + default: + // For large integers, a temporary buffer is needed. + buffer = make([]byte, size) + if err := s.readFull(buffer); err != nil { + return err + } + } + + // Reject leading zero bytes. + if len(buffer) > 0 && buffer[0] == 0 { + return ErrCanonInt + } + // Set the integer bytes. + dst.SetBytes(buffer) + return nil +} + +// Decode decodes a value and stores the result in the value pointed +// to by val. Please see the documentation for the Decode function +// to learn about the decoding rules. +func (s *Stream) Decode(val interface{}) error { + if val == nil { + return errDecodeIntoNil + } + rval := reflect.ValueOf(val) + rtyp := rval.Type() + if rtyp.Kind() != reflect.Ptr { + return errNoPointer + } + if rval.IsNil() { + return errDecodeIntoNil + } + decoder, err := cachedDecoder(rtyp.Elem()) + if err != nil { + return err + } + + err = decoder(s, rval.Elem()) + if decErr, ok := err.(*decodeError); ok && len(decErr.ctx) > 0 { + // Add decode target type to error so context has more meaning. + decErr.ctx = append(decErr.ctx, fmt.Sprint("(", rtyp.Elem(), ")")) + } + return err +} + +// Reset discards any information about the current decoding context +// and starts reading from r. This method is meant to facilitate reuse +// of a preallocated Stream across many decoding operations. +// +// If r does not also implement ByteReader, Stream will do its own +// buffering. +func (s *Stream) Reset(r io.Reader, inputLimit uint64) { + if inputLimit > 0 { + s.remaining = inputLimit + s.limited = true + } else { + // Attempt to automatically discover + // the limit when reading from a byte slice. + switch br := r.(type) { + case *bytes.Reader: + s.remaining = uint64(br.Len()) + s.limited = true + case *bytes.Buffer: + s.remaining = uint64(br.Len()) + s.limited = true + case *strings.Reader: + s.remaining = uint64(br.Len()) + s.limited = true + default: + s.limited = false + } + } + // Wrap r with a buffer if it doesn't have one. + bufr, ok := r.(ByteReader) + if !ok { + bufr = bufio.NewReader(r) + } + s.r = bufr + // Reset the decoding context. + s.stack = s.stack[:0] + s.size = 0 + s.kind = -1 + s.kinderr = nil + s.byteval = 0 + s.uintbuf = [32]byte{} +} + +// Kind returns the kind and size of the next value in the +// input stream. +// +// The returned size is the number of bytes that make up the value. +// For kind == Byte, the size is zero because the value is +// contained in the type tag. +// +// The first call to Kind will read size information from the input +// reader and leave it positioned at the start of the actual bytes of +// the value. Subsequent calls to Kind (until the value is decoded) +// will not advance the input reader and return cached information. +func (s *Stream) Kind() (kind Kind, size uint64, err error) { + if s.kind >= 0 { + return s.kind, s.size, s.kinderr + } + + // Check for end of list. This needs to be done here because readKind + // checks against the list size, and would return the wrong error. + inList, listLimit := s.listLimit() + if inList && listLimit == 0 { + return 0, 0, EOL + } + // Read the actual size tag. + s.kind, s.size, s.kinderr = s.readKind() + if s.kinderr == nil { + // Check the data size of the value ahead against input limits. This + // is done here because many decoders require allocating an input + // buffer matching the value size. Checking it here protects those + // decoders from inputs declaring very large value size. + if inList && s.size > listLimit { + s.kinderr = ErrElemTooLarge + } else if s.limited && s.size > s.remaining { + s.kinderr = ErrValueTooLarge + } + } + return s.kind, s.size, s.kinderr +} + +func (s *Stream) readKind() (kind Kind, size uint64, err error) { + b, err := s.readByte() + if err != nil { + if len(s.stack) == 0 { + // At toplevel, Adjust the error to actual EOF. io.EOF is + // used by callers to determine when to stop decoding. + switch err { + case io.ErrUnexpectedEOF: + err = io.EOF + case ErrValueTooLarge: + err = io.EOF + } + } + return 0, 0, err + } + s.byteval = 0 + switch { + case b < 0x80: + // For a single byte whose value is in the [0x00, 0x7F] range, that byte + // is its own RLP encoding. + s.byteval = b + return Byte, 0, nil + case b < 0xB8: + // Otherwise, if a string is 0-55 bytes long, the RLP encoding consists + // of a single byte with value 0x80 plus the length of the string + // followed by the string. The range of the first byte is thus [0x80, 0xB7]. + return String, uint64(b - 0x80), nil + case b < 0xC0: + // If a string is more than 55 bytes long, the RLP encoding consists of a + // single byte with value 0xB7 plus the length of the length of the + // string in binary form, followed by the length of the string, followed + // by the string. For example, a length-1024 string would be encoded as + // 0xB90400 followed by the string. The range of the first byte is thus + // [0xB8, 0xBF]. + size, err = s.readUint(b - 0xB7) + if err == nil && size < 56 { + err = ErrCanonSize + } + return String, size, err + case b < 0xF8: + // If the total payload of a list (i.e. the combined length of all its + // items) is 0-55 bytes long, the RLP encoding consists of a single byte + // with value 0xC0 plus the length of the list followed by the + // concatenation of the RLP encodings of the items. The range of the + // first byte is thus [0xC0, 0xF7]. + return List, uint64(b - 0xC0), nil + default: + // If the total payload of a list is more than 55 bytes long, the RLP + // encoding consists of a single byte with value 0xF7 plus the length of + // the length of the payload in binary form, followed by the length of + // the payload, followed by the concatenation of the RLP encodings of + // the items. The range of the first byte is thus [0xF8, 0xFF]. + size, err = s.readUint(b - 0xF7) + if err == nil && size < 56 { + err = ErrCanonSize + } + return List, size, err + } +} + +func (s *Stream) readUint(size byte) (uint64, error) { + switch size { + case 0: + s.kind = -1 // rearm Kind + return 0, nil + case 1: + b, err := s.readByte() + return uint64(b), err + default: + buffer := s.uintbuf[:8] + for i := range buffer { + buffer[i] = 0 + } + start := int(8 - size) + if err := s.readFull(buffer[start:]); err != nil { + return 0, err + } + if buffer[start] == 0 { + // Note: readUint is also used to decode integer values. + // The error needs to be adjusted to become ErrCanonInt in this case. + return 0, ErrCanonSize + } + return binary.BigEndian.Uint64(buffer[:]), nil + } +} + +// readFull reads into buf from the underlying stream. +func (s *Stream) readFull(buf []byte) (err error) { + if err := s.willRead(uint64(len(buf))); err != nil { + return err + } + var nn, n int + for n < len(buf) && err == nil { + nn, err = s.r.Read(buf[n:]) + n += nn + } + if err == io.EOF { + if n < len(buf) { + err = io.ErrUnexpectedEOF + } else { + // Readers are allowed to give EOF even though the read succeeded. + // In such cases, we discard the EOF, like io.ReadFull() does. + err = nil + } + } + return err +} + +// readByte reads a single byte from the underlying stream. +func (s *Stream) readByte() (byte, error) { + if err := s.willRead(1); err != nil { + return 0, err + } + b, err := s.r.ReadByte() + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + return b, err +} + +// willRead is called before any read from the underlying stream. It checks +// n against size limits, and updates the limits if n doesn't overflow them. +func (s *Stream) willRead(n uint64) error { + s.kind = -1 // rearm Kind + + if inList, limit := s.listLimit(); inList { + if n > limit { + return ErrElemTooLarge + } + s.stack[len(s.stack)-1] = limit - n + } + if s.limited { + if n > s.remaining { + return ErrValueTooLarge + } + s.remaining -= n + } + return nil +} + +// listLimit returns the amount of data remaining in the innermost list. +func (s *Stream) listLimit() (inList bool, limit uint64) { + if len(s.stack) == 0 { + return false, 0 + } + return true, s.stack[len(s.stack)-1] +} diff --git a/rlp/decode_tail_test.go b/rlp/decode_tail_test.go new file mode 100644 index 0000000000..884c1148b2 --- /dev/null +++ b/rlp/decode_tail_test.go @@ -0,0 +1,49 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp + +import ( + "bytes" + "fmt" +) + +type structWithTail struct { + A, B uint + C []uint `rlp:"tail"` +} + +func ExampleDecode_structTagTail() { + // In this example, the "tail" struct tag is used to decode lists of + // differing length into a struct. + var val structWithTail + + err := Decode(bytes.NewReader([]byte{0xC4, 0x01, 0x02, 0x03, 0x04}), &val) + fmt.Printf("with 4 elements: err=%v val=%v\n", err, val) + + err = Decode(bytes.NewReader([]byte{0xC6, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}), &val) + fmt.Printf("with 6 elements: err=%v val=%v\n", err, val) + + // Note that at least two list elements must be present to + // fill fields A and B: + err = Decode(bytes.NewReader([]byte{0xC1, 0x01}), &val) + fmt.Printf("with 1 element: err=%q\n", err) + + // Output: + // with 4 elements: err= val={1 2 [3 4]} + // with 6 elements: err= val={1 2 [3 4 5 6]} + // with 1 element: err="rlp: too few elements for rlp.structWithTail" +} diff --git a/rlp/decode_test.go b/rlp/decode_test.go new file mode 100644 index 0000000000..00722f847b --- /dev/null +++ b/rlp/decode_test.go @@ -0,0 +1,1210 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "io" + "math/big" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common/math" +) + +func TestStreamKind(t *testing.T) { + tests := []struct { + input string + wantKind Kind + wantLen uint64 + }{ + {"00", Byte, 0}, + {"01", Byte, 0}, + {"7F", Byte, 0}, + {"80", String, 0}, + {"B7", String, 55}, + {"B90400", String, 1024}, + {"BFFFFFFFFFFFFFFFFF", String, ^uint64(0)}, + {"C0", List, 0}, + {"C8", List, 8}, + {"F7", List, 55}, + {"F90400", List, 1024}, + {"FFFFFFFFFFFFFFFFFF", List, ^uint64(0)}, + } + + for i, test := range tests { + // using plainReader to inhibit input limit errors. + s := NewStream(newPlainReader(unhex(test.input)), 0) + kind, len, err := s.Kind() + if err != nil { + t.Errorf("test %d: Kind returned error: %v", i, err) + continue + } + if kind != test.wantKind { + t.Errorf("test %d: kind mismatch: got %d, want %d", i, kind, test.wantKind) + } + if len != test.wantLen { + t.Errorf("test %d: len mismatch: got %d, want %d", i, len, test.wantLen) + } + } +} + +func TestNewListStream(t *testing.T) { + ls := NewListStream(bytes.NewReader(unhex("0101010101")), 3) + if k, size, err := ls.Kind(); k != List || size != 3 || err != nil { + t.Errorf("Kind() returned (%v, %d, %v), expected (List, 3, nil)", k, size, err) + } + if size, err := ls.List(); size != 3 || err != nil { + t.Errorf("List() returned (%d, %v), expected (3, nil)", size, err) + } + for i := 0; i < 3; i++ { + if val, err := ls.Uint(); val != 1 || err != nil { + t.Errorf("Uint() returned (%d, %v), expected (1, nil)", val, err) + } + } + if err := ls.ListEnd(); err != nil { + t.Errorf("ListEnd() returned %v, expected (3, nil)", err) + } +} + +func TestStreamErrors(t *testing.T) { + withoutInputLimit := func(b []byte) *Stream { + return NewStream(newPlainReader(b), 0) + } + withCustomInputLimit := func(limit uint64) func([]byte) *Stream { + return func(b []byte) *Stream { + return NewStream(bytes.NewReader(b), limit) + } + } + + type calls []string + tests := []struct { + string + calls + newStream func([]byte) *Stream // uses bytes.Reader if nil + error error + }{ + {"C0", calls{"Bytes"}, nil, ErrExpectedString}, + {"C0", calls{"Uint"}, nil, ErrExpectedString}, + {"89000000000000000001", calls{"Uint"}, nil, errUintOverflow}, + {"00", calls{"List"}, nil, ErrExpectedList}, + {"80", calls{"List"}, nil, ErrExpectedList}, + {"C0", calls{"List", "Uint"}, nil, EOL}, + {"C8C9010101010101010101", calls{"List", "Kind"}, nil, ErrElemTooLarge}, + {"C3C2010201", calls{"List", "List", "Uint", "Uint", "ListEnd", "Uint"}, nil, EOL}, + {"00", calls{"ListEnd"}, nil, errNotInList}, + {"C401020304", calls{"List", "Uint", "ListEnd"}, nil, errNotAtEOL}, + + // Non-canonical integers (e.g. leading zero bytes). + {"00", calls{"Uint"}, nil, ErrCanonInt}, + {"820002", calls{"Uint"}, nil, ErrCanonInt}, + {"8133", calls{"Uint"}, nil, ErrCanonSize}, + {"817F", calls{"Uint"}, nil, ErrCanonSize}, + {"8180", calls{"Uint"}, nil, nil}, + + // Non-valid boolean + {"02", calls{"Bool"}, nil, errors.New("rlp: invalid boolean value: 2")}, + + // Size tags must use the smallest possible encoding. + // Leading zero bytes in the size tag are also rejected. + {"8100", calls{"Uint"}, nil, ErrCanonSize}, + {"8100", calls{"Bytes"}, nil, ErrCanonSize}, + {"8101", calls{"Bytes"}, nil, ErrCanonSize}, + {"817F", calls{"Bytes"}, nil, ErrCanonSize}, + {"8180", calls{"Bytes"}, nil, nil}, + {"B800", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, + {"B90000", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, + {"B90055", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, + {"BA0002FFFF", calls{"Bytes"}, withoutInputLimit, ErrCanonSize}, + {"F800", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, + {"F90000", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, + {"F90055", calls{"Kind"}, withoutInputLimit, ErrCanonSize}, + {"FA0002FFFF", calls{"List"}, withoutInputLimit, ErrCanonSize}, + + // Expected EOF + {"", calls{"Kind"}, nil, io.EOF}, + {"", calls{"Uint"}, nil, io.EOF}, + {"", calls{"List"}, nil, io.EOF}, + {"8180", calls{"Uint", "Uint"}, nil, io.EOF}, + {"C0", calls{"List", "ListEnd", "List"}, nil, io.EOF}, + + {"", calls{"List"}, withoutInputLimit, io.EOF}, + {"8180", calls{"Uint", "Uint"}, withoutInputLimit, io.EOF}, + {"C0", calls{"List", "ListEnd", "List"}, withoutInputLimit, io.EOF}, + + // Input limit errors. + {"81", calls{"Bytes"}, nil, ErrValueTooLarge}, + {"81", calls{"Uint"}, nil, ErrValueTooLarge}, + {"81", calls{"Raw"}, nil, ErrValueTooLarge}, + {"BFFFFFFFFFFFFFFFFFFF", calls{"Bytes"}, nil, ErrValueTooLarge}, + {"C801", calls{"List"}, nil, ErrValueTooLarge}, + + // Test for list element size check overflow. + {"CD04040404FFFFFFFFFFFFFFFFFF0303", calls{"List", "Uint", "Uint", "Uint", "Uint", "List"}, nil, ErrElemTooLarge}, + + // Test for input limit overflow. Since we are counting the limit + // down toward zero in Stream.remaining, reading too far can overflow + // remaining to a large value, effectively disabling the limit. + {"C40102030401", calls{"Raw", "Uint"}, withCustomInputLimit(5), io.EOF}, + {"C4010203048180", calls{"Raw", "Uint"}, withCustomInputLimit(6), ErrValueTooLarge}, + + // Check that the same calls are fine without a limit. + {"C40102030401", calls{"Raw", "Uint"}, withoutInputLimit, nil}, + {"C4010203048180", calls{"Raw", "Uint"}, withoutInputLimit, nil}, + + // Unexpected EOF. This only happens when there is + // no input limit, so the reader needs to be 'dumbed down'. + {"81", calls{"Bytes"}, withoutInputLimit, io.ErrUnexpectedEOF}, + {"81", calls{"Uint"}, withoutInputLimit, io.ErrUnexpectedEOF}, + {"BFFFFFFFFFFFFFFF", calls{"Bytes"}, withoutInputLimit, io.ErrUnexpectedEOF}, + {"C801", calls{"List", "Uint", "Uint"}, withoutInputLimit, io.ErrUnexpectedEOF}, + + // This test verifies that the input position is advanced + // correctly when calling Bytes for empty strings. Kind can be called + // any number of times in between and doesn't advance. + {"C3808080", calls{ + "List", // enter the list + "Bytes", // past first element + + "Kind", "Kind", "Kind", // this shouldn't advance + + "Bytes", // past second element + + "Kind", "Kind", // can't hurt to try + + "Bytes", // past final element + "Bytes", // this one should fail + }, nil, EOL}, + } + +testfor: + for i, test := range tests { + if test.newStream == nil { + test.newStream = func(b []byte) *Stream { return NewStream(bytes.NewReader(b), 0) } + } + s := test.newStream(unhex(test.string)) + rs := reflect.ValueOf(s) + for j, call := range test.calls { + fval := rs.MethodByName(call) + ret := fval.Call(nil) + err := "" + if lastret := ret[len(ret)-1].Interface(); lastret != nil { + err = lastret.(error).Error() + } + if j == len(test.calls)-1 { + want := "" + if test.error != nil { + want = test.error.Error() + } + if err != want { + t.Log(test) + t.Errorf("test %d: last call (%s) error mismatch\ngot: %s\nwant: %s", + i, call, err, test.error) + } + } else if err != "" { + t.Log(test) + t.Errorf("test %d: call %d (%s) unexpected error: %q", i, j, call, err) + continue testfor + } + } + } +} + +func TestStreamList(t *testing.T) { + s := NewStream(bytes.NewReader(unhex("C80102030405060708")), 0) + + len, err := s.List() + if err != nil { + t.Fatalf("List error: %v", err) + } + if len != 8 { + t.Fatalf("List returned invalid length, got %d, want 8", len) + } + + for i := uint64(1); i <= 8; i++ { + v, err := s.Uint() + if err != nil { + t.Fatalf("Uint error: %v", err) + } + if i != v { + t.Errorf("Uint returned wrong value, got %d, want %d", v, i) + } + } + + if _, err := s.Uint(); err != EOL { + t.Errorf("Uint error mismatch, got %v, want %v", err, EOL) + } + if err = s.ListEnd(); err != nil { + t.Fatalf("ListEnd error: %v", err) + } +} + +func TestStreamRaw(t *testing.T) { + tests := []struct { + input string + output string + }{ + { + "C58401010101", + "8401010101", + }, + { + "F842B84001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101", + "B84001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101", + }, + } + for i, tt := range tests { + s := NewStream(bytes.NewReader(unhex(tt.input)), 0) + s.List() + + want := unhex(tt.output) + raw, err := s.Raw() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(want, raw) { + t.Errorf("test %d: raw mismatch: got %x, want %x", i, raw, want) + } + } +} + +func TestStreamReadBytes(t *testing.T) { + tests := []struct { + input string + size int + err string + }{ + // kind List + {input: "C0", size: 1, err: "rlp: expected String or Byte"}, + // kind Byte + {input: "04", size: 0, err: "input value has wrong size 1, want 0"}, + {input: "04", size: 1}, + {input: "04", size: 2, err: "input value has wrong size 1, want 2"}, + // kind String + {input: "820102", size: 0, err: "input value has wrong size 2, want 0"}, + {input: "820102", size: 1, err: "input value has wrong size 2, want 1"}, + {input: "820102", size: 2}, + {input: "820102", size: 3, err: "input value has wrong size 2, want 3"}, + } + + for _, test := range tests { + test := test + name := fmt.Sprintf("input_%s/size_%d", test.input, test.size) + t.Run(name, func(t *testing.T) { + s := NewStream(bytes.NewReader(unhex(test.input)), 0) + b := make([]byte, test.size) + err := s.ReadBytes(b) + if test.err == "" { + if err != nil { + t.Errorf("unexpected error %q", err) + } + } else { + if err == nil { + t.Errorf("expected error, got nil") + } else if err.Error() != test.err { + t.Errorf("wrong error %q", err) + } + } + }) + } +} + +func TestDecodeErrors(t *testing.T) { + r := bytes.NewReader(nil) + + if err := Decode(r, nil); err != errDecodeIntoNil { + t.Errorf("Decode(r, nil) error mismatch, got %q, want %q", err, errDecodeIntoNil) + } + + var nilptr *struct{} + if err := Decode(r, nilptr); err != errDecodeIntoNil { + t.Errorf("Decode(r, nilptr) error mismatch, got %q, want %q", err, errDecodeIntoNil) + } + + if err := Decode(r, struct{}{}); err != errNoPointer { + t.Errorf("Decode(r, struct{}{}) error mismatch, got %q, want %q", err, errNoPointer) + } + + expectErr := "rlp: type chan bool is not RLP-serializable" + if err := Decode(r, new(chan bool)); err == nil || err.Error() != expectErr { + t.Errorf("Decode(r, new(chan bool)) error mismatch, got %q, want %q", err, expectErr) + } + + if err := Decode(r, new(uint)); err != io.EOF { + t.Errorf("Decode(r, new(int)) error mismatch, got %q, want %q", err, io.EOF) + } +} + +type decodeTest struct { + input string + ptr interface{} + value interface{} + error string +} + +type simplestruct struct { + A uint + B string +} + +type recstruct struct { + I uint + Child *recstruct `rlp:"nil"` +} + +type bigIntStruct struct { + I *big.Int + B string +} + +type invalidNilTag struct { + X []byte `rlp:"nil"` +} + +type invalidTail1 struct { + A uint `rlp:"tail"` + B string +} + +type invalidTail2 struct { + A uint + B string `rlp:"tail"` +} + +type tailRaw struct { + A uint + Tail []RawValue `rlp:"tail"` +} + +type tailUint struct { + A uint + Tail []uint `rlp:"tail"` +} + +type tailPrivateFields struct { + A uint + Tail []uint `rlp:"tail"` + x, y bool //lint:ignore U1000 unused fields required for testing purposes. +} + +type nilListUint struct { + X *uint `rlp:"nilList"` +} + +type nilStringSlice struct { + X *[]uint `rlp:"nilString"` +} + +type intField struct { + X int +} + +type optionalFields struct { + A uint + B uint `rlp:"optional"` + C uint `rlp:"optional"` +} + +type optionalAndTailField struct { + A uint + B uint `rlp:"optional"` + Tail []uint `rlp:"tail"` +} + +type optionalBigIntField struct { + A uint + B *big.Int `rlp:"optional"` +} + +type optionalPtrField struct { + A uint + B *[3]byte `rlp:"optional"` +} + +type optionalPtrFieldNil struct { + A uint + B *[3]byte `rlp:"optional,nil"` +} + +type ignoredField struct { + A uint + B uint `rlp:"-"` + C uint +} + +var ( + veryBigInt = new(big.Int).Add( + new(big.Int).Lsh(big.NewInt(0xFFFFFFFFFFFFFF), 16), + big.NewInt(0xFFFF), + ) + veryVeryBigInt = new(big.Int).Exp(veryBigInt, big.NewInt(8), nil) +) + +var decodeTests = []decodeTest{ + // booleans + {input: "01", ptr: new(bool), value: true}, + {input: "80", ptr: new(bool), value: false}, + {input: "02", ptr: new(bool), error: "rlp: invalid boolean value: 2"}, + + // integers + {input: "05", ptr: new(uint32), value: uint32(5)}, + {input: "80", ptr: new(uint32), value: uint32(0)}, + {input: "820505", ptr: new(uint32), value: uint32(0x0505)}, + {input: "83050505", ptr: new(uint32), value: uint32(0x050505)}, + {input: "8405050505", ptr: new(uint32), value: uint32(0x05050505)}, + {input: "850505050505", ptr: new(uint32), error: "rlp: input string too long for uint32"}, + {input: "C0", ptr: new(uint32), error: "rlp: expected input string or byte for uint32"}, + {input: "00", ptr: new(uint32), error: "rlp: non-canonical integer (leading zero bytes) for uint32"}, + {input: "8105", ptr: new(uint32), error: "rlp: non-canonical size information for uint32"}, + {input: "820004", ptr: new(uint32), error: "rlp: non-canonical integer (leading zero bytes) for uint32"}, + {input: "B8020004", ptr: new(uint32), error: "rlp: non-canonical size information for uint32"}, + + // slices + {input: "C0", ptr: new([]uint), value: []uint{}}, + {input: "C80102030405060708", ptr: new([]uint), value: []uint{1, 2, 3, 4, 5, 6, 7, 8}}, + {input: "F8020004", ptr: new([]uint), error: "rlp: non-canonical size information for []uint"}, + + // arrays + {input: "C50102030405", ptr: new([5]uint), value: [5]uint{1, 2, 3, 4, 5}}, + {input: "C0", ptr: new([5]uint), error: "rlp: input list has too few elements for [5]uint"}, + {input: "C102", ptr: new([5]uint), error: "rlp: input list has too few elements for [5]uint"}, + {input: "C6010203040506", ptr: new([5]uint), error: "rlp: input list has too many elements for [5]uint"}, + {input: "F8020004", ptr: new([5]uint), error: "rlp: non-canonical size information for [5]uint"}, + + // zero sized arrays + {input: "C0", ptr: new([0]uint), value: [0]uint{}}, + {input: "C101", ptr: new([0]uint), error: "rlp: input list has too many elements for [0]uint"}, + + // byte slices + {input: "01", ptr: new([]byte), value: []byte{1}}, + {input: "80", ptr: new([]byte), value: []byte{}}, + {input: "8D6162636465666768696A6B6C6D", ptr: new([]byte), value: []byte("abcdefghijklm")}, + {input: "C0", ptr: new([]byte), error: "rlp: expected input string or byte for []uint8"}, + {input: "8105", ptr: new([]byte), error: "rlp: non-canonical size information for []uint8"}, + + // byte arrays + {input: "02", ptr: new([1]byte), value: [1]byte{2}}, + {input: "8180", ptr: new([1]byte), value: [1]byte{128}}, + {input: "850102030405", ptr: new([5]byte), value: [5]byte{1, 2, 3, 4, 5}}, + + // byte array errors + {input: "02", ptr: new([5]byte), error: "rlp: input string too short for [5]uint8"}, + {input: "80", ptr: new([5]byte), error: "rlp: input string too short for [5]uint8"}, + {input: "820000", ptr: new([5]byte), error: "rlp: input string too short for [5]uint8"}, + {input: "C0", ptr: new([5]byte), error: "rlp: expected input string or byte for [5]uint8"}, + {input: "C3010203", ptr: new([5]byte), error: "rlp: expected input string or byte for [5]uint8"}, + {input: "86010203040506", ptr: new([5]byte), error: "rlp: input string too long for [5]uint8"}, + {input: "8105", ptr: new([1]byte), error: "rlp: non-canonical size information for [1]uint8"}, + {input: "817F", ptr: new([1]byte), error: "rlp: non-canonical size information for [1]uint8"}, + + // zero sized byte arrays + {input: "80", ptr: new([0]byte), value: [0]byte{}}, + {input: "01", ptr: new([0]byte), error: "rlp: input string too long for [0]uint8"}, + {input: "8101", ptr: new([0]byte), error: "rlp: input string too long for [0]uint8"}, + + // strings + {input: "00", ptr: new(string), value: "\000"}, + {input: "8D6162636465666768696A6B6C6D", ptr: new(string), value: "abcdefghijklm"}, + {input: "C0", ptr: new(string), error: "rlp: expected input string or byte for string"}, + + // big ints + {input: "80", ptr: new(*big.Int), value: big.NewInt(0)}, + {input: "01", ptr: new(*big.Int), value: big.NewInt(1)}, + {input: "89FFFFFFFFFFFFFFFFFF", ptr: new(*big.Int), value: veryBigInt}, + {input: "B848FFFFFFFFFFFFFFFFF800000000000000001BFFFFFFFFFFFFFFFFC8000000000000000045FFFFFFFFFFFFFFFFC800000000000000001BFFFFFFFFFFFFFFFFF8000000000000000001", ptr: new(*big.Int), value: veryVeryBigInt}, + {input: "10", ptr: new(big.Int), value: *big.NewInt(16)}, // non-pointer also works + {input: "C0", ptr: new(*big.Int), error: "rlp: expected input string or byte for *big.Int"}, + {input: "00", ptr: new(*big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"}, + {input: "820001", ptr: new(*big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"}, + {input: "8105", ptr: new(*big.Int), error: "rlp: non-canonical size information for *big.Int"}, + + // structs + { + input: "C50583343434", + ptr: new(simplestruct), + value: simplestruct{5, "444"}, + }, + { + input: "C601C402C203C0", + ptr: new(recstruct), + value: recstruct{1, &recstruct{2, &recstruct{3, nil}}}, + }, + { + // This checks that empty big.Int works correctly in struct context. It's easy to + // miss the update of s.kind for this case, so it needs its own test. + input: "C58083343434", + ptr: new(bigIntStruct), + value: bigIntStruct{new(big.Int), "444"}, + }, + + // struct errors + { + input: "C0", + ptr: new(simplestruct), + error: "rlp: too few elements for rlp.simplestruct", + }, + { + input: "C105", + ptr: new(simplestruct), + error: "rlp: too few elements for rlp.simplestruct", + }, + { + input: "C7C50583343434C0", + ptr: new([]*simplestruct), + error: "rlp: too few elements for rlp.simplestruct, decoding into ([]*rlp.simplestruct)[1]", + }, + { + input: "83222222", + ptr: new(simplestruct), + error: "rlp: expected input list for rlp.simplestruct", + }, + { + input: "C3010101", + ptr: new(simplestruct), + error: "rlp: input list has too many elements for rlp.simplestruct", + }, + { + input: "C501C3C00000", + ptr: new(recstruct), + error: "rlp: expected input string or byte for uint, decoding into (rlp.recstruct).Child.I", + }, + { + input: "C103", + ptr: new(intField), + error: "rlp: type int is not RLP-serializable (struct field rlp.intField.X)", + }, + { + input: "C50102C20102", + ptr: new(tailUint), + error: "rlp: expected input string or byte for uint, decoding into (rlp.tailUint).Tail[1]", + }, + { + input: "C0", + ptr: new(invalidNilTag), + error: `rlp: invalid struct tag "nil" for rlp.invalidNilTag.X (field is not a pointer)`, + }, + + // struct tag "tail" + { + input: "C3010203", + ptr: new(tailRaw), + value: tailRaw{A: 1, Tail: []RawValue{unhex("02"), unhex("03")}}, + }, + { + input: "C20102", + ptr: new(tailRaw), + value: tailRaw{A: 1, Tail: []RawValue{unhex("02")}}, + }, + { + input: "C101", + ptr: new(tailRaw), + value: tailRaw{A: 1, Tail: []RawValue{}}, + }, + { + input: "C3010203", + ptr: new(tailPrivateFields), + value: tailPrivateFields{A: 1, Tail: []uint{2, 3}}, + }, + { + input: "C0", + ptr: new(invalidTail1), + error: `rlp: invalid struct tag "tail" for rlp.invalidTail1.A (must be on last field)`, + }, + { + input: "C0", + ptr: new(invalidTail2), + error: `rlp: invalid struct tag "tail" for rlp.invalidTail2.B (field type is not slice)`, + }, + + // struct tag "-" + { + input: "C20102", + ptr: new(ignoredField), + value: ignoredField{A: 1, C: 2}, + }, + + // struct tag "nilList" + { + input: "C180", + ptr: new(nilListUint), + error: "rlp: wrong kind of empty value (got String, want List) for *uint, decoding into (rlp.nilListUint).X", + }, + { + input: "C1C0", + ptr: new(nilListUint), + value: nilListUint{}, + }, + { + input: "C103", + ptr: new(nilListUint), + value: func() interface{} { + v := uint(3) + return nilListUint{X: &v} + }(), + }, + + // struct tag "nilString" + { + input: "C1C0", + ptr: new(nilStringSlice), + error: "rlp: wrong kind of empty value (got List, want String) for *[]uint, decoding into (rlp.nilStringSlice).X", + }, + { + input: "C180", + ptr: new(nilStringSlice), + value: nilStringSlice{}, + }, + { + input: "C2C103", + ptr: new(nilStringSlice), + value: nilStringSlice{X: &[]uint{3}}, + }, + + // struct tag "optional" + { + input: "C101", + ptr: new(optionalFields), + value: optionalFields{1, 0, 0}, + }, + { + input: "C20102", + ptr: new(optionalFields), + value: optionalFields{1, 2, 0}, + }, + { + input: "C3010203", + ptr: new(optionalFields), + value: optionalFields{1, 2, 3}, + }, + { + input: "C401020304", + ptr: new(optionalFields), + error: "rlp: input list has too many elements for rlp.optionalFields", + }, + { + input: "C101", + ptr: new(optionalAndTailField), + value: optionalAndTailField{A: 1}, + }, + { + input: "C20102", + ptr: new(optionalAndTailField), + value: optionalAndTailField{A: 1, B: 2, Tail: []uint{}}, + }, + { + input: "C401020304", + ptr: new(optionalAndTailField), + value: optionalAndTailField{A: 1, B: 2, Tail: []uint{3, 4}}, + }, + { + input: "C101", + ptr: new(optionalBigIntField), + value: optionalBigIntField{A: 1, B: nil}, + }, + { + input: "C20102", + ptr: new(optionalBigIntField), + value: optionalBigIntField{A: 1, B: big.NewInt(2)}, + }, + { + input: "C101", + ptr: new(optionalPtrField), + value: optionalPtrField{A: 1}, + }, + { + input: "C20180", // not accepted because "optional" doesn't enable "nil" + ptr: new(optionalPtrField), + error: "rlp: input string too short for [3]uint8, decoding into (rlp.optionalPtrField).B", + }, + { + input: "C20102", + ptr: new(optionalPtrField), + error: "rlp: input string too short for [3]uint8, decoding into (rlp.optionalPtrField).B", + }, + { + input: "C50183010203", + ptr: new(optionalPtrField), + value: optionalPtrField{A: 1, B: &[3]byte{1, 2, 3}}, + }, + { + input: "C101", + ptr: new(optionalPtrFieldNil), + value: optionalPtrFieldNil{A: 1}, + }, + { + input: "C20180", // accepted because "nil" tag allows empty input + ptr: new(optionalPtrFieldNil), + value: optionalPtrFieldNil{A: 1}, + }, + { + input: "C20102", + ptr: new(optionalPtrFieldNil), + error: "rlp: input string too short for [3]uint8, decoding into (rlp.optionalPtrFieldNil).B", + }, + + // struct tag "optional" field clearing + { + input: "C101", + ptr: &optionalFields{A: 9, B: 8, C: 7}, + value: optionalFields{A: 1, B: 0, C: 0}, + }, + { + input: "C20102", + ptr: &optionalFields{A: 9, B: 8, C: 7}, + value: optionalFields{A: 1, B: 2, C: 0}, + }, + { + input: "C20102", + ptr: &optionalAndTailField{A: 9, B: 8, Tail: []uint{7, 6, 5}}, + value: optionalAndTailField{A: 1, B: 2, Tail: []uint{}}, + }, + { + input: "C101", + ptr: &optionalPtrField{A: 9, B: &[3]byte{8, 7, 6}}, + value: optionalPtrField{A: 1}, + }, + + // RawValue + {input: "01", ptr: new(RawValue), value: RawValue(unhex("01"))}, + {input: "82FFFF", ptr: new(RawValue), value: RawValue(unhex("82FFFF"))}, + {input: "C20102", ptr: new([]RawValue), value: []RawValue{unhex("01"), unhex("02")}}, + + // pointers + {input: "00", ptr: new(*[]byte), value: &[]byte{0}}, + {input: "80", ptr: new(*uint), value: uintp(0)}, + {input: "C0", ptr: new(*uint), error: "rlp: expected input string or byte for uint"}, + {input: "07", ptr: new(*uint), value: uintp(7)}, + {input: "817F", ptr: new(*uint), error: "rlp: non-canonical size information for uint"}, + {input: "8180", ptr: new(*uint), value: uintp(0x80)}, + {input: "C109", ptr: new(*[]uint), value: &[]uint{9}}, + {input: "C58403030303", ptr: new(*[][]byte), value: &[][]byte{{3, 3, 3, 3}}}, + + // check that input position is advanced also for empty values. + {input: "C3808005", ptr: new([]*uint), value: []*uint{uintp(0), uintp(0), uintp(5)}}, + + // interface{} + {input: "00", ptr: new(interface{}), value: []byte{0}}, + {input: "01", ptr: new(interface{}), value: []byte{1}}, + {input: "80", ptr: new(interface{}), value: []byte{}}, + {input: "850505050505", ptr: new(interface{}), value: []byte{5, 5, 5, 5, 5}}, + {input: "C0", ptr: new(interface{}), value: []interface{}{}}, + {input: "C50183040404", ptr: new(interface{}), value: []interface{}{[]byte{1}, []byte{4, 4, 4}}}, + { + input: "C3010203", + ptr: new([]io.Reader), + error: "rlp: type io.Reader is not RLP-serializable", + }, + + // fuzzer crashes + { + input: "c330f9c030f93030ce3030303030303030bd303030303030", + ptr: new(interface{}), + error: "rlp: element is larger than containing list", + }, +} + +func uintp(i uint) *uint { return &i } + +func runTests(t *testing.T, decode func([]byte, interface{}) error) { + for i, test := range decodeTests { + input, err := hex.DecodeString(test.input) + if err != nil { + t.Errorf("test %d: invalid hex input %q", i, test.input) + continue + } + err = decode(input, test.ptr) + if err != nil && test.error == "" { + t.Errorf("test %d: unexpected Decode error: %v\ndecoding into %T\ninput %q", + i, err, test.ptr, test.input) + continue + } + if test.error != "" && fmt.Sprint(err) != test.error { + t.Errorf("test %d: Decode error mismatch\ngot %v\nwant %v\ndecoding into %T\ninput %q", + i, err, test.error, test.ptr, test.input) + continue + } + deref := reflect.ValueOf(test.ptr).Elem().Interface() + if err == nil && !reflect.DeepEqual(deref, test.value) { + t.Errorf("test %d: value mismatch\ngot %#v\nwant %#v\ndecoding into %T\ninput %q", + i, deref, test.value, test.ptr, test.input) + } + } +} + +func TestDecodeWithByteReader(t *testing.T) { + runTests(t, func(input []byte, into interface{}) error { + return Decode(bytes.NewReader(input), into) + }) +} + +func testDecodeWithEncReader(t *testing.T, n int) { + s := strings.Repeat("0", n) + _, r, _ := EncodeToReader(s) + var decoded string + err := Decode(r, &decoded) + if err != nil { + t.Errorf("Unexpected decode error with n=%v: %v", n, err) + } + if decoded != s { + t.Errorf("Decode mismatch with n=%v", n) + } +} + +// This is a regression test checking that decoding from encReader +// works for RLP values of size 8192 bytes or more. +func TestDecodeWithEncReader(t *testing.T) { + testDecodeWithEncReader(t, 8188) // length with header is 8191 + testDecodeWithEncReader(t, 8189) // length with header is 8192 +} + +// plainReader reads from a byte slice but does not +// implement ReadByte. It is also not recognized by the +// size validation. This is useful to test how the decoder +// behaves on a non-buffered input stream. +type plainReader []byte + +func newPlainReader(b []byte) io.Reader { + return (*plainReader)(&b) +} + +func (r *plainReader) Read(buf []byte) (n int, err error) { + if len(*r) == 0 { + return 0, io.EOF + } + n = copy(buf, *r) + *r = (*r)[n:] + return n, nil +} + +func TestDecodeWithNonByteReader(t *testing.T) { + runTests(t, func(input []byte, into interface{}) error { + return Decode(newPlainReader(input), into) + }) +} + +func TestDecodeStreamReset(t *testing.T) { + s := NewStream(nil, 0) + runTests(t, func(input []byte, into interface{}) error { + s.Reset(bytes.NewReader(input), 0) + return s.Decode(into) + }) +} + +type testDecoder struct{ called bool } + +func (t *testDecoder) DecodeRLP(s *Stream) error { + if _, err := s.Uint(); err != nil { + return err + } + t.called = true + return nil +} + +func TestDecodeDecoder(t *testing.T) { + var s struct { + T1 testDecoder + T2 *testDecoder + T3 **testDecoder + } + if err := Decode(bytes.NewReader(unhex("C3010203")), &s); err != nil { + t.Fatalf("Decode error: %v", err) + } + + if !s.T1.called { + t.Errorf("DecodeRLP was not called for (non-pointer) testDecoder") + } + + if s.T2 == nil { + t.Errorf("*testDecoder has not been allocated") + } else if !s.T2.called { + t.Errorf("DecodeRLP was not called for *testDecoder") + } + + if s.T3 == nil || *s.T3 == nil { + t.Errorf("**testDecoder has not been allocated") + } else if !(*s.T3).called { + t.Errorf("DecodeRLP was not called for **testDecoder") + } +} + +func TestDecodeDecoderNilPointer(t *testing.T) { + var s struct { + T1 *testDecoder `rlp:"nil"` + T2 *testDecoder + } + if err := Decode(bytes.NewReader(unhex("C2C002")), &s); err != nil { + t.Fatalf("Decode error: %v", err) + } + if s.T1 != nil { + t.Errorf("decoder T1 allocated for empty input (called: %v)", s.T1.called) + } + if s.T2 == nil || !s.T2.called { + t.Errorf("decoder T2 not allocated/called") + } +} + +type byteDecoder byte + +func (bd *byteDecoder) DecodeRLP(s *Stream) error { + _, err := s.Uint() + *bd = 255 + return err +} + +func (bd byteDecoder) called() bool { + return bd == 255 +} + +// This test verifies that the byte slice/byte array logic +// does not kick in for element types implementing Decoder. +func TestDecoderInByteSlice(t *testing.T) { + var slice []byteDecoder + if err := Decode(bytes.NewReader(unhex("C101")), &slice); err != nil { + t.Errorf("unexpected Decode error %v", err) + } else if !slice[0].called() { + t.Errorf("DecodeRLP not called for slice element") + } + + var array [1]byteDecoder + if err := Decode(bytes.NewReader(unhex("C101")), &array); err != nil { + t.Errorf("unexpected Decode error %v", err) + } else if !array[0].called() { + t.Errorf("DecodeRLP not called for array element") + } +} + +type unencodableDecoder func() + +func (f *unencodableDecoder) DecodeRLP(s *Stream) error { + if _, err := s.List(); err != nil { + return err + } + if err := s.ListEnd(); err != nil { + return err + } + *f = func() {} + return nil +} + +func TestDecoderFunc(t *testing.T) { + var x func() + if err := DecodeBytes([]byte{0xC0}, (*unencodableDecoder)(&x)); err != nil { + t.Fatal(err) + } + x() +} + +// This tests the validity checks for fields with struct tag "optional". +func TestInvalidOptionalField(t *testing.T) { + type ( + invalid1 struct { + A uint `rlp:"optional"` + B uint + } + invalid2 struct { + T []uint `rlp:"tail,optional"` + } + invalid3 struct { + T []uint `rlp:"optional,tail"` + } + ) + + tests := []struct { + v interface{} + err string + }{ + {v: new(invalid1), err: `rlp: invalid struct tag "" for rlp.invalid1.B (must be optional because preceding field "A" is optional)`}, + {v: new(invalid2), err: `rlp: invalid struct tag "optional" for rlp.invalid2.T (also has "tail" tag)`}, + {v: new(invalid3), err: `rlp: invalid struct tag "tail" for rlp.invalid3.T (also has "optional" tag)`}, + } + for _, test := range tests { + err := DecodeBytes(unhex("C20102"), test.v) + if err == nil { + t.Errorf("no error for %T", test.v) + } else if err.Error() != test.err { + t.Errorf("wrong error for %T: %v", test.v, err.Error()) + } + } +} + +func ExampleDecode() { + input, _ := hex.DecodeString("C90A1486666F6F626172") + + type example struct { + A, B uint + String string + } + + var s example + err := Decode(bytes.NewReader(input), &s) + if err != nil { + fmt.Printf("Error: %v\n", err) + } else { + fmt.Printf("Decoded value: %#v\n", s) + } + // Output: + // Decoded value: rlp.example{A:0xa, B:0x14, String:"foobar"} +} + +func ExampleDecode_structTagNil() { + // In this example, we'll use the "nil" struct tag to change + // how a pointer-typed field is decoded. The input contains an RLP + // list of one element, an empty string. + input := []byte{0xC1, 0x80} + + // This type uses the normal rules. + // The empty input string is decoded as a pointer to an empty Go string. + var normalRules struct { + String *string + } + Decode(bytes.NewReader(input), &normalRules) + fmt.Printf("normal: String = %q\n", *normalRules.String) + + // This type uses the struct tag. + // The empty input string is decoded as a nil pointer. + var withEmptyOK struct { + String *string `rlp:"nil"` + } + Decode(bytes.NewReader(input), &withEmptyOK) + fmt.Printf("with nil tag: String = %v\n", withEmptyOK.String) + + // Output: + // normal: String = "" + // with nil tag: String = +} + +func ExampleStream() { + input, _ := hex.DecodeString("C90A1486666F6F626172") + s := NewStream(bytes.NewReader(input), 0) + + // Check what kind of value lies ahead + kind, size, _ := s.Kind() + fmt.Printf("Kind: %v size:%d\n", kind, size) + + // Enter the list + if _, err := s.List(); err != nil { + fmt.Printf("List error: %v\n", err) + return + } + + // Decode elements + fmt.Println(s.Uint()) + fmt.Println(s.Uint()) + fmt.Println(s.Bytes()) + + // Acknowledge end of list + if err := s.ListEnd(); err != nil { + fmt.Printf("ListEnd error: %v\n", err) + } + // Output: + // Kind: List size:9 + // 10 + // 20 + // [102 111 111 98 97 114] +} + +func BenchmarkDecodeUints(b *testing.B) { + enc := encodeTestSlice(90000) + b.SetBytes(int64(len(enc))) + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + var s []uint + r := bytes.NewReader(enc) + if err := Decode(r, &s); err != nil { + b.Fatalf("Decode error: %v", err) + } + } +} + +func BenchmarkDecodeUintsReused(b *testing.B) { + enc := encodeTestSlice(100000) + b.SetBytes(int64(len(enc))) + b.ReportAllocs() + b.ResetTimer() + + var s []uint + for i := 0; i < b.N; i++ { + r := bytes.NewReader(enc) + if err := Decode(r, &s); err != nil { + b.Fatalf("Decode error: %v", err) + } + } +} + +func BenchmarkDecodeByteArrayStruct(b *testing.B) { + enc, err := EncodeToBytes(&byteArrayStruct{}) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(len(enc))) + b.ReportAllocs() + b.ResetTimer() + + var out byteArrayStruct + for i := 0; i < b.N; i++ { + if err := DecodeBytes(enc, &out); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDecodeBigInts(b *testing.B) { + ints := make([]*big.Int, 200) + for i := range ints { + ints[i] = math.BigPow(2, int64(i)) + } + enc, err := EncodeToBytes(ints) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(len(enc))) + b.ReportAllocs() + b.ResetTimer() + + var out []*big.Int + for i := 0; i < b.N; i++ { + if err := DecodeBytes(enc, &out); err != nil { + b.Fatal(err) + } + } +} + +func encodeTestSlice(n uint) []byte { + s := make([]uint, n) + for i := uint(0); i < n; i++ { + s[i] = i + } + b, err := EncodeToBytes(s) + if err != nil { + panic(fmt.Sprintf("encode error: %v", err)) + } + return b +} + +func unhex(str string) []byte { + b, err := hex.DecodeString(strings.ReplaceAll(str, " ", "")) + if err != nil { + panic(fmt.Sprintf("invalid hex string: %q", str)) + } + return b +} diff --git a/rlp/doc.go b/rlp/doc.go new file mode 100644 index 0000000000..e4404c978d --- /dev/null +++ b/rlp/doc.go @@ -0,0 +1,161 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +/* +Package rlp implements the RLP serialization format. + +The purpose of RLP (Recursive Linear Prefix) is to encode arbitrarily nested arrays of +binary data, and RLP is the main encoding method used to serialize objects in Ethereum. +The only purpose of RLP is to encode structure; encoding specific atomic data types (eg. +strings, ints, floats) is left up to higher-order protocols. In Ethereum integers must be +represented in big endian binary form with no leading zeroes (thus making the integer +value zero equivalent to the empty string). + +RLP values are distinguished by a type tag. The type tag precedes the value in the input +stream and defines the size and kind of the bytes that follow. + + +Encoding Rules + +Package rlp uses reflection and encodes RLP based on the Go type of the value. + +If the type implements the Encoder interface, Encode calls EncodeRLP. It does not +call EncodeRLP on nil pointer values. + +To encode a pointer, the value being pointed to is encoded. A nil pointer to a struct +type, slice or array always encodes as an empty RLP list unless the slice or array has +element type byte. A nil pointer to any other value encodes as the empty string. + +Struct values are encoded as an RLP list of all their encoded public fields. Recursive +struct types are supported. + +To encode slices and arrays, the elements are encoded as an RLP list of the value's +elements. Note that arrays and slices with element type uint8 or byte are always encoded +as an RLP string. + +A Go string is encoded as an RLP string. + +An unsigned integer value is encoded as an RLP string. Zero always encodes as an empty RLP +string. big.Int values are treated as integers. Signed integers (int, int8, int16, ...) +are not supported and will return an error when encoding. + +Boolean values are encoded as the unsigned integers zero (false) and one (true). + +An interface value encodes as the value contained in the interface. + +Floating point numbers, maps, channels and functions are not supported. + + +Decoding Rules + +Decoding uses the following type-dependent rules: + +If the type implements the Decoder interface, DecodeRLP is called. + +To decode into a pointer, the value will be decoded as the element type of the pointer. If +the pointer is nil, a new value of the pointer's element type is allocated. If the pointer +is non-nil, the existing value will be reused. Note that package rlp never leaves a +pointer-type struct field as nil unless one of the "nil" struct tags is present. + +To decode into a struct, decoding expects the input to be an RLP list. The decoded +elements of the list are assigned to each public field in the order given by the struct's +definition. The input list must contain an element for each decoded field. Decoding +returns an error if there are too few or too many elements for the struct. + +To decode into a slice, the input must be a list and the resulting slice will contain the +input elements in order. For byte slices, the input must be an RLP string. Array types +decode similarly, with the additional restriction that the number of input elements (or +bytes) must match the array's defined length. + +To decode into a Go string, the input must be an RLP string. The input bytes are taken +as-is and will not necessarily be valid UTF-8. + +To decode into an unsigned integer type, the input must also be an RLP string. The bytes +are interpreted as a big endian representation of the integer. If the RLP string is larger +than the bit size of the type, decoding will return an error. Decode also supports +*big.Int. There is no size limit for big integers. + +To decode into a boolean, the input must contain an unsigned integer of value zero (false) +or one (true). + +To decode into an interface value, one of these types is stored in the value: + + []interface{}, for RLP lists + []byte, for RLP strings + +Non-empty interface types are not supported when decoding. +Signed integers, floating point numbers, maps, channels and functions cannot be decoded into. + + +Struct Tags + +As with other encoding packages, the "-" tag ignores fields. + + type StructWithIgnoredField struct{ + Ignored uint `rlp:"-"` + Field uint + } + +Go struct values encode/decode as RLP lists. There are two ways of influencing the mapping +of fields to list elements. The "tail" tag, which may only be used on the last exported +struct field, allows slurping up any excess list elements into a slice. + + type StructWithTail struct{ + Field uint + Tail []string `rlp:"tail"` + } + +The "optional" tag says that the field may be omitted if it is zero-valued. If this tag is +used on a struct field, all subsequent public fields must also be declared optional. + +When encoding a struct with optional fields, the output RLP list contains all values up to +the last non-zero optional field. + +When decoding into a struct, optional fields may be omitted from the end of the input +list. For the example below, this means input lists of one, two, or three elements are +accepted. + + type StructWithOptionalFields struct{ + Required uint + Optional1 uint `rlp:"optional"` + Optional2 uint `rlp:"optional"` + } + +The "nil", "nilList" and "nilString" tags apply to pointer-typed fields only, and change +the decoding rules for the field type. For regular pointer fields without the "nil" tag, +input values must always match the required input length exactly and the decoder does not +produce nil values. When the "nil" tag is set, input values of size zero decode as a nil +pointer. This is especially useful for recursive types. + + type StructWithNilField struct { + Field *[3]byte `rlp:"nil"` + } + +In the example above, Field allows two possible input sizes. For input 0xC180 (a list +containing an empty string) Field is set to nil after decoding. For input 0xC483000000 (a +list containing a 3-byte string), Field is set to a non-nil array pointer. + +RLP supports two kinds of empty values: empty lists and empty strings. When using the +"nil" tag, the kind of empty value allowed for a type is chosen automatically. A field +whose Go type is a pointer to an unsigned integer, string, boolean or byte array/slice +expects an empty RLP string. Any other pointer field type encodes/decodes as an empty RLP +list. + +The choice of null value can be made explicit with the "nilList" and "nilString" struct +tags. Using these tags encodes/decodes a Go nil pointer value as the empty RLP value kind +defined by the tag. +*/ +package rlp diff --git a/rlp/encbuffer.go b/rlp/encbuffer.go new file mode 100644 index 0000000000..687949c044 --- /dev/null +++ b/rlp/encbuffer.go @@ -0,0 +1,398 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp + +import ( + "io" + "math/big" + "reflect" + "sync" +) + +type encBuffer struct { + str []byte // string data, contains everything except list headers + lheads []listhead // all list headers + lhsize int // sum of sizes of all encoded list headers + sizebuf [9]byte // auxiliary buffer for uint encoding +} + +// The global encBuffer pool. +var encBufferPool = sync.Pool{ + New: func() interface{} { return new(encBuffer) }, +} + +func getEncBuffer() *encBuffer { + buf := encBufferPool.Get().(*encBuffer) + buf.reset() + return buf +} + +func (buf *encBuffer) reset() { + buf.lhsize = 0 + buf.str = buf.str[:0] + buf.lheads = buf.lheads[:0] +} + +// size returns the length of the encoded data. +func (buf *encBuffer) size() int { + return len(buf.str) + buf.lhsize +} + +// makeBytes creates the encoder output. +func (w *encBuffer) makeBytes() []byte { + out := make([]byte, w.size()) + w.copyTo(out) + return out +} + +func (w *encBuffer) copyTo(dst []byte) { + strpos := 0 + pos := 0 + for _, head := range w.lheads { + // write string data before header + n := copy(dst[pos:], w.str[strpos:head.offset]) + pos += n + strpos += n + // write the header + enc := head.encode(dst[pos:]) + pos += len(enc) + } + // copy string data after the last list header + copy(dst[pos:], w.str[strpos:]) +} + +// writeTo writes the encoder output to w. +func (buf *encBuffer) writeTo(w io.Writer) (err error) { + strpos := 0 + for _, head := range buf.lheads { + // write string data before header + if head.offset-strpos > 0 { + n, err := w.Write(buf.str[strpos:head.offset]) + strpos += n + if err != nil { + return err + } + } + // write the header + enc := head.encode(buf.sizebuf[:]) + if _, err = w.Write(enc); err != nil { + return err + } + } + if strpos < len(buf.str) { + // write string data after the last list header + _, err = w.Write(buf.str[strpos:]) + } + return err +} + +// Write implements io.Writer and appends b directly to the output. +func (buf *encBuffer) Write(b []byte) (int, error) { + buf.str = append(buf.str, b...) + return len(b), nil +} + +// writeBool writes b as the integer 0 (false) or 1 (true). +func (buf *encBuffer) writeBool(b bool) { + if b { + buf.str = append(buf.str, 0x01) + } else { + buf.str = append(buf.str, 0x80) + } +} + +func (buf *encBuffer) writeUint64(i uint64) { + if i == 0 { + buf.str = append(buf.str, 0x80) + } else if i < 128 { + // fits single byte + buf.str = append(buf.str, byte(i)) + } else { + s := putint(buf.sizebuf[1:], i) + buf.sizebuf[0] = 0x80 + byte(s) + buf.str = append(buf.str, buf.sizebuf[:s+1]...) + } +} + +func (buf *encBuffer) writeBytes(b []byte) { + if len(b) == 1 && b[0] <= 0x7F { + // fits single byte, no string header + buf.str = append(buf.str, b[0]) + } else { + buf.encodeStringHeader(len(b)) + buf.str = append(buf.str, b...) + } +} + +func (buf *encBuffer) writeString(s string) { + buf.writeBytes([]byte(s)) +} + +// wordBytes is the number of bytes in a big.Word +const wordBytes = (32 << (uint64(^big.Word(0)) >> 63)) / 8 + +// writeBigInt writes i as an integer. +func (w *encBuffer) writeBigInt(i *big.Int) { + bitlen := i.BitLen() + if bitlen <= 64 { + w.writeUint64(i.Uint64()) + return + } + // Integer is larger than 64 bits, encode from i.Bits(). + // The minimal byte length is bitlen rounded up to the next + // multiple of 8, divided by 8. + length := ((bitlen + 7) & -8) >> 3 + w.encodeStringHeader(length) + w.str = append(w.str, make([]byte, length)...) + index := length + buf := w.str[len(w.str)-length:] + for _, d := range i.Bits() { + for j := 0; j < wordBytes && index > 0; j++ { + index-- + buf[index] = byte(d) + d >>= 8 + } + } +} + +// list adds a new list header to the header stack. It returns the index of the header. +// Call listEnd with this index after encoding the content of the list. +func (buf *encBuffer) list() int { + buf.lheads = append(buf.lheads, listhead{offset: len(buf.str), size: buf.lhsize}) + return len(buf.lheads) - 1 +} + +func (buf *encBuffer) listEnd(index int) { + lh := &buf.lheads[index] + lh.size = buf.size() - lh.offset - lh.size + if lh.size < 56 { + buf.lhsize++ // length encoded into kind tag + } else { + buf.lhsize += 1 + intsize(uint64(lh.size)) + } +} + +func (buf *encBuffer) encode(val interface{}) error { + rval := reflect.ValueOf(val) + writer, err := cachedWriter(rval.Type()) + if err != nil { + return err + } + return writer(rval, buf) +} + +func (buf *encBuffer) encodeStringHeader(size int) { + if size < 56 { + buf.str = append(buf.str, 0x80+byte(size)) + } else { + sizesize := putint(buf.sizebuf[1:], uint64(size)) + buf.sizebuf[0] = 0xB7 + byte(sizesize) + buf.str = append(buf.str, buf.sizebuf[:sizesize+1]...) + } +} + +// encReader is the io.Reader returned by EncodeToReader. +// It releases its encbuf at EOF. +type encReader struct { + buf *encBuffer // the buffer we're reading from. this is nil when we're at EOF. + lhpos int // index of list header that we're reading + strpos int // current position in string buffer + piece []byte // next piece to be read +} + +func (r *encReader) Read(b []byte) (n int, err error) { + for { + if r.piece = r.next(); r.piece == nil { + // Put the encode buffer back into the pool at EOF when it + // is first encountered. Subsequent calls still return EOF + // as the error but the buffer is no longer valid. + if r.buf != nil { + encBufferPool.Put(r.buf) + r.buf = nil + } + return n, io.EOF + } + nn := copy(b[n:], r.piece) + n += nn + if nn < len(r.piece) { + // piece didn't fit, see you next time. + r.piece = r.piece[nn:] + return n, nil + } + r.piece = nil + } +} + +// next returns the next piece of data to be read. +// it returns nil at EOF. +func (r *encReader) next() []byte { + switch { + case r.buf == nil: + return nil + + case r.piece != nil: + // There is still data available for reading. + return r.piece + + case r.lhpos < len(r.buf.lheads): + // We're before the last list header. + head := r.buf.lheads[r.lhpos] + sizebefore := head.offset - r.strpos + if sizebefore > 0 { + // String data before header. + p := r.buf.str[r.strpos:head.offset] + r.strpos += sizebefore + return p + } + r.lhpos++ + return head.encode(r.buf.sizebuf[:]) + + case r.strpos < len(r.buf.str): + // String data at the end, after all list headers. + p := r.buf.str[r.strpos:] + r.strpos = len(r.buf.str) + return p + + default: + return nil + } +} + +func encBufferFromWriter(w io.Writer) *encBuffer { + switch w := w.(type) { + case EncoderBuffer: + return w.buf + case *EncoderBuffer: + return w.buf + case *encBuffer: + return w + default: + return nil + } +} + +// EncoderBuffer is a buffer for incremental encoding. +// +// The zero value is NOT ready for use. To get a usable buffer, +// create it using NewEncoderBuffer or call Reset. +type EncoderBuffer struct { + buf *encBuffer + dst io.Writer + + ownBuffer bool +} + +// NewEncoderBuffer creates an encoder buffer. +func NewEncoderBuffer(dst io.Writer) EncoderBuffer { + var w EncoderBuffer + w.Reset(dst) + return w +} + +// Reset truncates the buffer and sets the output destination. +func (w *EncoderBuffer) Reset(dst io.Writer) { + if w.buf != nil && !w.ownBuffer { + panic("can't Reset derived EncoderBuffer") + } + + // If the destination writer has an *encBuffer, use it. + // Note that w.ownBuffer is left false here. + if dst != nil { + if outer := encBufferFromWriter(dst); outer != nil { + *w = EncoderBuffer{outer, nil, false} + return + } + } + + // Get a fresh buffer. + if w.buf == nil { + w.buf = encBufferPool.Get().(*encBuffer) + w.ownBuffer = true + } + w.buf.reset() + w.dst = dst +} + +// Flush writes encoded RLP data to the output writer. This can only be called once. +// If you want to re-use the buffer after Flush, you must call Reset. +func (w *EncoderBuffer) Flush() error { + var err error + if w.dst != nil { + err = w.buf.writeTo(w.dst) + } + // Release the internal buffer. + if w.ownBuffer { + encBufferPool.Put(w.buf) + } + *w = EncoderBuffer{} + return err +} + +// ToBytes returns the encoded bytes. +func (w *EncoderBuffer) ToBytes() []byte { + return w.buf.makeBytes() +} + +// AppendToBytes appends the encoded bytes to dst. +func (w *EncoderBuffer) AppendToBytes(dst []byte) []byte { + size := w.buf.size() + out := append(dst, make([]byte, size)...) + w.buf.copyTo(out[len(dst):]) + return out +} + +// Write appends b directly to the encoder output. +func (w EncoderBuffer) Write(b []byte) (int, error) { + return w.buf.Write(b) +} + +// WriteBool writes b as the integer 0 (false) or 1 (true). +func (w EncoderBuffer) WriteBool(b bool) { + w.buf.writeBool(b) +} + +// WriteUint64 encodes an unsigned integer. +func (w EncoderBuffer) WriteUint64(i uint64) { + w.buf.writeUint64(i) +} + +// WriteBigInt encodes a big.Int as an RLP string. +// Note: Unlike with Encode, the sign of i is ignored. +func (w EncoderBuffer) WriteBigInt(i *big.Int) { + w.buf.writeBigInt(i) +} + +// WriteBytes encodes b as an RLP string. +func (w EncoderBuffer) WriteBytes(b []byte) { + w.buf.writeBytes(b) +} + +// WriteBytes encodes s as an RLP string. +func (w EncoderBuffer) WriteString(s string) { + w.buf.writeString(s) +} + +// List starts a list. It returns an internal index. Call EndList with +// this index after encoding the content to finish the list. +func (w EncoderBuffer) List() int { + return w.buf.list() +} + +// ListEnd finishes the given list. +func (w EncoderBuffer) ListEnd(index int) { + w.buf.listEnd(index) +} diff --git a/rlp/encbuffer_example_test.go b/rlp/encbuffer_example_test.go new file mode 100644 index 0000000000..11b4b578aa --- /dev/null +++ b/rlp/encbuffer_example_test.go @@ -0,0 +1,45 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp_test + +import ( + "bytes" + "fmt" + + "github.com/tenderly/coreth/rlp" +) + +func ExampleEncoderBuffer() { + var w bytes.Buffer + + // Encode [4, [5, 6]] to w. + buf := rlp.NewEncoderBuffer(&w) + l1 := buf.List() + buf.WriteUint64(4) + l2 := buf.List() + buf.WriteUint64(5) + buf.WriteUint64(6) + buf.ListEnd(l2) + buf.ListEnd(l1) + + if err := buf.Flush(); err != nil { + panic(err) + } + fmt.Printf("%X\n", w.Bytes()) + // Output: + // C404C20506 +} diff --git a/rlp/encode.go b/rlp/encode.go new file mode 100644 index 0000000000..75c4e276cc --- /dev/null +++ b/rlp/encode.go @@ -0,0 +1,471 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp + +import ( + "errors" + "fmt" + "io" + "math/big" + "reflect" + + "github.com/tenderly/coreth/rlp/internal/rlpstruct" +) + +var ( + // Common encoded values. + // These are useful when implementing EncodeRLP. + EmptyString = []byte{0x80} + EmptyList = []byte{0xC0} +) + +var ErrNegativeBigInt = errors.New("rlp: cannot encode negative big.Int") + +// Encoder is implemented by types that require custom +// encoding rules or want to encode private fields. +type Encoder interface { + // EncodeRLP should write the RLP encoding of its receiver to w. + // If the implementation is a pointer method, it may also be + // called for nil pointers. + // + // Implementations should generate valid RLP. The data written is + // not verified at the moment, but a future version might. It is + // recommended to write only a single value but writing multiple + // values or no value at all is also permitted. + EncodeRLP(io.Writer) error +} + +// Encode writes the RLP encoding of val to w. Note that Encode may +// perform many small writes in some cases. Consider making w +// buffered. +// +// Please see package-level documentation of encoding rules. +func Encode(w io.Writer, val interface{}) error { + // Optimization: reuse *encBuffer when called by EncodeRLP. + if buf := encBufferFromWriter(w); buf != nil { + return buf.encode(val) + } + + buf := getEncBuffer() + defer encBufferPool.Put(buf) + if err := buf.encode(val); err != nil { + return err + } + return buf.writeTo(w) +} + +// EncodeToBytes returns the RLP encoding of val. +// Please see package-level documentation for the encoding rules. +func EncodeToBytes(val interface{}) ([]byte, error) { + buf := getEncBuffer() + defer encBufferPool.Put(buf) + + if err := buf.encode(val); err != nil { + return nil, err + } + return buf.makeBytes(), nil +} + +// EncodeToReader returns a reader from which the RLP encoding of val +// can be read. The returned size is the total size of the encoded +// data. +// +// Please see the documentation of Encode for the encoding rules. +func EncodeToReader(val interface{}) (size int, r io.Reader, err error) { + buf := getEncBuffer() + if err := buf.encode(val); err != nil { + encBufferPool.Put(buf) + return 0, nil, err + } + // Note: can't put the reader back into the pool here + // because it is held by encReader. The reader puts it + // back when it has been fully consumed. + return buf.size(), &encReader{buf: buf}, nil +} + +type listhead struct { + offset int // index of this header in string data + size int // total size of encoded data (including list headers) +} + +// encode writes head to the given buffer, which must be at least +// 9 bytes long. It returns the encoded bytes. +func (head *listhead) encode(buf []byte) []byte { + return buf[:puthead(buf, 0xC0, 0xF7, uint64(head.size))] +} + +// headsize returns the size of a list or string header +// for a value of the given size. +func headsize(size uint64) int { + if size < 56 { + return 1 + } + return 1 + intsize(size) +} + +// puthead writes a list or string header to buf. +// buf must be at least 9 bytes long. +func puthead(buf []byte, smalltag, largetag byte, size uint64) int { + if size < 56 { + buf[0] = smalltag + byte(size) + return 1 + } + sizesize := putint(buf[1:], size) + buf[0] = largetag + byte(sizesize) + return sizesize + 1 +} + +var encoderInterface = reflect.TypeOf(new(Encoder)).Elem() + +// makeWriter creates a writer function for the given type. +func makeWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { + kind := typ.Kind() + switch { + case typ == rawValueType: + return writeRawValue, nil + case typ.AssignableTo(reflect.PtrTo(bigInt)): + return writeBigIntPtr, nil + case typ.AssignableTo(bigInt): + return writeBigIntNoPtr, nil + case kind == reflect.Ptr: + return makePtrWriter(typ, ts) + case reflect.PtrTo(typ).Implements(encoderInterface): + return makeEncoderWriter(typ), nil + case isUint(kind): + return writeUint, nil + case kind == reflect.Bool: + return writeBool, nil + case kind == reflect.String: + return writeString, nil + case kind == reflect.Slice && isByte(typ.Elem()): + return writeBytes, nil + case kind == reflect.Array && isByte(typ.Elem()): + return makeByteArrayWriter(typ), nil + case kind == reflect.Slice || kind == reflect.Array: + return makeSliceWriter(typ, ts) + case kind == reflect.Struct: + return makeStructWriter(typ) + case kind == reflect.Interface: + return writeInterface, nil + default: + return nil, fmt.Errorf("rlp: type %v is not RLP-serializable", typ) + } +} + +func writeRawValue(val reflect.Value, w *encBuffer) error { + w.str = append(w.str, val.Bytes()...) + return nil +} + +func writeUint(val reflect.Value, w *encBuffer) error { + w.writeUint64(val.Uint()) + return nil +} + +func writeBool(val reflect.Value, w *encBuffer) error { + w.writeBool(val.Bool()) + return nil +} + +func writeBigIntPtr(val reflect.Value, w *encBuffer) error { + ptr := val.Interface().(*big.Int) + if ptr == nil { + w.str = append(w.str, 0x80) + return nil + } + if ptr.Sign() == -1 { + return ErrNegativeBigInt + } + w.writeBigInt(ptr) + return nil +} + +func writeBigIntNoPtr(val reflect.Value, w *encBuffer) error { + i := val.Interface().(big.Int) + if i.Sign() == -1 { + return ErrNegativeBigInt + } + w.writeBigInt(&i) + return nil +} + +func writeBytes(val reflect.Value, w *encBuffer) error { + w.writeBytes(val.Bytes()) + return nil +} + +func makeByteArrayWriter(typ reflect.Type) writer { + switch typ.Len() { + case 0: + return writeLengthZeroByteArray + case 1: + return writeLengthOneByteArray + default: + length := typ.Len() + return func(val reflect.Value, w *encBuffer) error { + if !val.CanAddr() { + // Getting the byte slice of val requires it to be addressable. Make it + // addressable by copying. + copy := reflect.New(val.Type()).Elem() + copy.Set(val) + val = copy + } + slice := byteArrayBytes(val, length) + w.encodeStringHeader(len(slice)) + w.str = append(w.str, slice...) + return nil + } + } +} + +func writeLengthZeroByteArray(val reflect.Value, w *encBuffer) error { + w.str = append(w.str, 0x80) + return nil +} + +func writeLengthOneByteArray(val reflect.Value, w *encBuffer) error { + b := byte(val.Index(0).Uint()) + if b <= 0x7f { + w.str = append(w.str, b) + } else { + w.str = append(w.str, 0x81, b) + } + return nil +} + +func writeString(val reflect.Value, w *encBuffer) error { + s := val.String() + if len(s) == 1 && s[0] <= 0x7f { + // fits single byte, no string header + w.str = append(w.str, s[0]) + } else { + w.encodeStringHeader(len(s)) + w.str = append(w.str, s...) + } + return nil +} + +func writeInterface(val reflect.Value, w *encBuffer) error { + if val.IsNil() { + // Write empty list. This is consistent with the previous RLP + // encoder that we had and should therefore avoid any + // problems. + w.str = append(w.str, 0xC0) + return nil + } + eval := val.Elem() + writer, err := cachedWriter(eval.Type()) + if err != nil { + return err + } + return writer(eval, w) +} + +func makeSliceWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { + etypeinfo := theTC.infoWhileGenerating(typ.Elem(), rlpstruct.Tags{}) + if etypeinfo.writerErr != nil { + return nil, etypeinfo.writerErr + } + + var wfn writer + if ts.Tail { + // This is for struct tail slices. + // w.list is not called for them. + wfn = func(val reflect.Value, w *encBuffer) error { + vlen := val.Len() + for i := 0; i < vlen; i++ { + if err := etypeinfo.writer(val.Index(i), w); err != nil { + return err + } + } + return nil + } + } else { + // This is for regular slices and arrays. + wfn = func(val reflect.Value, w *encBuffer) error { + vlen := val.Len() + if vlen == 0 { + w.str = append(w.str, 0xC0) + return nil + } + listOffset := w.list() + for i := 0; i < vlen; i++ { + if err := etypeinfo.writer(val.Index(i), w); err != nil { + return err + } + } + w.listEnd(listOffset) + return nil + } + } + return wfn, nil +} + +func makeStructWriter(typ reflect.Type) (writer, error) { + fields, err := structFields(typ) + if err != nil { + return nil, err + } + for _, f := range fields { + if f.info.writerErr != nil { + return nil, structFieldError{typ, f.index, f.info.writerErr} + } + } + + var writer writer + firstOptionalField := firstOptionalField(fields) + if firstOptionalField == len(fields) { + // This is the writer function for structs without any optional fields. + writer = func(val reflect.Value, w *encBuffer) error { + lh := w.list() + for _, f := range fields { + if err := f.info.writer(val.Field(f.index), w); err != nil { + return err + } + } + w.listEnd(lh) + return nil + } + } else { + // If there are any "optional" fields, the writer needs to perform additional + // checks to determine the output list length. + writer = func(val reflect.Value, w *encBuffer) error { + lastField := len(fields) - 1 + for ; lastField >= firstOptionalField; lastField-- { + if !val.Field(fields[lastField].index).IsZero() { + break + } + } + lh := w.list() + for i := 0; i <= lastField; i++ { + if err := fields[i].info.writer(val.Field(fields[i].index), w); err != nil { + return err + } + } + w.listEnd(lh) + return nil + } + } + return writer, nil +} + +func makePtrWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { + nilEncoding := byte(0xC0) + if typeNilKind(typ.Elem(), ts) == String { + nilEncoding = 0x80 + } + + etypeinfo := theTC.infoWhileGenerating(typ.Elem(), rlpstruct.Tags{}) + if etypeinfo.writerErr != nil { + return nil, etypeinfo.writerErr + } + + writer := func(val reflect.Value, w *encBuffer) error { + if ev := val.Elem(); ev.IsValid() { + return etypeinfo.writer(ev, w) + } + w.str = append(w.str, nilEncoding) + return nil + } + return writer, nil +} + +func makeEncoderWriter(typ reflect.Type) writer { + if typ.Implements(encoderInterface) { + return func(val reflect.Value, w *encBuffer) error { + return val.Interface().(Encoder).EncodeRLP(w) + } + } + w := func(val reflect.Value, w *encBuffer) error { + if !val.CanAddr() { + // package json simply doesn't call MarshalJSON for this case, but encodes the + // value as if it didn't implement the interface. We don't want to handle it that + // way. + return fmt.Errorf("rlp: unadressable value of type %v, EncodeRLP is pointer method", val.Type()) + } + return val.Addr().Interface().(Encoder).EncodeRLP(w) + } + return w +} + +// putint writes i to the beginning of b in big endian byte +// order, using the least number of bytes needed to represent i. +func putint(b []byte, i uint64) (size int) { + switch { + case i < (1 << 8): + b[0] = byte(i) + return 1 + case i < (1 << 16): + b[0] = byte(i >> 8) + b[1] = byte(i) + return 2 + case i < (1 << 24): + b[0] = byte(i >> 16) + b[1] = byte(i >> 8) + b[2] = byte(i) + return 3 + case i < (1 << 32): + b[0] = byte(i >> 24) + b[1] = byte(i >> 16) + b[2] = byte(i >> 8) + b[3] = byte(i) + return 4 + case i < (1 << 40): + b[0] = byte(i >> 32) + b[1] = byte(i >> 24) + b[2] = byte(i >> 16) + b[3] = byte(i >> 8) + b[4] = byte(i) + return 5 + case i < (1 << 48): + b[0] = byte(i >> 40) + b[1] = byte(i >> 32) + b[2] = byte(i >> 24) + b[3] = byte(i >> 16) + b[4] = byte(i >> 8) + b[5] = byte(i) + return 6 + case i < (1 << 56): + b[0] = byte(i >> 48) + b[1] = byte(i >> 40) + b[2] = byte(i >> 32) + b[3] = byte(i >> 24) + b[4] = byte(i >> 16) + b[5] = byte(i >> 8) + b[6] = byte(i) + return 7 + default: + b[0] = byte(i >> 56) + b[1] = byte(i >> 48) + b[2] = byte(i >> 40) + b[3] = byte(i >> 32) + b[4] = byte(i >> 24) + b[5] = byte(i >> 16) + b[6] = byte(i >> 8) + b[7] = byte(i) + return 8 + } +} + +// intsize computes the minimum number of bytes required to store i. +func intsize(i uint64) (size int) { + for size = 1; ; size++ { + if i >>= 8; i == 0 { + return size + } + } +} diff --git a/rlp/encode_test.go b/rlp/encode_test.go new file mode 100644 index 0000000000..58ddc0d120 --- /dev/null +++ b/rlp/encode_test.go @@ -0,0 +1,585 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp + +import ( + "bytes" + "errors" + "fmt" + "io" + "math/big" + "runtime" + "sync" + "testing" + + "github.com/ethereum/go-ethereum/common/math" +) + +type testEncoder struct { + err error +} + +func (e *testEncoder) EncodeRLP(w io.Writer) error { + if e == nil { + panic("EncodeRLP called on nil value") + } + if e.err != nil { + return e.err + } + w.Write([]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1}) + return nil +} + +type testEncoderValueMethod struct{} + +func (e testEncoderValueMethod) EncodeRLP(w io.Writer) error { + w.Write([]byte{0xFA, 0xFE, 0xF0}) + return nil +} + +type byteEncoder byte + +func (e byteEncoder) EncodeRLP(w io.Writer) error { + w.Write(EmptyList) + return nil +} + +type undecodableEncoder func() + +func (f undecodableEncoder) EncodeRLP(w io.Writer) error { + w.Write([]byte{0xF5, 0xF5, 0xF5}) + return nil +} + +type encodableReader struct { + A, B uint +} + +func (e *encodableReader) Read(b []byte) (int, error) { + panic("called") +} + +type namedByteType byte + +var ( + _ = Encoder(&testEncoder{}) + _ = Encoder(byteEncoder(0)) + + reader io.Reader = &encodableReader{1, 2} +) + +type encTest struct { + val interface{} + output, error string +} + +var encTests = []encTest{ + // booleans + {val: true, output: "01"}, + {val: false, output: "80"}, + + // integers + {val: uint32(0), output: "80"}, + {val: uint32(127), output: "7F"}, + {val: uint32(128), output: "8180"}, + {val: uint32(256), output: "820100"}, + {val: uint32(1024), output: "820400"}, + {val: uint32(0xFFFFFF), output: "83FFFFFF"}, + {val: uint32(0xFFFFFFFF), output: "84FFFFFFFF"}, + {val: uint64(0xFFFFFFFF), output: "84FFFFFFFF"}, + {val: uint64(0xFFFFFFFFFF), output: "85FFFFFFFFFF"}, + {val: uint64(0xFFFFFFFFFFFF), output: "86FFFFFFFFFFFF"}, + {val: uint64(0xFFFFFFFFFFFFFF), output: "87FFFFFFFFFFFFFF"}, + {val: uint64(0xFFFFFFFFFFFFFFFF), output: "88FFFFFFFFFFFFFFFF"}, + + // big integers (should match uint for small values) + {val: big.NewInt(0), output: "80"}, + {val: big.NewInt(1), output: "01"}, + {val: big.NewInt(127), output: "7F"}, + {val: big.NewInt(128), output: "8180"}, + {val: big.NewInt(256), output: "820100"}, + {val: big.NewInt(1024), output: "820400"}, + {val: big.NewInt(0xFFFFFF), output: "83FFFFFF"}, + {val: big.NewInt(0xFFFFFFFF), output: "84FFFFFFFF"}, + {val: big.NewInt(0xFFFFFFFFFF), output: "85FFFFFFFFFF"}, + {val: big.NewInt(0xFFFFFFFFFFFF), output: "86FFFFFFFFFFFF"}, + {val: big.NewInt(0xFFFFFFFFFFFFFF), output: "87FFFFFFFFFFFFFF"}, + { + val: new(big.Int).SetBytes(unhex("102030405060708090A0B0C0D0E0F2")), + output: "8F102030405060708090A0B0C0D0E0F2", + }, + { + val: new(big.Int).SetBytes(unhex("0100020003000400050006000700080009000A000B000C000D000E01")), + output: "9C0100020003000400050006000700080009000A000B000C000D000E01", + }, + { + val: new(big.Int).SetBytes(unhex("010000000000000000000000000000000000000000000000000000000000000000")), + output: "A1010000000000000000000000000000000000000000000000000000000000000000", + }, + { + val: veryBigInt, + output: "89FFFFFFFFFFFFFFFFFF", + }, + { + val: veryVeryBigInt, + output: "B848FFFFFFFFFFFFFFFFF800000000000000001BFFFFFFFFFFFFFFFFC8000000000000000045FFFFFFFFFFFFFFFFC800000000000000001BFFFFFFFFFFFFFFFFF8000000000000000001", + }, + + // non-pointer big.Int + {val: *big.NewInt(0), output: "80"}, + {val: *big.NewInt(0xFFFFFF), output: "83FFFFFF"}, + + // negative ints are not supported + {val: big.NewInt(-1), error: "rlp: cannot encode negative big.Int"}, + {val: *big.NewInt(-1), error: "rlp: cannot encode negative big.Int"}, + + // byte arrays + {val: [0]byte{}, output: "80"}, + {val: [1]byte{0}, output: "00"}, + {val: [1]byte{1}, output: "01"}, + {val: [1]byte{0x7F}, output: "7F"}, + {val: [1]byte{0x80}, output: "8180"}, + {val: [1]byte{0xFF}, output: "81FF"}, + {val: [3]byte{1, 2, 3}, output: "83010203"}, + {val: [57]byte{1, 2, 3}, output: "B839010203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, + + // named byte type arrays + {val: [0]namedByteType{}, output: "80"}, + {val: [1]namedByteType{0}, output: "00"}, + {val: [1]namedByteType{1}, output: "01"}, + {val: [1]namedByteType{0x7F}, output: "7F"}, + {val: [1]namedByteType{0x80}, output: "8180"}, + {val: [1]namedByteType{0xFF}, output: "81FF"}, + {val: [3]namedByteType{1, 2, 3}, output: "83010203"}, + {val: [57]namedByteType{1, 2, 3}, output: "B839010203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, + + // byte slices + {val: []byte{}, output: "80"}, + {val: []byte{0}, output: "00"}, + {val: []byte{0x7E}, output: "7E"}, + {val: []byte{0x7F}, output: "7F"}, + {val: []byte{0x80}, output: "8180"}, + {val: []byte{1, 2, 3}, output: "83010203"}, + + // named byte type slices + {val: []namedByteType{}, output: "80"}, + {val: []namedByteType{0}, output: "00"}, + {val: []namedByteType{0x7E}, output: "7E"}, + {val: []namedByteType{0x7F}, output: "7F"}, + {val: []namedByteType{0x80}, output: "8180"}, + {val: []namedByteType{1, 2, 3}, output: "83010203"}, + + // strings + {val: "", output: "80"}, + {val: "\x7E", output: "7E"}, + {val: "\x7F", output: "7F"}, + {val: "\x80", output: "8180"}, + {val: "dog", output: "83646F67"}, + { + val: "Lorem ipsum dolor sit amet, consectetur adipisicing eli", + output: "B74C6F72656D20697073756D20646F6C6F722073697420616D65742C20636F6E7365637465747572206164697069736963696E6720656C69", + }, + { + val: "Lorem ipsum dolor sit amet, consectetur adipisicing elit", + output: "B8384C6F72656D20697073756D20646F6C6F722073697420616D65742C20636F6E7365637465747572206164697069736963696E6720656C6974", + }, + { + val: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur mauris magna, suscipit sed vehicula non, iaculis faucibus tortor. Proin suscipit ultricies malesuada. Duis tortor elit, dictum quis tristique eu, ultrices at risus. Morbi a est imperdiet mi ullamcorper aliquet suscipit nec lorem. Aenean quis leo mollis, vulputate elit varius, consequat enim. Nulla ultrices turpis justo, et posuere urna consectetur nec. Proin non convallis metus. Donec tempor ipsum in mauris congue sollicitudin. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse convallis sem vel massa faucibus, eget lacinia lacus tempor. Nulla quis ultricies purus. Proin auctor rhoncus nibh condimentum mollis. Aliquam consequat enim at metus luctus, a eleifend purus egestas. Curabitur at nibh metus. Nam bibendum, neque at auctor tristique, lorem libero aliquet arcu, non interdum tellus lectus sit amet eros. Cras rhoncus, metus ac ornare cursus, dolor justo ultrices metus, at ullamcorper volutpat", + output: "B904004C6F72656D20697073756D20646F6C6F722073697420616D65742C20636F6E73656374657475722061646970697363696E6720656C69742E20437572616269747572206D6175726973206D61676E612C20737573636970697420736564207665686963756C61206E6F6E2C20696163756C697320666175636962757320746F72746F722E2050726F696E20737573636970697420756C74726963696573206D616C6573756164612E204475697320746F72746F7220656C69742C2064696374756D2071756973207472697374697175652065752C20756C7472696365732061742072697375732E204D6F72626920612065737420696D70657264696574206D6920756C6C616D636F7270657220616C6971756574207375736369706974206E6563206C6F72656D2E2041656E65616E2071756973206C656F206D6F6C6C69732C2076756C70757461746520656C6974207661726975732C20636F6E73657175617420656E696D2E204E756C6C6120756C74726963657320747572706973206A7573746F2C20657420706F73756572652075726E6120636F6E7365637465747572206E65632E2050726F696E206E6F6E20636F6E76616C6C6973206D657475732E20446F6E65632074656D706F7220697073756D20696E206D617572697320636F6E67756520736F6C6C696369747564696E2E20566573746962756C756D20616E746520697073756D207072696D697320696E206661756369627573206F726369206C756374757320657420756C74726963657320706F737565726520637562696C69612043757261653B2053757370656E646973736520636F6E76616C6C69732073656D2076656C206D617373612066617563696275732C2065676574206C6163696E6961206C616375732074656D706F722E204E756C6C61207175697320756C747269636965732070757275732E2050726F696E20617563746F722072686F6E637573206E69626820636F6E64696D656E74756D206D6F6C6C69732E20416C697175616D20636F6E73657175617420656E696D206174206D65747573206C75637475732C206120656C656966656E6420707572757320656765737461732E20437572616269747572206174206E696268206D657475732E204E616D20626962656E64756D2C206E6571756520617420617563746F72207472697374697175652C206C6F72656D206C696265726F20616C697175657420617263752C206E6F6E20696E74657264756D2074656C6C7573206C65637475732073697420616D65742065726F732E20437261732072686F6E6375732C206D65747573206163206F726E617265206375727375732C20646F6C6F72206A7573746F20756C747269636573206D657475732C20617420756C6C616D636F7270657220766F6C7574706174", + }, + + // slices + {val: []uint{}, output: "C0"}, + {val: []uint{1, 2, 3}, output: "C3010203"}, + { + // [ [], [[]], [ [], [[]] ] ] + val: []interface{}{[]interface{}{}, [][]interface{}{{}}, []interface{}{[]interface{}{}, [][]interface{}{{}}}}, + output: "C7C0C1C0C3C0C1C0", + }, + { + val: []string{"aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii", "jjj", "kkk", "lll", "mmm", "nnn", "ooo"}, + output: "F83C836161618362626283636363836464648365656583666666836767678368686883696969836A6A6A836B6B6B836C6C6C836D6D6D836E6E6E836F6F6F", + }, + { + val: []interface{}{uint(1), uint(0xFFFFFF), []interface{}{[]uint{4, 5, 5}}, "abc"}, + output: "CE0183FFFFFFC4C304050583616263", + }, + { + val: [][]string{ + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + {"asdf", "qwer", "zxcv"}, + }, + output: "F90200CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376", + }, + + // RawValue + {val: RawValue(unhex("01")), output: "01"}, + {val: RawValue(unhex("82FFFF")), output: "82FFFF"}, + {val: []RawValue{unhex("01"), unhex("02")}, output: "C20102"}, + + // structs + {val: simplestruct{}, output: "C28080"}, + {val: simplestruct{A: 3, B: "foo"}, output: "C50383666F6F"}, + {val: &recstruct{5, nil}, output: "C205C0"}, + {val: &recstruct{5, &recstruct{4, &recstruct{3, nil}}}, output: "C605C404C203C0"}, + {val: &intField{X: 3}, error: "rlp: type int is not RLP-serializable (struct field rlp.intField.X)"}, + + // struct tag "-" + {val: &ignoredField{A: 1, B: 2, C: 3}, output: "C20103"}, + + // struct tag "tail" + {val: &tailRaw{A: 1, Tail: []RawValue{unhex("02"), unhex("03")}}, output: "C3010203"}, + {val: &tailRaw{A: 1, Tail: []RawValue{unhex("02")}}, output: "C20102"}, + {val: &tailRaw{A: 1, Tail: []RawValue{}}, output: "C101"}, + {val: &tailRaw{A: 1, Tail: nil}, output: "C101"}, + + // struct tag "optional" + {val: &optionalFields{}, output: "C180"}, + {val: &optionalFields{A: 1}, output: "C101"}, + {val: &optionalFields{A: 1, B: 2}, output: "C20102"}, + {val: &optionalFields{A: 1, B: 2, C: 3}, output: "C3010203"}, + {val: &optionalFields{A: 1, B: 0, C: 3}, output: "C3018003"}, + {val: &optionalAndTailField{A: 1}, output: "C101"}, + {val: &optionalAndTailField{A: 1, B: 2}, output: "C20102"}, + {val: &optionalAndTailField{A: 1, Tail: []uint{5, 6}}, output: "C401800506"}, + {val: &optionalAndTailField{A: 1, Tail: []uint{5, 6}}, output: "C401800506"}, + {val: &optionalBigIntField{A: 1}, output: "C101"}, + {val: &optionalPtrField{A: 1}, output: "C101"}, + {val: &optionalPtrFieldNil{A: 1}, output: "C101"}, + + // nil + {val: (*uint)(nil), output: "80"}, + {val: (*string)(nil), output: "80"}, + {val: (*[]byte)(nil), output: "80"}, + {val: (*[10]byte)(nil), output: "80"}, + {val: (*big.Int)(nil), output: "80"}, + {val: (*[]string)(nil), output: "C0"}, + {val: (*[10]string)(nil), output: "C0"}, + {val: (*[]interface{})(nil), output: "C0"}, + {val: (*[]struct{ uint })(nil), output: "C0"}, + {val: (*interface{})(nil), output: "C0"}, + + // nil struct fields + { + val: struct { + X *[]byte + }{}, + output: "C180", + }, + { + val: struct { + X *[2]byte + }{}, + output: "C180", + }, + { + val: struct { + X *uint64 + }{}, + output: "C180", + }, + { + val: struct { + X *uint64 `rlp:"nilList"` + }{}, + output: "C1C0", + }, + { + val: struct { + X *[]uint64 + }{}, + output: "C1C0", + }, + { + val: struct { + X *[]uint64 `rlp:"nilString"` + }{}, + output: "C180", + }, + + // interfaces + {val: []io.Reader{reader}, output: "C3C20102"}, // the contained value is a struct + + // Encoder + {val: (*testEncoder)(nil), output: "C0"}, + {val: &testEncoder{}, output: "00010001000100010001"}, + {val: &testEncoder{errors.New("test error")}, error: "test error"}, + {val: struct{ E testEncoderValueMethod }{}, output: "C3FAFEF0"}, + {val: struct{ E *testEncoderValueMethod }{}, output: "C1C0"}, + + // Verify that the Encoder interface works for unsupported types like func(). + {val: undecodableEncoder(func() {}), output: "F5F5F5"}, + + // Verify that pointer method testEncoder.EncodeRLP is called for + // addressable non-pointer values. + {val: &struct{ TE testEncoder }{testEncoder{}}, output: "CA00010001000100010001"}, + {val: &struct{ TE testEncoder }{testEncoder{errors.New("test error")}}, error: "test error"}, + + // Verify the error for non-addressable non-pointer Encoder. + {val: testEncoder{}, error: "rlp: unadressable value of type rlp.testEncoder, EncodeRLP is pointer method"}, + + // Verify Encoder takes precedence over []byte. + {val: []byteEncoder{0, 1, 2, 3, 4}, output: "C5C0C0C0C0C0"}, +} + +func runEncTests(t *testing.T, f func(val interface{}) ([]byte, error)) { + for i, test := range encTests { + output, err := f(test.val) + if err != nil && test.error == "" { + t.Errorf("test %d: unexpected error: %v\nvalue %#v\ntype %T", + i, err, test.val, test.val) + continue + } + if test.error != "" && fmt.Sprint(err) != test.error { + t.Errorf("test %d: error mismatch\ngot %v\nwant %v\nvalue %#v\ntype %T", + i, err, test.error, test.val, test.val) + continue + } + if err == nil && !bytes.Equal(output, unhex(test.output)) { + t.Errorf("test %d: output mismatch:\ngot %X\nwant %s\nvalue %#v\ntype %T", + i, output, test.output, test.val, test.val) + } + } +} + +func TestEncode(t *testing.T) { + runEncTests(t, func(val interface{}) ([]byte, error) { + b := new(bytes.Buffer) + err := Encode(b, val) + return b.Bytes(), err + }) +} + +func TestEncodeToBytes(t *testing.T) { + runEncTests(t, EncodeToBytes) +} + +func TestEncodeAppendToBytes(t *testing.T) { + buffer := make([]byte, 20) + runEncTests(t, func(val interface{}) ([]byte, error) { + w := NewEncoderBuffer(nil) + defer w.Flush() + + err := Encode(w, val) + if err != nil { + return nil, err + } + output := w.AppendToBytes(buffer[:0]) + return output, nil + }) +} + +func TestEncodeToReader(t *testing.T) { + runEncTests(t, func(val interface{}) ([]byte, error) { + _, r, err := EncodeToReader(val) + if err != nil { + return nil, err + } + return io.ReadAll(r) + }) +} + +func TestEncodeToReaderPiecewise(t *testing.T) { + runEncTests(t, func(val interface{}) ([]byte, error) { + size, r, err := EncodeToReader(val) + if err != nil { + return nil, err + } + + // read output piecewise + output := make([]byte, size) + for start, end := 0, 0; start < size; start = end { + if remaining := size - start; remaining < 3 { + end += remaining + } else { + end = start + 3 + } + n, err := r.Read(output[start:end]) + end = start + n + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + } + return output, nil + }) +} + +// This is a regression test verifying that encReader +// returns its encbuf to the pool only once. +func TestEncodeToReaderReturnToPool(t *testing.T) { + buf := make([]byte, 50) + wg := new(sync.WaitGroup) + for i := 0; i < 5; i++ { + wg.Add(1) + go func() { + for i := 0; i < 1000; i++ { + _, r, _ := EncodeToReader("foo") + io.ReadAll(r) + r.Read(buf) + r.Read(buf) + r.Read(buf) + r.Read(buf) + } + wg.Done() + }() + } + wg.Wait() +} + +var sink interface{} + +func BenchmarkIntsize(b *testing.B) { + for i := 0; i < b.N; i++ { + sink = intsize(0x12345678) + } +} + +func BenchmarkPutint(b *testing.B) { + buf := make([]byte, 8) + for i := 0; i < b.N; i++ { + putint(buf, 0x12345678) + sink = buf + } +} + +func BenchmarkEncodeBigInts(b *testing.B) { + ints := make([]*big.Int, 200) + for i := range ints { + ints[i] = math.BigPow(2, int64(i)) + } + out := bytes.NewBuffer(make([]byte, 0, 4096)) + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + out.Reset() + if err := Encode(out, ints); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEncodeConcurrentInterface(b *testing.B) { + type struct1 struct { + A string + B *big.Int + C [20]byte + } + value := []interface{}{ + uint(999), + &struct1{A: "hello", B: big.NewInt(0xFFFFFFFF)}, + [10]byte{1, 2, 3, 4, 5, 6}, + []string{"yeah", "yeah", "yeah"}, + } + + var wg sync.WaitGroup + for cpu := 0; cpu < runtime.NumCPU(); cpu++ { + wg.Add(1) + go func() { + defer wg.Done() + + var buffer bytes.Buffer + for i := 0; i < b.N; i++ { + buffer.Reset() + err := Encode(&buffer, value) + if err != nil { + panic(err) + } + } + }() + } + wg.Wait() +} + +type byteArrayStruct struct { + A [20]byte + B [32]byte + C [32]byte +} + +func BenchmarkEncodeByteArrayStruct(b *testing.B) { + var out bytes.Buffer + var value byteArrayStruct + + b.ReportAllocs() + for i := 0; i < b.N; i++ { + out.Reset() + if err := Encode(&out, &value); err != nil { + b.Fatal(err) + } + } +} + +type structSliceElem struct { + X uint64 + Y uint64 + Z uint64 +} + +type structPtrSlice []*structSliceElem + +func BenchmarkEncodeStructPtrSlice(b *testing.B) { + var out bytes.Buffer + var value = structPtrSlice{ + &structSliceElem{1, 1, 1}, + &structSliceElem{2, 2, 2}, + &structSliceElem{3, 3, 3}, + &structSliceElem{5, 5, 5}, + &structSliceElem{6, 6, 6}, + &structSliceElem{7, 7, 7}, + } + + b.ReportAllocs() + for i := 0; i < b.N; i++ { + out.Reset() + if err := Encode(&out, &value); err != nil { + b.Fatal(err) + } + } +} diff --git a/rlp/encoder_example_test.go b/rlp/encoder_example_test.go new file mode 100644 index 0000000000..6b27513968 --- /dev/null +++ b/rlp/encoder_example_test.go @@ -0,0 +1,48 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp_test + +import ( + "fmt" + "io" + + "github.com/tenderly/coreth/rlp" +) + +type MyCoolType struct { + Name string + a, b uint +} + +// EncodeRLP writes x as RLP list [a, b] that omits the Name field. +func (x *MyCoolType) EncodeRLP(w io.Writer) (err error) { + return rlp.Encode(w, []uint{x.a, x.b}) +} + +func ExampleEncoder() { + var t *MyCoolType // t is nil pointer to MyCoolType + bytes, _ := rlp.EncodeToBytes(t) + fmt.Printf("%v → %X\n", t, bytes) + + t = &MyCoolType{Name: "foobar", a: 5, b: 6} + bytes, _ = rlp.EncodeToBytes(t) + fmt.Printf("%v → %X\n", t, bytes) + + // Output: + // → C0 + // &{foobar 5 6} → C20506 +} diff --git a/rlp/internal/rlpstruct/rlpstruct.go b/rlp/internal/rlpstruct/rlpstruct.go new file mode 100644 index 0000000000..1edead96ce --- /dev/null +++ b/rlp/internal/rlpstruct/rlpstruct.go @@ -0,0 +1,213 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package rlpstruct implements struct processing for RLP encoding/decoding. +// +// In particular, this package handles all rules around field filtering, +// struct tags and nil value determination. +package rlpstruct + +import ( + "fmt" + "reflect" + "strings" +) + +// Field represents a struct field. +type Field struct { + Name string + Index int + Exported bool + Type Type + Tag string +} + +// Type represents the attributes of a Go type. +type Type struct { + Name string + Kind reflect.Kind + IsEncoder bool // whether type implements rlp.Encoder + IsDecoder bool // whether type implements rlp.Decoder + Elem *Type // non-nil for Kind values of Ptr, Slice, Array +} + +// defaultNilValue determines whether a nil pointer to t encodes/decodes +// as an empty string or empty list. +func (t Type) DefaultNilValue() NilKind { + k := t.Kind + if isUint(k) || k == reflect.String || k == reflect.Bool || isByteArray(t) { + return NilKindString + } + return NilKindList +} + +// NilKind is the RLP value encoded in place of nil pointers. +type NilKind uint8 + +const ( + NilKindString NilKind = 0x80 + NilKindList NilKind = 0xC0 +) + +// Tags represents struct tags. +type Tags struct { + // rlp:"nil" controls whether empty input results in a nil pointer. + // nilKind is the kind of empty value allowed for the field. + NilKind NilKind + NilOK bool + + // rlp:"optional" allows for a field to be missing in the input list. + // If this is set, all subsequent fields must also be optional. + Optional bool + + // rlp:"tail" controls whether this field swallows additional list elements. It can + // only be set for the last field, which must be of slice type. + Tail bool + + // rlp:"-" ignores fields. + Ignored bool +} + +// TagError is raised for invalid struct tags. +type TagError struct { + StructType string + + // These are set by this package. + Field string + Tag string + Err string +} + +func (e TagError) Error() string { + field := "field " + e.Field + if e.StructType != "" { + field = e.StructType + "." + e.Field + } + return fmt.Sprintf("rlp: invalid struct tag %q for %s (%s)", e.Tag, field, e.Err) +} + +// ProcessFields filters the given struct fields, returning only fields +// that should be considered for encoding/decoding. +func ProcessFields(allFields []Field) ([]Field, []Tags, error) { + lastPublic := lastPublicField(allFields) + + // Gather all exported fields and their tags. + var fields []Field + var tags []Tags + for _, field := range allFields { + if !field.Exported { + continue + } + ts, err := parseTag(field, lastPublic) + if err != nil { + return nil, nil, err + } + if ts.Ignored { + continue + } + fields = append(fields, field) + tags = append(tags, ts) + } + + // Verify optional field consistency. If any optional field exists, + // all fields after it must also be optional. Note: optional + tail + // is supported. + var anyOptional bool + var firstOptionalName string + for i, ts := range tags { + name := fields[i].Name + if ts.Optional || ts.Tail { + if !anyOptional { + firstOptionalName = name + } + anyOptional = true + } else { + if anyOptional { + msg := fmt.Sprintf("must be optional because preceding field %q is optional", firstOptionalName) + return nil, nil, TagError{Field: name, Err: msg} + } + } + } + return fields, tags, nil +} + +func parseTag(field Field, lastPublic int) (Tags, error) { + name := field.Name + tag := reflect.StructTag(field.Tag) + var ts Tags + for _, t := range strings.Split(tag.Get("rlp"), ",") { + switch t = strings.TrimSpace(t); t { + case "": + // empty tag is allowed for some reason + case "-": + ts.Ignored = true + case "nil", "nilString", "nilList": + ts.NilOK = true + if field.Type.Kind != reflect.Ptr { + return ts, TagError{Field: name, Tag: t, Err: "field is not a pointer"} + } + switch t { + case "nil": + ts.NilKind = field.Type.Elem.DefaultNilValue() + case "nilString": + ts.NilKind = NilKindString + case "nilList": + ts.NilKind = NilKindList + } + case "optional": + ts.Optional = true + if ts.Tail { + return ts, TagError{Field: name, Tag: t, Err: `also has "tail" tag`} + } + case "tail": + ts.Tail = true + if field.Index != lastPublic { + return ts, TagError{Field: name, Tag: t, Err: "must be on last field"} + } + if ts.Optional { + return ts, TagError{Field: name, Tag: t, Err: `also has "optional" tag`} + } + if field.Type.Kind != reflect.Slice { + return ts, TagError{Field: name, Tag: t, Err: "field type is not slice"} + } + default: + return ts, TagError{Field: name, Tag: t, Err: "unknown tag"} + } + } + return ts, nil +} + +func lastPublicField(fields []Field) int { + last := 0 + for _, f := range fields { + if f.Exported { + last = f.Index + } + } + return last +} + +func isUint(k reflect.Kind) bool { + return k >= reflect.Uint && k <= reflect.Uintptr +} + +func isByte(typ Type) bool { + return typ.Kind == reflect.Uint8 && !typ.IsEncoder +} + +func isByteArray(typ Type) bool { + return (typ.Kind == reflect.Slice || typ.Kind == reflect.Array) && isByte(*typ.Elem) +} diff --git a/rlp/iterator.go b/rlp/iterator.go new file mode 100644 index 0000000000..6be574572e --- /dev/null +++ b/rlp/iterator.go @@ -0,0 +1,60 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp + +type listIterator struct { + data []byte + next []byte + err error +} + +// NewListIterator creates an iterator for the (list) represented by data +// TODO: Consider removing this implementation, as it is no longer used. +func NewListIterator(data RawValue) (*listIterator, error) { + k, t, c, err := readKind(data) + if err != nil { + return nil, err + } + if k != List { + return nil, ErrExpectedList + } + it := &listIterator{ + data: data[t : t+c], + } + return it, nil +} + +// Next forwards the iterator one step, returns true if it was not at end yet +func (it *listIterator) Next() bool { + if len(it.data) == 0 { + return false + } + _, t, c, err := readKind(it.data) + it.next = it.data[:t+c] + it.data = it.data[t+c:] + it.err = err + return true +} + +// Value returns the current value +func (it *listIterator) Value() []byte { + return it.next +} + +func (it *listIterator) Err() error { + return it.err +} diff --git a/rlp/iterator_test.go b/rlp/iterator_test.go new file mode 100644 index 0000000000..a22aaec862 --- /dev/null +++ b/rlp/iterator_test.go @@ -0,0 +1,59 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// TestIterator tests some basic things about the ListIterator. A more +// comprehensive test can be found in core/rlp_test.go, where we can +// use both types and rlp without dependency cycles +func TestIterator(t *testing.T) { + bodyRlpHex := "0xf902cbf8d6f869800182c35094000000000000000000000000000000000000aaaa808a000000000000000000001ba01025c66fad28b4ce3370222624d952c35529e602af7cbe04f667371f61b0e3b3a00ab8813514d1217059748fd903288ace1b4001a4bc5fbde2790debdc8167de2ff869010182c35094000000000000000000000000000000000000aaaa808a000000000000000000001ca05ac4cf1d19be06f3742c21df6c49a7e929ceb3dbaf6a09f3cfb56ff6828bd9a7a06875970133a35e63ac06d360aa166d228cc013e9b96e0a2cae7f55b22e1ee2e8f901f0f901eda0c75448377c0e426b8017b23c5f77379ecf69abc1d5c224284ad3ba1c46c59adaa00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808080808080a00000000000000000000000000000000000000000000000000000000000000000880000000000000000" + bodyRlp := hexutil.MustDecode(bodyRlpHex) + + it, err := NewListIterator(bodyRlp) + if err != nil { + t.Fatal(err) + } + // Check that txs exist + if !it.Next() { + t.Fatal("expected two elems, got zero") + } + txs := it.Value() + // Check that uncles exist + if !it.Next() { + t.Fatal("expected two elems, got one") + } + txit, err := NewListIterator(txs) + if err != nil { + t.Fatal(err) + } + var i = 0 + for txit.Next() { + if txit.err != nil { + t.Fatal(txit.err) + } + i++ + } + if exp := 2; i != exp { + t.Errorf("count wrong, expected %d got %d", i, exp) + } +} diff --git a/rlp/raw.go b/rlp/raw.go new file mode 100644 index 0000000000..f355efc144 --- /dev/null +++ b/rlp/raw.go @@ -0,0 +1,261 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp + +import ( + "io" + "reflect" +) + +// RawValue represents an encoded RLP value and can be used to delay +// RLP decoding or to precompute an encoding. Note that the decoder does +// not verify whether the content of RawValues is valid RLP. +type RawValue []byte + +var rawValueType = reflect.TypeOf(RawValue{}) + +// ListSize returns the encoded size of an RLP list with the given +// content size. +func ListSize(contentSize uint64) uint64 { + return uint64(headsize(contentSize)) + contentSize +} + +// IntSize returns the encoded size of the integer x. +func IntSize(x uint64) int { + if x < 0x80 { + return 1 + } + return 1 + intsize(x) +} + +// Split returns the content of first RLP value and any +// bytes after the value as subslices of b. +func Split(b []byte) (k Kind, content, rest []byte, err error) { + k, ts, cs, err := readKind(b) + if err != nil { + return 0, nil, b, err + } + return k, b[ts : ts+cs], b[ts+cs:], nil +} + +// SplitString splits b into the content of an RLP string +// and any remaining bytes after the string. +func SplitString(b []byte) (content, rest []byte, err error) { + k, content, rest, err := Split(b) + if err != nil { + return nil, b, err + } + if k == List { + return nil, b, ErrExpectedString + } + return content, rest, nil +} + +// SplitUint64 decodes an integer at the beginning of b. +// It also returns the remaining data after the integer in 'rest'. +func SplitUint64(b []byte) (x uint64, rest []byte, err error) { + content, rest, err := SplitString(b) + if err != nil { + return 0, b, err + } + switch { + case len(content) == 0: + return 0, rest, nil + case len(content) == 1: + if content[0] == 0 { + return 0, b, ErrCanonInt + } + return uint64(content[0]), rest, nil + case len(content) > 8: + return 0, b, errUintOverflow + default: + x, err = readSize(content, byte(len(content))) + if err != nil { + return 0, b, ErrCanonInt + } + return x, rest, nil + } +} + +// SplitList splits b into the content of a list and any remaining +// bytes after the list. +func SplitList(b []byte) (content, rest []byte, err error) { + k, content, rest, err := Split(b) + if err != nil { + return nil, b, err + } + if k != List { + return nil, b, ErrExpectedList + } + return content, rest, nil +} + +// CountValues counts the number of encoded values in b. +func CountValues(b []byte) (int, error) { + i := 0 + for ; len(b) > 0; i++ { + _, tagsize, size, err := readKind(b) + if err != nil { + return 0, err + } + b = b[tagsize+size:] + } + return i, nil +} + +func readKind(buf []byte) (k Kind, tagsize, contentsize uint64, err error) { + if len(buf) == 0 { + return 0, 0, 0, io.ErrUnexpectedEOF + } + b := buf[0] + switch { + case b < 0x80: + k = Byte + tagsize = 0 + contentsize = 1 + case b < 0xB8: + k = String + tagsize = 1 + contentsize = uint64(b - 0x80) + // Reject strings that should've been single bytes. + if contentsize == 1 && len(buf) > 1 && buf[1] < 128 { + return 0, 0, 0, ErrCanonSize + } + case b < 0xC0: + k = String + tagsize = uint64(b-0xB7) + 1 + contentsize, err = readSize(buf[1:], b-0xB7) + case b < 0xF8: + k = List + tagsize = 1 + contentsize = uint64(b - 0xC0) + default: + k = List + tagsize = uint64(b-0xF7) + 1 + contentsize, err = readSize(buf[1:], b-0xF7) + } + if err != nil { + return 0, 0, 0, err + } + // Reject values larger than the input slice. + if contentsize > uint64(len(buf))-tagsize { + return 0, 0, 0, ErrValueTooLarge + } + return k, tagsize, contentsize, err +} + +func readSize(b []byte, slen byte) (uint64, error) { + if int(slen) > len(b) { + return 0, io.ErrUnexpectedEOF + } + var s uint64 + switch slen { + case 1: + s = uint64(b[0]) + case 2: + s = uint64(b[0])<<8 | uint64(b[1]) + case 3: + s = uint64(b[0])<<16 | uint64(b[1])<<8 | uint64(b[2]) + case 4: + s = uint64(b[0])<<24 | uint64(b[1])<<16 | uint64(b[2])<<8 | uint64(b[3]) + case 5: + s = uint64(b[0])<<32 | uint64(b[1])<<24 | uint64(b[2])<<16 | uint64(b[3])<<8 | uint64(b[4]) + case 6: + s = uint64(b[0])<<40 | uint64(b[1])<<32 | uint64(b[2])<<24 | uint64(b[3])<<16 | uint64(b[4])<<8 | uint64(b[5]) + case 7: + s = uint64(b[0])<<48 | uint64(b[1])<<40 | uint64(b[2])<<32 | uint64(b[3])<<24 | uint64(b[4])<<16 | uint64(b[5])<<8 | uint64(b[6]) + case 8: + s = uint64(b[0])<<56 | uint64(b[1])<<48 | uint64(b[2])<<40 | uint64(b[3])<<32 | uint64(b[4])<<24 | uint64(b[5])<<16 | uint64(b[6])<<8 | uint64(b[7]) + } + // Reject sizes < 56 (shouldn't have separate size) and sizes with + // leading zero bytes. + if s < 56 || b[0] == 0 { + return 0, ErrCanonSize + } + return s, nil +} + +// AppendUint64 appends the RLP encoding of i to b, and returns the resulting slice. +func AppendUint64(b []byte, i uint64) []byte { + if i == 0 { + return append(b, 0x80) + } else if i < 128 { + return append(b, byte(i)) + } + switch { + case i < (1 << 8): + return append(b, 0x81, byte(i)) + case i < (1 << 16): + return append(b, 0x82, + byte(i>>8), + byte(i), + ) + case i < (1 << 24): + return append(b, 0x83, + byte(i>>16), + byte(i>>8), + byte(i), + ) + case i < (1 << 32): + return append(b, 0x84, + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + case i < (1 << 40): + return append(b, 0x85, + byte(i>>32), + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + + case i < (1 << 48): + return append(b, 0x86, + byte(i>>40), + byte(i>>32), + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + case i < (1 << 56): + return append(b, 0x87, + byte(i>>48), + byte(i>>40), + byte(i>>32), + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + + default: + return append(b, 0x88, + byte(i>>56), + byte(i>>48), + byte(i>>40), + byte(i>>32), + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + } +} diff --git a/rlp/raw_test.go b/rlp/raw_test.go new file mode 100644 index 0000000000..46adff22c5 --- /dev/null +++ b/rlp/raw_test.go @@ -0,0 +1,285 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp + +import ( + "bytes" + "errors" + "io" + "testing" + "testing/quick" +) + +func TestCountValues(t *testing.T) { + tests := []struct { + input string // note: spaces in input are stripped by unhex + count int + err error + }{ + // simple cases + {"", 0, nil}, + {"00", 1, nil}, + {"80", 1, nil}, + {"C0", 1, nil}, + {"01 02 03", 3, nil}, + {"01 C406070809 02", 3, nil}, + {"820101 820202 8403030303 04", 4, nil}, + + // size errors + {"8142", 0, ErrCanonSize}, + {"01 01 8142", 0, ErrCanonSize}, + {"02 84020202", 0, ErrValueTooLarge}, + + { + input: "A12000BF49F440A1CD0527E4D06E2765654C0F56452257516D793A9B8D604DCFDF2AB853F851808D10000000000000000000000000A056E81F171BCC55A6FF8345E692C0F86E5B48E01B996CADC001622FB5E363B421A0C5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470", + count: 2, + }, + } + for i, test := range tests { + count, err := CountValues(unhex(test.input)) + if count != test.count { + t.Errorf("test %d: count mismatch, got %d want %d\ninput: %s", i, count, test.count, test.input) + } + if !errors.Is(err, test.err) { + t.Errorf("test %d: err mismatch, got %q want %q\ninput: %s", i, err, test.err, test.input) + } + } +} + +func TestSplitTypes(t *testing.T) { + if _, _, err := SplitString(unhex("C100")); err != ErrExpectedString { + t.Errorf("SplitString returned %q, want %q", err, ErrExpectedString) + } + if _, _, err := SplitList(unhex("01")); err != ErrExpectedList { + t.Errorf("SplitString returned %q, want %q", err, ErrExpectedList) + } + if _, _, err := SplitList(unhex("81FF")); err != ErrExpectedList { + t.Errorf("SplitString returned %q, want %q", err, ErrExpectedList) + } +} + +func TestSplitUint64(t *testing.T) { + tests := []struct { + input string + val uint64 + rest string + err error + }{ + {"01", 1, "", nil}, + {"7FFF", 0x7F, "FF", nil}, + {"80FF", 0, "FF", nil}, + {"81FAFF", 0xFA, "FF", nil}, + {"82FAFAFF", 0xFAFA, "FF", nil}, + {"83FAFAFAFF", 0xFAFAFA, "FF", nil}, + {"84FAFAFAFAFF", 0xFAFAFAFA, "FF", nil}, + {"85FAFAFAFAFAFF", 0xFAFAFAFAFA, "FF", nil}, + {"86FAFAFAFAFAFAFF", 0xFAFAFAFAFAFA, "FF", nil}, + {"87FAFAFAFAFAFAFAFF", 0xFAFAFAFAFAFAFA, "FF", nil}, + {"88FAFAFAFAFAFAFAFAFF", 0xFAFAFAFAFAFAFAFA, "FF", nil}, + + // errors + {"", 0, "", io.ErrUnexpectedEOF}, + {"00", 0, "00", ErrCanonInt}, + {"81", 0, "81", ErrValueTooLarge}, + {"8100", 0, "8100", ErrCanonSize}, + {"8200FF", 0, "8200FF", ErrCanonInt}, + {"8103FF", 0, "8103FF", ErrCanonSize}, + {"89FAFAFAFAFAFAFAFAFAFF", 0, "89FAFAFAFAFAFAFAFAFAFF", errUintOverflow}, + } + + for i, test := range tests { + val, rest, err := SplitUint64(unhex(test.input)) + if val != test.val { + t.Errorf("test %d: val mismatch: got %x, want %x (input %q)", i, val, test.val, test.input) + } + if !bytes.Equal(rest, unhex(test.rest)) { + t.Errorf("test %d: rest mismatch: got %x, want %s (input %q)", i, rest, test.rest, test.input) + } + if err != test.err { + t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err) + } + } +} + +func TestSplit(t *testing.T) { + tests := []struct { + input string + kind Kind + val, rest string + err error + }{ + {input: "00FFFF", kind: Byte, val: "00", rest: "FFFF"}, + {input: "01FFFF", kind: Byte, val: "01", rest: "FFFF"}, + {input: "7FFFFF", kind: Byte, val: "7F", rest: "FFFF"}, + {input: "80FFFF", kind: String, val: "", rest: "FFFF"}, + {input: "C3010203", kind: List, val: "010203"}, + + // errors + {input: "", err: io.ErrUnexpectedEOF}, + + {input: "8141", err: ErrCanonSize, rest: "8141"}, + {input: "B800", err: ErrCanonSize, rest: "B800"}, + {input: "B802FFFF", err: ErrCanonSize, rest: "B802FFFF"}, + {input: "B90000", err: ErrCanonSize, rest: "B90000"}, + {input: "B90055", err: ErrCanonSize, rest: "B90055"}, + {input: "BA0002FFFF", err: ErrCanonSize, rest: "BA0002FFFF"}, + {input: "F800", err: ErrCanonSize, rest: "F800"}, + {input: "F90000", err: ErrCanonSize, rest: "F90000"}, + {input: "F90055", err: ErrCanonSize, rest: "F90055"}, + {input: "FA0002FFFF", err: ErrCanonSize, rest: "FA0002FFFF"}, + + {input: "81", err: ErrValueTooLarge, rest: "81"}, + {input: "8501010101", err: ErrValueTooLarge, rest: "8501010101"}, + {input: "C60607080902", err: ErrValueTooLarge, rest: "C60607080902"}, + + // size check overflow + {input: "BFFFFFFFFFFFFFFFFF", err: ErrValueTooLarge, rest: "BFFFFFFFFFFFFFFFFF"}, + {input: "FFFFFFFFFFFFFFFFFF", err: ErrValueTooLarge, rest: "FFFFFFFFFFFFFFFFFF"}, + + { + input: "B838FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + err: ErrValueTooLarge, + rest: "B838FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + }, + { + input: "F838FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + err: ErrValueTooLarge, + rest: "F838FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + }, + + // a few bigger values, just for kicks + { + input: "F839FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + kind: List, + val: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + rest: "", + }, + { + input: "F90211A060EF29F20CC1007AE6E9530AEE16F4B31F8F1769A2D1264EC995C6D1241868D6A07C62AB8AC9838F5F5877B20BB37B387BC2106E97A3D52172CBEDB5EE17C36008A00EAB6B7324AADC0F6047C6AFC8229F09F7CF451B51D67C8DFB08D49BA8C3C626A04453343B2F3A6E42FCF87948F88AF7C8FC16D0C2735CBA7F026836239AB2C15FA024635C7291C882CE4C0763760C1A362DFC3FFCD802A55722236DE058D74202ACA0A220C808DE10F55E40AB25255201CFF009EA181D3906638E944EE2BF34049984A08D325AB26796F1CCB470F69C0F842501DC35D368A0C2575B2D243CFD1E8AB0FDA0B5298FF60DA5069463D610513C9F04F24051348391A143AFFAB7197DFACDEA72A02D2A7058A4463F8FB69378369E11EF33AE3252E2DB86CB545B36D3C26DDECE5AA0888F97BCA8E0BD83DC5B3B91CFF5FAF2F66F9501010682D67EF4A3B4E66115FBA0E8175A60C93BE9ED02921958F0EA55DA0FB5E4802AF5846147BAD92BC2D8AF26A08B3376FF433F3A4250FA64B7F804004CAC5807877D91C4427BD1CD05CF912ED8A09B32EF0F03BD13C37FF950C0CCCEFCCDD6669F2E7F2AA5CB859928E84E29763EA09BBA5E46610C8C8B1F8E921E5691BF8C7E40D75825D5EA3217AA9C3A8A355F39A0EEB95BC78251CCCEC54A97F19755C4A59A293544EEE6119AFA50531211E53C4FA00B6E86FE150BF4A9E0FEEE9C90F5465E617A861BB5E357F942881EE762212E2580", + kind: List, + val: "A060EF29F20CC1007AE6E9530AEE16F4B31F8F1769A2D1264EC995C6D1241868D6A07C62AB8AC9838F5F5877B20BB37B387BC2106E97A3D52172CBEDB5EE17C36008A00EAB6B7324AADC0F6047C6AFC8229F09F7CF451B51D67C8DFB08D49BA8C3C626A04453343B2F3A6E42FCF87948F88AF7C8FC16D0C2735CBA7F026836239AB2C15FA024635C7291C882CE4C0763760C1A362DFC3FFCD802A55722236DE058D74202ACA0A220C808DE10F55E40AB25255201CFF009EA181D3906638E944EE2BF34049984A08D325AB26796F1CCB470F69C0F842501DC35D368A0C2575B2D243CFD1E8AB0FDA0B5298FF60DA5069463D610513C9F04F24051348391A143AFFAB7197DFACDEA72A02D2A7058A4463F8FB69378369E11EF33AE3252E2DB86CB545B36D3C26DDECE5AA0888F97BCA8E0BD83DC5B3B91CFF5FAF2F66F9501010682D67EF4A3B4E66115FBA0E8175A60C93BE9ED02921958F0EA55DA0FB5E4802AF5846147BAD92BC2D8AF26A08B3376FF433F3A4250FA64B7F804004CAC5807877D91C4427BD1CD05CF912ED8A09B32EF0F03BD13C37FF950C0CCCEFCCDD6669F2E7F2AA5CB859928E84E29763EA09BBA5E46610C8C8B1F8E921E5691BF8C7E40D75825D5EA3217AA9C3A8A355F39A0EEB95BC78251CCCEC54A97F19755C4A59A293544EEE6119AFA50531211E53C4FA00B6E86FE150BF4A9E0FEEE9C90F5465E617A861BB5E357F942881EE762212E2580", + rest: "", + }, + { + input: "F877A12000BF49F440A1CD0527E4D06E2765654C0F56452257516D793A9B8D604DCFDF2AB853F851808D10000000000000000000000000A056E81F171BCC55A6FF8345E692C0F86E5B48E01B996CADC001622FB5E363B421A0C5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470", + kind: List, + val: "A12000BF49F440A1CD0527E4D06E2765654C0F56452257516D793A9B8D604DCFDF2AB853F851808D10000000000000000000000000A056E81F171BCC55A6FF8345E692C0F86E5B48E01B996CADC001622FB5E363B421A0C5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470", + rest: "", + }, + } + + for i, test := range tests { + kind, val, rest, err := Split(unhex(test.input)) + if kind != test.kind { + t.Errorf("test %d: kind mismatch: got %v, want %v", i, kind, test.kind) + } + if !bytes.Equal(val, unhex(test.val)) { + t.Errorf("test %d: val mismatch: got %x, want %s", i, val, test.val) + } + if !bytes.Equal(rest, unhex(test.rest)) { + t.Errorf("test %d: rest mismatch: got %x, want %s", i, rest, test.rest) + } + if err != test.err { + t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err) + } + } +} + +func TestReadSize(t *testing.T) { + tests := []struct { + input string + slen byte + size uint64 + err error + }{ + {input: "", slen: 1, err: io.ErrUnexpectedEOF}, + {input: "FF", slen: 2, err: io.ErrUnexpectedEOF}, + {input: "00", slen: 1, err: ErrCanonSize}, + {input: "36", slen: 1, err: ErrCanonSize}, + {input: "37", slen: 1, err: ErrCanonSize}, + {input: "38", slen: 1, size: 0x38}, + {input: "FF", slen: 1, size: 0xFF}, + {input: "FFFF", slen: 2, size: 0xFFFF}, + {input: "FFFFFF", slen: 3, size: 0xFFFFFF}, + {input: "FFFFFFFF", slen: 4, size: 0xFFFFFFFF}, + {input: "FFFFFFFFFF", slen: 5, size: 0xFFFFFFFFFF}, + {input: "FFFFFFFFFFFF", slen: 6, size: 0xFFFFFFFFFFFF}, + {input: "FFFFFFFFFFFFFF", slen: 7, size: 0xFFFFFFFFFFFFFF}, + {input: "FFFFFFFFFFFFFFFF", slen: 8, size: 0xFFFFFFFFFFFFFFFF}, + {input: "0102", slen: 2, size: 0x0102}, + {input: "010203", slen: 3, size: 0x010203}, + {input: "01020304", slen: 4, size: 0x01020304}, + {input: "0102030405", slen: 5, size: 0x0102030405}, + {input: "010203040506", slen: 6, size: 0x010203040506}, + {input: "01020304050607", slen: 7, size: 0x01020304050607}, + {input: "0102030405060708", slen: 8, size: 0x0102030405060708}, + } + + for _, test := range tests { + size, err := readSize(unhex(test.input), test.slen) + if err != test.err { + t.Errorf("readSize(%s, %d): error mismatch: got %q, want %q", test.input, test.slen, err, test.err) + continue + } + if size != test.size { + t.Errorf("readSize(%s, %d): size mismatch: got %#x, want %#x", test.input, test.slen, size, test.size) + } + } +} + +func TestAppendUint64(t *testing.T) { + tests := []struct { + input uint64 + slice []byte + output string + }{ + {0, nil, "80"}, + {1, nil, "01"}, + {2, nil, "02"}, + {127, nil, "7F"}, + {128, nil, "8180"}, + {129, nil, "8181"}, + {0xFFFFFF, nil, "83FFFFFF"}, + {127, []byte{1, 2, 3}, "0102037F"}, + {0xFFFFFF, []byte{1, 2, 3}, "01020383FFFFFF"}, + } + + for _, test := range tests { + x := AppendUint64(test.slice, test.input) + if !bytes.Equal(x, unhex(test.output)) { + t.Errorf("AppendUint64(%v, %d): got %x, want %s", test.slice, test.input, x, test.output) + } + + // Check that IntSize returns the appended size. + length := len(x) - len(test.slice) + if s := IntSize(test.input); s != length { + t.Errorf("IntSize(%d): got %d, want %d", test.input, s, length) + } + } +} + +func TestAppendUint64Random(t *testing.T) { + fn := func(i uint64) bool { + enc, _ := EncodeToBytes(i) + encAppend := AppendUint64(nil, i) + return bytes.Equal(enc, encAppend) + } + config := quick.Config{MaxCountScale: 50} + if err := quick.Check(fn, &config); err != nil { + t.Fatal(err) + } +} diff --git a/rlp/rlpgen/gen.go b/rlp/rlpgen/gen.go new file mode 100644 index 0000000000..15582b6ea9 --- /dev/null +++ b/rlp/rlpgen/gen.go @@ -0,0 +1,751 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package main + +import ( + "bytes" + "fmt" + "go/format" + "go/types" + "sort" + + "github.com/tenderly/coreth/rlp/internal/rlpstruct" +) + +// buildContext keeps the data needed for make*Op. +type buildContext struct { + topType *types.Named // the type we're creating methods for + + encoderIface *types.Interface + decoderIface *types.Interface + rawValueType *types.Named + + typeToStructCache map[types.Type]*rlpstruct.Type +} + +func newBuildContext(packageRLP *types.Package) *buildContext { + enc := packageRLP.Scope().Lookup("Encoder").Type().Underlying() + dec := packageRLP.Scope().Lookup("Decoder").Type().Underlying() + rawv := packageRLP.Scope().Lookup("RawValue").Type() + return &buildContext{ + typeToStructCache: make(map[types.Type]*rlpstruct.Type), + encoderIface: enc.(*types.Interface), + decoderIface: dec.(*types.Interface), + rawValueType: rawv.(*types.Named), + } +} + +func (bctx *buildContext) isEncoder(typ types.Type) bool { + return types.Implements(typ, bctx.encoderIface) +} + +func (bctx *buildContext) isDecoder(typ types.Type) bool { + return types.Implements(typ, bctx.decoderIface) +} + +// typeToStructType converts typ to rlpstruct.Type. +func (bctx *buildContext) typeToStructType(typ types.Type) *rlpstruct.Type { + if prev := bctx.typeToStructCache[typ]; prev != nil { + return prev // short-circuit for recursive types. + } + + // Resolve named types to their underlying type, but keep the name. + name := types.TypeString(typ, nil) + for { + utype := typ.Underlying() + if utype == typ { + break + } + typ = utype + } + + // Create the type and store it in cache. + t := &rlpstruct.Type{ + Name: name, + Kind: typeReflectKind(typ), + IsEncoder: bctx.isEncoder(typ), + IsDecoder: bctx.isDecoder(typ), + } + bctx.typeToStructCache[typ] = t + + // Assign element type. + switch typ.(type) { + case *types.Array, *types.Slice, *types.Pointer: + etype := typ.(interface{ Elem() types.Type }).Elem() + t.Elem = bctx.typeToStructType(etype) + } + return t +} + +// genContext is passed to the gen* methods of op when generating +// the output code. It tracks packages to be imported by the output +// file and assigns unique names of temporary variables. +type genContext struct { + inPackage *types.Package + imports map[string]struct{} + tempCounter int +} + +func newGenContext(inPackage *types.Package) *genContext { + return &genContext{ + inPackage: inPackage, + imports: make(map[string]struct{}), + } +} + +func (ctx *genContext) temp() string { + v := fmt.Sprintf("_tmp%d", ctx.tempCounter) + ctx.tempCounter++ + return v +} + +func (ctx *genContext) resetTemp() { + ctx.tempCounter = 0 +} + +func (ctx *genContext) addImport(path string) { + if path == ctx.inPackage.Path() { + return // avoid importing the package that we're generating in. + } + // TODO: renaming? + ctx.imports[path] = struct{}{} +} + +// importsList returns all packages that need to be imported. +func (ctx *genContext) importsList() []string { + imp := make([]string, 0, len(ctx.imports)) + for k := range ctx.imports { + imp = append(imp, k) + } + sort.Strings(imp) + return imp +} + +// qualify is the types.Qualifier used for printing types. +func (ctx *genContext) qualify(pkg *types.Package) string { + if pkg.Path() == ctx.inPackage.Path() { + return "" + } + ctx.addImport(pkg.Path()) + // TODO: renaming? + return pkg.Name() +} + +type op interface { + // genWrite creates the encoder. The generated code should write v, + // which is any Go expression, to the rlp.EncoderBuffer 'w'. + genWrite(ctx *genContext, v string) string + + // genDecode creates the decoder. The generated code should read + // a value from the rlp.Stream 'dec' and store it to dst. + genDecode(ctx *genContext) (string, string) +} + +// basicOp handles basic types bool, uint*, string. +type basicOp struct { + typ types.Type + writeMethod string // calle write the value + writeArgType types.Type // parameter type of writeMethod + decMethod string + decResultType types.Type // return type of decMethod + decUseBitSize bool // if true, result bit size is appended to decMethod +} + +func (*buildContext) makeBasicOp(typ *types.Basic) (op, error) { + op := basicOp{typ: typ} + kind := typ.Kind() + switch { + case kind == types.Bool: + op.writeMethod = "WriteBool" + op.writeArgType = types.Typ[types.Bool] + op.decMethod = "Bool" + op.decResultType = types.Typ[types.Bool] + case kind >= types.Uint8 && kind <= types.Uint64: + op.writeMethod = "WriteUint64" + op.writeArgType = types.Typ[types.Uint64] + op.decMethod = "Uint" + op.decResultType = typ + op.decUseBitSize = true + case kind == types.String: + op.writeMethod = "WriteString" + op.writeArgType = types.Typ[types.String] + op.decMethod = "String" + op.decResultType = types.Typ[types.String] + default: + return nil, fmt.Errorf("unhandled basic type: %v", typ) + } + return op, nil +} + +func (*buildContext) makeByteSliceOp(typ *types.Slice) op { + if !isByte(typ.Elem()) { + panic("non-byte slice type in makeByteSliceOp") + } + bslice := types.NewSlice(types.Typ[types.Uint8]) + return basicOp{ + typ: typ, + writeMethod: "WriteBytes", + writeArgType: bslice, + decMethod: "Bytes", + decResultType: bslice, + } +} + +func (bctx *buildContext) makeRawValueOp() op { + bslice := types.NewSlice(types.Typ[types.Uint8]) + return basicOp{ + typ: bctx.rawValueType, + writeMethod: "Write", + writeArgType: bslice, + decMethod: "Raw", + decResultType: bslice, + } +} + +func (op basicOp) writeNeedsConversion() bool { + return !types.AssignableTo(op.typ, op.writeArgType) +} + +func (op basicOp) decodeNeedsConversion() bool { + return !types.AssignableTo(op.decResultType, op.typ) +} + +func (op basicOp) genWrite(ctx *genContext, v string) string { + if op.writeNeedsConversion() { + v = fmt.Sprintf("%s(%s)", op.writeArgType, v) + } + return fmt.Sprintf("w.%s(%s)\n", op.writeMethod, v) +} + +func (op basicOp) genDecode(ctx *genContext) (string, string) { + var ( + resultV = ctx.temp() + result = resultV + method = op.decMethod + ) + if op.decUseBitSize { + // Note: For now, this only works for platform-independent integer + // sizes. makeBasicOp forbids the platform-dependent types. + var sizes types.StdSizes + method = fmt.Sprintf("%s%d", op.decMethod, sizes.Sizeof(op.typ)*8) + } + + // Call the decoder method. + var b bytes.Buffer + fmt.Fprintf(&b, "%s, err := dec.%s()\n", resultV, method) + fmt.Fprintf(&b, "if err != nil { return err }\n") + if op.decodeNeedsConversion() { + conv := ctx.temp() + fmt.Fprintf(&b, "%s := %s(%s)\n", conv, types.TypeString(op.typ, ctx.qualify), resultV) + result = conv + } + return result, b.String() +} + +// byteArrayOp handles [...]byte. +type byteArrayOp struct { + typ types.Type + name types.Type // name != typ for named byte array types (e.g. common.Address) +} + +func (bctx *buildContext) makeByteArrayOp(name *types.Named, typ *types.Array) byteArrayOp { + nt := types.Type(name) + if name == nil { + nt = typ + } + return byteArrayOp{typ, nt} +} + +func (op byteArrayOp) genWrite(ctx *genContext, v string) string { + return fmt.Sprintf("w.WriteBytes(%s[:])\n", v) +} + +func (op byteArrayOp) genDecode(ctx *genContext) (string, string) { + var resultV = ctx.temp() + + var b bytes.Buffer + fmt.Fprintf(&b, "var %s %s\n", resultV, types.TypeString(op.name, ctx.qualify)) + fmt.Fprintf(&b, "if err := dec.ReadBytes(%s[:]); err != nil { return err }\n", resultV) + return resultV, b.String() +} + +// bigIntNoPtrOp handles non-pointer big.Int. +// This exists because big.Int has it's own decoder operation on rlp.Stream, +// but the decode method returns *big.Int, so it needs to be dereferenced. +type bigIntOp struct { + pointer bool +} + +func (op bigIntOp) genWrite(ctx *genContext, v string) string { + var b bytes.Buffer + + fmt.Fprintf(&b, "if %s.Sign() == -1 {\n", v) + fmt.Fprintf(&b, " return rlp.ErrNegativeBigInt\n") + fmt.Fprintf(&b, "}\n") + dst := v + if !op.pointer { + dst = "&" + v + } + fmt.Fprintf(&b, "w.WriteBigInt(%s)\n", dst) + + // Wrap with nil check. + if op.pointer { + code := b.String() + b.Reset() + fmt.Fprintf(&b, "if %s == nil {\n", v) + fmt.Fprintf(&b, " w.Write(rlp.EmptyString)") + fmt.Fprintf(&b, "} else {\n") + fmt.Fprint(&b, code) + fmt.Fprintf(&b, "}\n") + } + + return b.String() +} + +func (op bigIntOp) genDecode(ctx *genContext) (string, string) { + var resultV = ctx.temp() + + var b bytes.Buffer + fmt.Fprintf(&b, "%s, err := dec.BigInt()\n", resultV) + fmt.Fprintf(&b, "if err != nil { return err }\n") + + result := resultV + if !op.pointer { + result = "(*" + resultV + ")" + } + return result, b.String() +} + +// encoderDecoderOp handles rlp.Encoder and rlp.Decoder. +// In order to be used with this, the type must implement both interfaces. +// This restriction may be lifted in the future by creating separate ops for +// encoding and decoding. +type encoderDecoderOp struct { + typ types.Type +} + +func (op encoderDecoderOp) genWrite(ctx *genContext, v string) string { + return fmt.Sprintf("if err := %s.EncodeRLP(w); err != nil { return err }\n", v) +} + +func (op encoderDecoderOp) genDecode(ctx *genContext) (string, string) { + // DecodeRLP must have pointer receiver, and this is verified in makeOp. + etyp := op.typ.(*types.Pointer).Elem() + var resultV = ctx.temp() + + var b bytes.Buffer + fmt.Fprintf(&b, "%s := new(%s)\n", resultV, types.TypeString(etyp, ctx.qualify)) + fmt.Fprintf(&b, "if err := %s.DecodeRLP(dec); err != nil { return err }\n", resultV) + return resultV, b.String() +} + +// ptrOp handles pointer types. +type ptrOp struct { + elemTyp types.Type + elem op + nilOK bool + nilValue rlpstruct.NilKind +} + +func (bctx *buildContext) makePtrOp(elemTyp types.Type, tags rlpstruct.Tags) (op, error) { + elemOp, err := bctx.makeOp(nil, elemTyp, rlpstruct.Tags{}) + if err != nil { + return nil, err + } + op := ptrOp{elemTyp: elemTyp, elem: elemOp} + + // Determine nil value. + if tags.NilOK { + op.nilOK = true + op.nilValue = tags.NilKind + } else { + styp := bctx.typeToStructType(elemTyp) + op.nilValue = styp.DefaultNilValue() + } + return op, nil +} + +func (op ptrOp) genWrite(ctx *genContext, v string) string { + // Note: in writer functions, accesses to v are read-only, i.e. v is any Go + // expression. To make all accesses work through the pointer, we substitute + // v with (*v). This is required for most accesses including `v`, `call(v)`, + // and `v[index]` on slices. + // + // For `v.field` and `v[:]` on arrays, the dereference operation is not required. + var vv string + _, isStruct := op.elem.(structOp) + _, isByteArray := op.elem.(byteArrayOp) + if isStruct || isByteArray { + vv = v + } else { + vv = fmt.Sprintf("(*%s)", v) + } + + var b bytes.Buffer + fmt.Fprintf(&b, "if %s == nil {\n", v) + fmt.Fprintf(&b, " w.Write([]byte{0x%X})\n", op.nilValue) + fmt.Fprintf(&b, "} else {\n") + fmt.Fprintf(&b, " %s", op.elem.genWrite(ctx, vv)) + fmt.Fprintf(&b, "}\n") + return b.String() +} + +func (op ptrOp) genDecode(ctx *genContext) (string, string) { + result, code := op.elem.genDecode(ctx) + if !op.nilOK { + // If nil pointers are not allowed, we can just decode the element. + return "&" + result, code + } + + // nil is allowed, so check the kind and size first. + // If size is zero and kind matches the nilKind of the type, + // the value decodes as a nil pointer. + var ( + resultV = ctx.temp() + kindV = ctx.temp() + sizeV = ctx.temp() + wantKind string + ) + if op.nilValue == rlpstruct.NilKindList { + wantKind = "rlp.List" + } else { + wantKind = "rlp.String" + } + var b bytes.Buffer + fmt.Fprintf(&b, "var %s %s\n", resultV, types.TypeString(types.NewPointer(op.elemTyp), ctx.qualify)) + fmt.Fprintf(&b, "if %s, %s, err := dec.Kind(); err != nil {\n", kindV, sizeV) + fmt.Fprintf(&b, " return err\n") + fmt.Fprintf(&b, "} else if %s != 0 || %s != %s {\n", sizeV, kindV, wantKind) + fmt.Fprint(&b, code) + fmt.Fprintf(&b, " %s = &%s\n", resultV, result) + fmt.Fprintf(&b, "}\n") + return resultV, b.String() +} + +// structOp handles struct types. +type structOp struct { + named *types.Named + typ *types.Struct + fields []*structField + optionalFields []*structField +} + +type structField struct { + name string + typ types.Type + elem op +} + +func (bctx *buildContext) makeStructOp(named *types.Named, typ *types.Struct) (op, error) { + // Convert fields to []rlpstruct.Field. + var allStructFields []rlpstruct.Field + for i := 0; i < typ.NumFields(); i++ { + f := typ.Field(i) + allStructFields = append(allStructFields, rlpstruct.Field{ + Name: f.Name(), + Exported: f.Exported(), + Index: i, + Tag: typ.Tag(i), + Type: *bctx.typeToStructType(f.Type()), + }) + } + + // Filter/validate fields. + fields, tags, err := rlpstruct.ProcessFields(allStructFields) + if err != nil { + return nil, err + } + + // Create field ops. + var op = structOp{named: named, typ: typ} + for i, field := range fields { + // Advanced struct tags are not supported yet. + tag := tags[i] + if err := checkUnsupportedTags(field.Name, tag); err != nil { + return nil, err + } + typ := typ.Field(field.Index).Type() + elem, err := bctx.makeOp(nil, typ, tags[i]) + if err != nil { + return nil, fmt.Errorf("field %s: %v", field.Name, err) + } + f := &structField{name: field.Name, typ: typ, elem: elem} + if tag.Optional { + op.optionalFields = append(op.optionalFields, f) + } else { + op.fields = append(op.fields, f) + } + } + return op, nil +} + +func checkUnsupportedTags(field string, tag rlpstruct.Tags) error { + if tag.Tail { + return fmt.Errorf(`field %s has unsupported struct tag "tail"`, field) + } + return nil +} + +func (op structOp) genWrite(ctx *genContext, v string) string { + var b bytes.Buffer + var listMarker = ctx.temp() + fmt.Fprintf(&b, "%s := w.List()\n", listMarker) + for _, field := range op.fields { + selector := v + "." + field.name + fmt.Fprint(&b, field.elem.genWrite(ctx, selector)) + } + op.writeOptionalFields(&b, ctx, v) + fmt.Fprintf(&b, "w.ListEnd(%s)\n", listMarker) + return b.String() +} + +func (op structOp) writeOptionalFields(b *bytes.Buffer, ctx *genContext, v string) { + if len(op.optionalFields) == 0 { + return + } + // First check zero-ness of all optional fields. + var zeroV = make([]string, len(op.optionalFields)) + for i, field := range op.optionalFields { + selector := v + "." + field.name + zeroV[i] = ctx.temp() + fmt.Fprintf(b, "%s := %s\n", zeroV[i], nonZeroCheck(selector, field.typ, ctx.qualify)) + } + // Now write the fields. + for i, field := range op.optionalFields { + selector := v + "." + field.name + cond := "" + for j := i; j < len(op.optionalFields); j++ { + if j > i { + cond += " || " + } + cond += zeroV[j] + } + fmt.Fprintf(b, "if %s {\n", cond) + fmt.Fprint(b, field.elem.genWrite(ctx, selector)) + fmt.Fprintf(b, "}\n") + } +} + +func (op structOp) genDecode(ctx *genContext) (string, string) { + // Get the string representation of the type. + // Here, named types are handled separately because the output + // would contain a copy of the struct definition otherwise. + var typeName string + if op.named != nil { + typeName = types.TypeString(op.named, ctx.qualify) + } else { + typeName = types.TypeString(op.typ, ctx.qualify) + } + + // Create struct object. + var resultV = ctx.temp() + var b bytes.Buffer + fmt.Fprintf(&b, "var %s %s\n", resultV, typeName) + + // Decode fields. + fmt.Fprintf(&b, "{\n") + fmt.Fprintf(&b, "if _, err := dec.List(); err != nil { return err }\n") + for _, field := range op.fields { + result, code := field.elem.genDecode(ctx) + fmt.Fprintf(&b, "// %s:\n", field.name) + fmt.Fprint(&b, code) + fmt.Fprintf(&b, "%s.%s = %s\n", resultV, field.name, result) + } + op.decodeOptionalFields(&b, ctx, resultV) + fmt.Fprintf(&b, "if err := dec.ListEnd(); err != nil { return err }\n") + fmt.Fprintf(&b, "}\n") + return resultV, b.String() +} + +func (op structOp) decodeOptionalFields(b *bytes.Buffer, ctx *genContext, resultV string) { + var suffix bytes.Buffer + for _, field := range op.optionalFields { + result, code := field.elem.genDecode(ctx) + fmt.Fprintf(b, "// %s:\n", field.name) + fmt.Fprintf(b, "if dec.MoreDataInList() {\n") + fmt.Fprint(b, code) + fmt.Fprintf(b, "%s.%s = %s\n", resultV, field.name, result) + fmt.Fprintf(&suffix, "}\n") + } + suffix.WriteTo(b) +} + +// sliceOp handles slice types. +type sliceOp struct { + typ *types.Slice + elemOp op +} + +func (bctx *buildContext) makeSliceOp(typ *types.Slice) (op, error) { + elemOp, err := bctx.makeOp(nil, typ.Elem(), rlpstruct.Tags{}) + if err != nil { + return nil, err + } + return sliceOp{typ: typ, elemOp: elemOp}, nil +} + +func (op sliceOp) genWrite(ctx *genContext, v string) string { + var ( + listMarker = ctx.temp() // holds return value of w.List() + iterElemV = ctx.temp() // iteration variable + elemCode = op.elemOp.genWrite(ctx, iterElemV) + ) + + var b bytes.Buffer + fmt.Fprintf(&b, "%s := w.List()\n", listMarker) + fmt.Fprintf(&b, "for _, %s := range %s {\n", iterElemV, v) + fmt.Fprint(&b, elemCode) + fmt.Fprintf(&b, "}\n") + fmt.Fprintf(&b, "w.ListEnd(%s)\n", listMarker) + return b.String() +} + +func (op sliceOp) genDecode(ctx *genContext) (string, string) { + var sliceV = ctx.temp() // holds the output slice + elemResult, elemCode := op.elemOp.genDecode(ctx) + + var b bytes.Buffer + fmt.Fprintf(&b, "var %s %s\n", sliceV, types.TypeString(op.typ, ctx.qualify)) + fmt.Fprintf(&b, "if _, err := dec.List(); err != nil { return err }\n") + fmt.Fprintf(&b, "for dec.MoreDataInList() {\n") + fmt.Fprintf(&b, " %s", elemCode) + fmt.Fprintf(&b, " %s = append(%s, %s)\n", sliceV, sliceV, elemResult) + fmt.Fprintf(&b, "}\n") + fmt.Fprintf(&b, "if err := dec.ListEnd(); err != nil { return err }\n") + return sliceV, b.String() +} + +func (bctx *buildContext) makeOp(name *types.Named, typ types.Type, tags rlpstruct.Tags) (op, error) { + switch typ := typ.(type) { + case *types.Named: + if isBigInt(typ) { + return bigIntOp{}, nil + } + if typ == bctx.rawValueType { + return bctx.makeRawValueOp(), nil + } + if bctx.isDecoder(typ) { + return nil, fmt.Errorf("type %v implements rlp.Decoder with non-pointer receiver", typ) + } + // TODO: same check for encoder? + return bctx.makeOp(typ, typ.Underlying(), tags) + case *types.Pointer: + if isBigInt(typ.Elem()) { + return bigIntOp{pointer: true}, nil + } + // Encoder/Decoder interfaces. + if bctx.isEncoder(typ) { + if bctx.isDecoder(typ) { + return encoderDecoderOp{typ}, nil + } + return nil, fmt.Errorf("type %v implements rlp.Encoder but not rlp.Decoder", typ) + } + if bctx.isDecoder(typ) { + return nil, fmt.Errorf("type %v implements rlp.Decoder but not rlp.Encoder", typ) + } + // Default pointer handling. + return bctx.makePtrOp(typ.Elem(), tags) + case *types.Basic: + return bctx.makeBasicOp(typ) + case *types.Struct: + return bctx.makeStructOp(name, typ) + case *types.Slice: + etyp := typ.Elem() + if isByte(etyp) && !bctx.isEncoder(etyp) { + return bctx.makeByteSliceOp(typ), nil + } + return bctx.makeSliceOp(typ) + case *types.Array: + etyp := typ.Elem() + if isByte(etyp) && !bctx.isEncoder(etyp) { + return bctx.makeByteArrayOp(name, typ), nil + } + return nil, fmt.Errorf("unhandled array type: %v", typ) + default: + return nil, fmt.Errorf("unhandled type: %v", typ) + } +} + +// generateDecoder generates the DecodeRLP method on 'typ'. +func generateDecoder(ctx *genContext, typ string, op op) []byte { + ctx.resetTemp() + ctx.addImport(pathOfPackageRLP) + + result, code := op.genDecode(ctx) + var b bytes.Buffer + fmt.Fprintf(&b, "func (obj *%s) DecodeRLP(dec *rlp.Stream) error {\n", typ) + fmt.Fprint(&b, code) + fmt.Fprintf(&b, " *obj = %s\n", result) + fmt.Fprintf(&b, " return nil\n") + fmt.Fprintf(&b, "}\n") + return b.Bytes() +} + +// generateEncoder generates the EncodeRLP method on 'typ'. +func generateEncoder(ctx *genContext, typ string, op op) []byte { + ctx.resetTemp() + ctx.addImport("io") + ctx.addImport(pathOfPackageRLP) + + var b bytes.Buffer + fmt.Fprintf(&b, "func (obj *%s) EncodeRLP(_w io.Writer) error {\n", typ) + fmt.Fprintf(&b, " w := rlp.NewEncoderBuffer(_w)\n") + fmt.Fprint(&b, op.genWrite(ctx, "obj")) + fmt.Fprintf(&b, " return w.Flush()\n") + fmt.Fprintf(&b, "}\n") + return b.Bytes() +} + +func (bctx *buildContext) generate(typ *types.Named, encoder, decoder bool) ([]byte, error) { + bctx.topType = typ + + pkg := typ.Obj().Pkg() + op, err := bctx.makeOp(nil, typ, rlpstruct.Tags{}) + if err != nil { + return nil, err + } + + var ( + ctx = newGenContext(pkg) + encSource []byte + decSource []byte + ) + if encoder { + encSource = generateEncoder(ctx, typ.Obj().Name(), op) + } + if decoder { + decSource = generateDecoder(ctx, typ.Obj().Name(), op) + } + + var b bytes.Buffer + fmt.Fprintf(&b, "package %s\n\n", pkg.Name()) + for _, imp := range ctx.importsList() { + fmt.Fprintf(&b, "import %q\n", imp) + } + if encoder { + fmt.Fprintln(&b) + b.Write(encSource) + } + if decoder { + fmt.Fprintln(&b) + b.Write(decSource) + } + + source := b.Bytes() + // fmt.Println(string(source)) + return format.Source(source) +} diff --git a/rlp/rlpgen/gen_test.go b/rlp/rlpgen/gen_test.go new file mode 100644 index 0000000000..241c34b6df --- /dev/null +++ b/rlp/rlpgen/gen_test.go @@ -0,0 +1,107 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "os" + "path/filepath" + "testing" +) + +// Package RLP is loaded only once and reused for all tests. +var ( + testFset = token.NewFileSet() + testImporter = importer.ForCompiler(testFset, "source", nil).(types.ImporterFrom) + testPackageRLP *types.Package +) + +func init() { + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + testPackageRLP, err = testImporter.ImportFrom(pathOfPackageRLP, cwd, 0) + if err != nil { + panic(fmt.Errorf("can't load package RLP: %v", err)) + } +} + +var tests = []string{"uints", "nil", "rawvalue", "optional", "bigint"} + +func TestOutput(t *testing.T) { + for _, test := range tests { + test := test + t.Run(test, func(t *testing.T) { + inputFile := filepath.Join("testdata", test+".in.txt") + outputFile := filepath.Join("testdata", test+".out.txt") + bctx, typ, err := loadTestSource(inputFile, "Test") + if err != nil { + t.Fatal("error loading test source:", err) + } + output, err := bctx.generate(typ, true, true) + if err != nil { + t.Fatal("error in generate:", err) + } + + // Set this environment variable to regenerate the test outputs. + if os.Getenv("WRITE_TEST_FILES") != "" { + os.WriteFile(outputFile, output, 0644) + } + + // Check if output matches. + wantOutput, err := os.ReadFile(outputFile) + if err != nil { + t.Fatal("error loading expected test output:", err) + } + if !bytes.Equal(output, wantOutput) { + t.Fatal("output mismatch:\n", string(output)) + } + }) + } +} + +func loadTestSource(file string, typeName string) (*buildContext, *types.Named, error) { + // Load the test input. + content, err := os.ReadFile(file) + if err != nil { + return nil, nil, err + } + f, err := parser.ParseFile(testFset, file, content, 0) + if err != nil { + return nil, nil, err + } + conf := types.Config{Importer: testImporter} + pkg, err := conf.Check("test", testFset, []*ast.File{f}, nil) + if err != nil { + return nil, nil, err + } + + // Find the test struct. + bctx := newBuildContext(testPackageRLP) + typ, err := lookupStructType(pkg.Scope(), typeName) + if err != nil { + return nil, nil, fmt.Errorf("can't find type %s: %v", typeName, err) + } + return bctx, typ, nil +} diff --git a/rlp/rlpgen/main.go b/rlp/rlpgen/main.go new file mode 100644 index 0000000000..fcef813285 --- /dev/null +++ b/rlp/rlpgen/main.go @@ -0,0 +1,147 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package main + +import ( + "bytes" + "errors" + "flag" + "fmt" + "go/types" + "os" + + "golang.org/x/tools/go/packages" +) + +const pathOfPackageRLP = "github.com/tenderly/coreth/rlp" + +func main() { + var ( + pkgdir = flag.String("dir", ".", "input package") + output = flag.String("out", "-", "output file (default is stdout)") + genEncoder = flag.Bool("encoder", true, "generate EncodeRLP?") + genDecoder = flag.Bool("decoder", false, "generate DecodeRLP?") + typename = flag.String("type", "", "type to generate methods for") + ) + flag.Parse() + + cfg := Config{ + Dir: *pkgdir, + Type: *typename, + GenerateEncoder: *genEncoder, + GenerateDecoder: *genDecoder, + } + code, err := cfg.process() + if err != nil { + fatal(err) + } + if *output == "-" { + os.Stdout.Write(code) + } else if err := os.WriteFile(*output, code, 0600); err != nil { + fatal(err) + } +} + +func fatal(args ...interface{}) { + fmt.Fprintln(os.Stderr, args...) + os.Exit(1) +} + +type Config struct { + Dir string // input package directory + Type string + + GenerateEncoder bool + GenerateDecoder bool +} + +// process generates the Go code. +func (cfg *Config) process() (code []byte, err error) { + // Load packages. + pcfg := &packages.Config{ + Mode: packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps, + Dir: cfg.Dir, + BuildFlags: []string{"-tags", "norlpgen"}, + } + ps, err := packages.Load(pcfg, pathOfPackageRLP, ".") + if err != nil { + return nil, err + } + if len(ps) == 0 { + return nil, fmt.Errorf("no Go package found in %s", cfg.Dir) + } + packages.PrintErrors(ps) + + // Find the packages that were loaded. + var ( + pkg *types.Package + packageRLP *types.Package + ) + for _, p := range ps { + if len(p.Errors) > 0 { + return nil, fmt.Errorf("package %s has errors", p.PkgPath) + } + if p.PkgPath == pathOfPackageRLP { + packageRLP = p.Types + } else { + pkg = p.Types + } + } + bctx := newBuildContext(packageRLP) + + // Find the type and generate. + typ, err := lookupStructType(pkg.Scope(), cfg.Type) + if err != nil { + return nil, fmt.Errorf("can't find %s in %s: %v", cfg.Type, pkg, err) + } + code, err = bctx.generate(typ, cfg.GenerateEncoder, cfg.GenerateDecoder) + if err != nil { + return nil, err + } + + // Add build comments. + // This is done here to avoid processing these lines with gofmt. + var header bytes.Buffer + fmt.Fprint(&header, "// Code generated by rlpgen. DO NOT EDIT.\n\n") + fmt.Fprint(&header, "//go:build !norlpgen\n") + fmt.Fprint(&header, "// +build !norlpgen\n\n") + return append(header.Bytes(), code...), nil +} + +func lookupStructType(scope *types.Scope, name string) (*types.Named, error) { + typ, err := lookupType(scope, name) + if err != nil { + return nil, err + } + _, ok := typ.Underlying().(*types.Struct) + if !ok { + return nil, errors.New("not a struct type") + } + return typ, nil +} + +func lookupType(scope *types.Scope, name string) (*types.Named, error) { + obj := scope.Lookup(name) + if obj == nil { + return nil, errors.New("no such identifier") + } + typ, ok := obj.(*types.TypeName) + if !ok { + return nil, errors.New("not a type") + } + return typ.Type().(*types.Named), nil +} diff --git a/rlp/rlpgen/testdata/bigint.in.txt b/rlp/rlpgen/testdata/bigint.in.txt new file mode 100644 index 0000000000..d23d84a287 --- /dev/null +++ b/rlp/rlpgen/testdata/bigint.in.txt @@ -0,0 +1,10 @@ +// -*- mode: go -*- + +package test + +import "math/big" + +type Test struct { + Int *big.Int + IntNoPtr big.Int +} diff --git a/rlp/rlpgen/testdata/bigint.out.txt b/rlp/rlpgen/testdata/bigint.out.txt new file mode 100644 index 0000000000..70f19680f7 --- /dev/null +++ b/rlp/rlpgen/testdata/bigint.out.txt @@ -0,0 +1,49 @@ +package test + +import "github.com/tenderly/coreth/rlp" +import "io" + +func (obj *Test) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + if obj.Int == nil { + w.Write(rlp.EmptyString) + } else { + if obj.Int.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.Int) + } + if obj.IntNoPtr.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(&obj.IntNoPtr) + w.ListEnd(_tmp0) + return w.Flush() +} + +func (obj *Test) DecodeRLP(dec *rlp.Stream) error { + var _tmp0 Test + { + if _, err := dec.List(); err != nil { + return err + } + // Int: + _tmp1, err := dec.BigInt() + if err != nil { + return err + } + _tmp0.Int = _tmp1 + // IntNoPtr: + _tmp2, err := dec.BigInt() + if err != nil { + return err + } + _tmp0.IntNoPtr = (*_tmp2) + if err := dec.ListEnd(); err != nil { + return err + } + } + *obj = _tmp0 + return nil +} diff --git a/rlp/rlpgen/testdata/nil.in.txt b/rlp/rlpgen/testdata/nil.in.txt new file mode 100644 index 0000000000..a28ff34487 --- /dev/null +++ b/rlp/rlpgen/testdata/nil.in.txt @@ -0,0 +1,30 @@ +// -*- mode: go -*- + +package test + +type Aux struct{ + A uint32 +} + +type Test struct{ + Uint8 *byte `rlp:"nil"` + Uint8List *byte `rlp:"nilList"` + + Uint32 *uint32 `rlp:"nil"` + Uint32List *uint32 `rlp:"nilList"` + + Uint64 *uint64 `rlp:"nil"` + Uint64List *uint64 `rlp:"nilList"` + + String *string `rlp:"nil"` + StringList *string `rlp:"nilList"` + + ByteArray *[3]byte `rlp:"nil"` + ByteArrayList *[3]byte `rlp:"nilList"` + + ByteSlice *[]byte `rlp:"nil"` + ByteSliceList *[]byte `rlp:"nilList"` + + Struct *Aux `rlp:"nil"` + StructString *Aux `rlp:"nilString"` +} diff --git a/rlp/rlpgen/testdata/nil.out.txt b/rlp/rlpgen/testdata/nil.out.txt new file mode 100644 index 0000000000..8f176e7e99 --- /dev/null +++ b/rlp/rlpgen/testdata/nil.out.txt @@ -0,0 +1,289 @@ +package test + +import "github.com/tenderly/coreth/rlp" +import "io" + +func (obj *Test) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + if obj.Uint8 == nil { + w.Write([]byte{0x80}) + } else { + w.WriteUint64(uint64((*obj.Uint8))) + } + if obj.Uint8List == nil { + w.Write([]byte{0xC0}) + } else { + w.WriteUint64(uint64((*obj.Uint8List))) + } + if obj.Uint32 == nil { + w.Write([]byte{0x80}) + } else { + w.WriteUint64(uint64((*obj.Uint32))) + } + if obj.Uint32List == nil { + w.Write([]byte{0xC0}) + } else { + w.WriteUint64(uint64((*obj.Uint32List))) + } + if obj.Uint64 == nil { + w.Write([]byte{0x80}) + } else { + w.WriteUint64((*obj.Uint64)) + } + if obj.Uint64List == nil { + w.Write([]byte{0xC0}) + } else { + w.WriteUint64((*obj.Uint64List)) + } + if obj.String == nil { + w.Write([]byte{0x80}) + } else { + w.WriteString((*obj.String)) + } + if obj.StringList == nil { + w.Write([]byte{0xC0}) + } else { + w.WriteString((*obj.StringList)) + } + if obj.ByteArray == nil { + w.Write([]byte{0x80}) + } else { + w.WriteBytes(obj.ByteArray[:]) + } + if obj.ByteArrayList == nil { + w.Write([]byte{0xC0}) + } else { + w.WriteBytes(obj.ByteArrayList[:]) + } + if obj.ByteSlice == nil { + w.Write([]byte{0x80}) + } else { + w.WriteBytes((*obj.ByteSlice)) + } + if obj.ByteSliceList == nil { + w.Write([]byte{0xC0}) + } else { + w.WriteBytes((*obj.ByteSliceList)) + } + if obj.Struct == nil { + w.Write([]byte{0xC0}) + } else { + _tmp1 := w.List() + w.WriteUint64(uint64(obj.Struct.A)) + w.ListEnd(_tmp1) + } + if obj.StructString == nil { + w.Write([]byte{0x80}) + } else { + _tmp2 := w.List() + w.WriteUint64(uint64(obj.StructString.A)) + w.ListEnd(_tmp2) + } + w.ListEnd(_tmp0) + return w.Flush() +} + +func (obj *Test) DecodeRLP(dec *rlp.Stream) error { + var _tmp0 Test + { + if _, err := dec.List(); err != nil { + return err + } + // Uint8: + var _tmp2 *byte + if _tmp3, _tmp4, err := dec.Kind(); err != nil { + return err + } else if _tmp4 != 0 || _tmp3 != rlp.String { + _tmp1, err := dec.Uint8() + if err != nil { + return err + } + _tmp2 = &_tmp1 + } + _tmp0.Uint8 = _tmp2 + // Uint8List: + var _tmp6 *byte + if _tmp7, _tmp8, err := dec.Kind(); err != nil { + return err + } else if _tmp8 != 0 || _tmp7 != rlp.List { + _tmp5, err := dec.Uint8() + if err != nil { + return err + } + _tmp6 = &_tmp5 + } + _tmp0.Uint8List = _tmp6 + // Uint32: + var _tmp10 *uint32 + if _tmp11, _tmp12, err := dec.Kind(); err != nil { + return err + } else if _tmp12 != 0 || _tmp11 != rlp.String { + _tmp9, err := dec.Uint32() + if err != nil { + return err + } + _tmp10 = &_tmp9 + } + _tmp0.Uint32 = _tmp10 + // Uint32List: + var _tmp14 *uint32 + if _tmp15, _tmp16, err := dec.Kind(); err != nil { + return err + } else if _tmp16 != 0 || _tmp15 != rlp.List { + _tmp13, err := dec.Uint32() + if err != nil { + return err + } + _tmp14 = &_tmp13 + } + _tmp0.Uint32List = _tmp14 + // Uint64: + var _tmp18 *uint64 + if _tmp19, _tmp20, err := dec.Kind(); err != nil { + return err + } else if _tmp20 != 0 || _tmp19 != rlp.String { + _tmp17, err := dec.Uint64() + if err != nil { + return err + } + _tmp18 = &_tmp17 + } + _tmp0.Uint64 = _tmp18 + // Uint64List: + var _tmp22 *uint64 + if _tmp23, _tmp24, err := dec.Kind(); err != nil { + return err + } else if _tmp24 != 0 || _tmp23 != rlp.List { + _tmp21, err := dec.Uint64() + if err != nil { + return err + } + _tmp22 = &_tmp21 + } + _tmp0.Uint64List = _tmp22 + // String: + var _tmp26 *string + if _tmp27, _tmp28, err := dec.Kind(); err != nil { + return err + } else if _tmp28 != 0 || _tmp27 != rlp.String { + _tmp25, err := dec.String() + if err != nil { + return err + } + _tmp26 = &_tmp25 + } + _tmp0.String = _tmp26 + // StringList: + var _tmp30 *string + if _tmp31, _tmp32, err := dec.Kind(); err != nil { + return err + } else if _tmp32 != 0 || _tmp31 != rlp.List { + _tmp29, err := dec.String() + if err != nil { + return err + } + _tmp30 = &_tmp29 + } + _tmp0.StringList = _tmp30 + // ByteArray: + var _tmp34 *[3]byte + if _tmp35, _tmp36, err := dec.Kind(); err != nil { + return err + } else if _tmp36 != 0 || _tmp35 != rlp.String { + var _tmp33 [3]byte + if err := dec.ReadBytes(_tmp33[:]); err != nil { + return err + } + _tmp34 = &_tmp33 + } + _tmp0.ByteArray = _tmp34 + // ByteArrayList: + var _tmp38 *[3]byte + if _tmp39, _tmp40, err := dec.Kind(); err != nil { + return err + } else if _tmp40 != 0 || _tmp39 != rlp.List { + var _tmp37 [3]byte + if err := dec.ReadBytes(_tmp37[:]); err != nil { + return err + } + _tmp38 = &_tmp37 + } + _tmp0.ByteArrayList = _tmp38 + // ByteSlice: + var _tmp42 *[]byte + if _tmp43, _tmp44, err := dec.Kind(); err != nil { + return err + } else if _tmp44 != 0 || _tmp43 != rlp.String { + _tmp41, err := dec.Bytes() + if err != nil { + return err + } + _tmp42 = &_tmp41 + } + _tmp0.ByteSlice = _tmp42 + // ByteSliceList: + var _tmp46 *[]byte + if _tmp47, _tmp48, err := dec.Kind(); err != nil { + return err + } else if _tmp48 != 0 || _tmp47 != rlp.List { + _tmp45, err := dec.Bytes() + if err != nil { + return err + } + _tmp46 = &_tmp45 + } + _tmp0.ByteSliceList = _tmp46 + // Struct: + var _tmp51 *Aux + if _tmp52, _tmp53, err := dec.Kind(); err != nil { + return err + } else if _tmp53 != 0 || _tmp52 != rlp.List { + var _tmp49 Aux + { + if _, err := dec.List(); err != nil { + return err + } + // A: + _tmp50, err := dec.Uint32() + if err != nil { + return err + } + _tmp49.A = _tmp50 + if err := dec.ListEnd(); err != nil { + return err + } + } + _tmp51 = &_tmp49 + } + _tmp0.Struct = _tmp51 + // StructString: + var _tmp56 *Aux + if _tmp57, _tmp58, err := dec.Kind(); err != nil { + return err + } else if _tmp58 != 0 || _tmp57 != rlp.String { + var _tmp54 Aux + { + if _, err := dec.List(); err != nil { + return err + } + // A: + _tmp55, err := dec.Uint32() + if err != nil { + return err + } + _tmp54.A = _tmp55 + if err := dec.ListEnd(); err != nil { + return err + } + } + _tmp56 = &_tmp54 + } + _tmp0.StructString = _tmp56 + if err := dec.ListEnd(); err != nil { + return err + } + } + *obj = _tmp0 + return nil +} diff --git a/rlp/rlpgen/testdata/optional.in.txt b/rlp/rlpgen/testdata/optional.in.txt new file mode 100644 index 0000000000..f1ac9f7899 --- /dev/null +++ b/rlp/rlpgen/testdata/optional.in.txt @@ -0,0 +1,17 @@ +// -*- mode: go -*- + +package test + +type Aux struct { + A uint64 +} + +type Test struct { + Uint64 uint64 `rlp:"optional"` + Pointer *uint64 `rlp:"optional"` + String string `rlp:"optional"` + Slice []uint64 `rlp:"optional"` + Array [3]byte `rlp:"optional"` + NamedStruct Aux `rlp:"optional"` + AnonStruct struct{ A string } `rlp:"optional"` +} diff --git a/rlp/rlpgen/testdata/optional.out.txt b/rlp/rlpgen/testdata/optional.out.txt new file mode 100644 index 0000000000..7dc5612547 --- /dev/null +++ b/rlp/rlpgen/testdata/optional.out.txt @@ -0,0 +1,153 @@ +package test + +import "github.com/tenderly/coreth/rlp" +import "io" + +func (obj *Test) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + _tmp1 := obj.Uint64 != 0 + _tmp2 := obj.Pointer != nil + _tmp3 := obj.String != "" + _tmp4 := len(obj.Slice) > 0 + _tmp5 := obj.Array != ([3]byte{}) + _tmp6 := obj.NamedStruct != (Aux{}) + _tmp7 := obj.AnonStruct != (struct{ A string }{}) + if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 { + w.WriteUint64(obj.Uint64) + } + if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 { + if obj.Pointer == nil { + w.Write([]byte{0x80}) + } else { + w.WriteUint64((*obj.Pointer)) + } + } + if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 { + w.WriteString(obj.String) + } + if _tmp4 || _tmp5 || _tmp6 || _tmp7 { + _tmp8 := w.List() + for _, _tmp9 := range obj.Slice { + w.WriteUint64(_tmp9) + } + w.ListEnd(_tmp8) + } + if _tmp5 || _tmp6 || _tmp7 { + w.WriteBytes(obj.Array[:]) + } + if _tmp6 || _tmp7 { + _tmp10 := w.List() + w.WriteUint64(obj.NamedStruct.A) + w.ListEnd(_tmp10) + } + if _tmp7 { + _tmp11 := w.List() + w.WriteString(obj.AnonStruct.A) + w.ListEnd(_tmp11) + } + w.ListEnd(_tmp0) + return w.Flush() +} + +func (obj *Test) DecodeRLP(dec *rlp.Stream) error { + var _tmp0 Test + { + if _, err := dec.List(); err != nil { + return err + } + // Uint64: + if dec.MoreDataInList() { + _tmp1, err := dec.Uint64() + if err != nil { + return err + } + _tmp0.Uint64 = _tmp1 + // Pointer: + if dec.MoreDataInList() { + _tmp2, err := dec.Uint64() + if err != nil { + return err + } + _tmp0.Pointer = &_tmp2 + // String: + if dec.MoreDataInList() { + _tmp3, err := dec.String() + if err != nil { + return err + } + _tmp0.String = _tmp3 + // Slice: + if dec.MoreDataInList() { + var _tmp4 []uint64 + if _, err := dec.List(); err != nil { + return err + } + for dec.MoreDataInList() { + _tmp5, err := dec.Uint64() + if err != nil { + return err + } + _tmp4 = append(_tmp4, _tmp5) + } + if err := dec.ListEnd(); err != nil { + return err + } + _tmp0.Slice = _tmp4 + // Array: + if dec.MoreDataInList() { + var _tmp6 [3]byte + if err := dec.ReadBytes(_tmp6[:]); err != nil { + return err + } + _tmp0.Array = _tmp6 + // NamedStruct: + if dec.MoreDataInList() { + var _tmp7 Aux + { + if _, err := dec.List(); err != nil { + return err + } + // A: + _tmp8, err := dec.Uint64() + if err != nil { + return err + } + _tmp7.A = _tmp8 + if err := dec.ListEnd(); err != nil { + return err + } + } + _tmp0.NamedStruct = _tmp7 + // AnonStruct: + if dec.MoreDataInList() { + var _tmp9 struct{ A string } + { + if _, err := dec.List(); err != nil { + return err + } + // A: + _tmp10, err := dec.String() + if err != nil { + return err + } + _tmp9.A = _tmp10 + if err := dec.ListEnd(); err != nil { + return err + } + } + _tmp0.AnonStruct = _tmp9 + } + } + } + } + } + } + } + if err := dec.ListEnd(); err != nil { + return err + } + } + *obj = _tmp0 + return nil +} diff --git a/rlp/rlpgen/testdata/rawvalue.in.txt b/rlp/rlpgen/testdata/rawvalue.in.txt new file mode 100644 index 0000000000..1540e071bf --- /dev/null +++ b/rlp/rlpgen/testdata/rawvalue.in.txt @@ -0,0 +1,11 @@ +// -*- mode: go -*- + +package test + +import "github.com/tenderly/coreth/rlp" + +type Test struct { + RawValue rlp.RawValue + PointerToRawValue *rlp.RawValue + SliceOfRawValue []rlp.RawValue +} diff --git a/rlp/rlpgen/testdata/rawvalue.out.txt b/rlp/rlpgen/testdata/rawvalue.out.txt new file mode 100644 index 0000000000..388c7db8fd --- /dev/null +++ b/rlp/rlpgen/testdata/rawvalue.out.txt @@ -0,0 +1,64 @@ +package test + +import "github.com/tenderly/coreth/rlp" +import "io" + +func (obj *Test) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.Write(obj.RawValue) + if obj.PointerToRawValue == nil { + w.Write([]byte{0x80}) + } else { + w.Write((*obj.PointerToRawValue)) + } + _tmp1 := w.List() + for _, _tmp2 := range obj.SliceOfRawValue { + w.Write(_tmp2) + } + w.ListEnd(_tmp1) + w.ListEnd(_tmp0) + return w.Flush() +} + +func (obj *Test) DecodeRLP(dec *rlp.Stream) error { + var _tmp0 Test + { + if _, err := dec.List(); err != nil { + return err + } + // RawValue: + _tmp1, err := dec.Raw() + if err != nil { + return err + } + _tmp0.RawValue = _tmp1 + // PointerToRawValue: + _tmp2, err := dec.Raw() + if err != nil { + return err + } + _tmp0.PointerToRawValue = &_tmp2 + // SliceOfRawValue: + var _tmp3 []rlp.RawValue + if _, err := dec.List(); err != nil { + return err + } + for dec.MoreDataInList() { + _tmp4, err := dec.Raw() + if err != nil { + return err + } + _tmp3 = append(_tmp3, _tmp4) + } + if err := dec.ListEnd(); err != nil { + return err + } + _tmp0.SliceOfRawValue = _tmp3 + if err := dec.ListEnd(); err != nil { + return err + } + } + *obj = _tmp0 + return nil +} diff --git a/rlp/rlpgen/testdata/uints.in.txt b/rlp/rlpgen/testdata/uints.in.txt new file mode 100644 index 0000000000..8095da997d --- /dev/null +++ b/rlp/rlpgen/testdata/uints.in.txt @@ -0,0 +1,10 @@ +// -*- mode: go -*- + +package test + +type Test struct{ + A uint8 + B uint16 + C uint32 + D uint64 +} diff --git a/rlp/rlpgen/testdata/uints.out.txt b/rlp/rlpgen/testdata/uints.out.txt new file mode 100644 index 0000000000..1d6eb768ff --- /dev/null +++ b/rlp/rlpgen/testdata/uints.out.txt @@ -0,0 +1,53 @@ +package test + +import "github.com/tenderly/coreth/rlp" +import "io" + +func (obj *Test) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.WriteUint64(uint64(obj.A)) + w.WriteUint64(uint64(obj.B)) + w.WriteUint64(uint64(obj.C)) + w.WriteUint64(obj.D) + w.ListEnd(_tmp0) + return w.Flush() +} + +func (obj *Test) DecodeRLP(dec *rlp.Stream) error { + var _tmp0 Test + { + if _, err := dec.List(); err != nil { + return err + } + // A: + _tmp1, err := dec.Uint8() + if err != nil { + return err + } + _tmp0.A = _tmp1 + // B: + _tmp2, err := dec.Uint16() + if err != nil { + return err + } + _tmp0.B = _tmp2 + // C: + _tmp3, err := dec.Uint32() + if err != nil { + return err + } + _tmp0.C = _tmp3 + // D: + _tmp4, err := dec.Uint64() + if err != nil { + return err + } + _tmp0.D = _tmp4 + if err := dec.ListEnd(); err != nil { + return err + } + } + *obj = _tmp0 + return nil +} diff --git a/rlp/rlpgen/types.go b/rlp/rlpgen/types.go new file mode 100644 index 0000000000..19694262e5 --- /dev/null +++ b/rlp/rlpgen/types.go @@ -0,0 +1,114 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package main + +import ( + "fmt" + "go/types" + "reflect" +) + +// typeReflectKind gives the reflect.Kind that represents typ. +func typeReflectKind(typ types.Type) reflect.Kind { + switch typ := typ.(type) { + case *types.Basic: + k := typ.Kind() + if k >= types.Bool && k <= types.Complex128 { + // value order matches for Bool..Complex128 + return reflect.Bool + reflect.Kind(k-types.Bool) + } + if k == types.String { + return reflect.String + } + if k == types.UnsafePointer { + return reflect.UnsafePointer + } + panic(fmt.Errorf("unhandled BasicKind %v", k)) + case *types.Array: + return reflect.Array + case *types.Chan: + return reflect.Chan + case *types.Interface: + return reflect.Interface + case *types.Map: + return reflect.Map + case *types.Pointer: + return reflect.Ptr + case *types.Signature: + return reflect.Func + case *types.Slice: + return reflect.Slice + case *types.Struct: + return reflect.Struct + default: + panic(fmt.Errorf("unhandled type %T", typ)) + } +} + +// nonZeroCheck returns the expression that checks whether 'v' is a non-zero value of type 'vtyp'. +func nonZeroCheck(v string, vtyp types.Type, qualify types.Qualifier) string { + // Resolve type name. + typ := resolveUnderlying(vtyp) + switch typ := typ.(type) { + case *types.Basic: + k := typ.Kind() + switch { + case k == types.Bool: + return v + case k >= types.Uint && k <= types.Complex128: + return fmt.Sprintf("%s != 0", v) + case k == types.String: + return fmt.Sprintf(`%s != ""`, v) + default: + panic(fmt.Errorf("unhandled BasicKind %v", k)) + } + case *types.Array, *types.Struct: + return fmt.Sprintf("%s != (%s{})", v, types.TypeString(vtyp, qualify)) + case *types.Interface, *types.Pointer, *types.Signature: + return fmt.Sprintf("%s != nil", v) + case *types.Slice, *types.Map: + return fmt.Sprintf("len(%s) > 0", v) + default: + panic(fmt.Errorf("unhandled type %T", typ)) + } +} + +// isBigInt checks whether 'typ' is "math/big".Int. +func isBigInt(typ types.Type) bool { + named, ok := typ.(*types.Named) + if !ok { + return false + } + name := named.Obj() + return name.Pkg().Path() == "math/big" && name.Name() == "Int" +} + +// isByte checks whether the underlying type of 'typ' is uint8. +func isByte(typ types.Type) bool { + basic, ok := resolveUnderlying(typ).(*types.Basic) + return ok && basic.Kind() == types.Uint8 +} + +func resolveUnderlying(typ types.Type) types.Type { + for { + t := typ.Underlying() + if t == typ { + return t + } + typ = t + } +} diff --git a/rlp/safe.go b/rlp/safe.go new file mode 100644 index 0000000000..3c910337b6 --- /dev/null +++ b/rlp/safe.go @@ -0,0 +1,27 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +//go:build nacl || js || !cgo +// +build nacl js !cgo + +package rlp + +import "reflect" + +// byteArrayBytes returns a slice of the byte array v. +func byteArrayBytes(v reflect.Value, length int) []byte { + return v.Slice(0, length).Bytes() +} diff --git a/rlp/typecache.go b/rlp/typecache.go new file mode 100644 index 0000000000..ab04751bbd --- /dev/null +++ b/rlp/typecache.go @@ -0,0 +1,240 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlp + +import ( + "fmt" + "reflect" + "sync" + "sync/atomic" + + "github.com/tenderly/coreth/rlp/internal/rlpstruct" +) + +// typeinfo is an entry in the type cache. +type typeinfo struct { + decoder decoder + decoderErr error // error from makeDecoder + writer writer + writerErr error // error from makeWriter +} + +// typekey is the key of a type in typeCache. It includes the struct tags because +// they might generate a different decoder. +type typekey struct { + reflect.Type + rlpstruct.Tags +} + +type decoder func(*Stream, reflect.Value) error + +type writer func(reflect.Value, *encBuffer) error + +var theTC = newTypeCache() + +type typeCache struct { + cur atomic.Value + + // This lock synchronizes writers. + mu sync.Mutex + next map[typekey]*typeinfo +} + +func newTypeCache() *typeCache { + c := new(typeCache) + c.cur.Store(make(map[typekey]*typeinfo)) + return c +} + +func cachedDecoder(typ reflect.Type) (decoder, error) { + info := theTC.info(typ) + return info.decoder, info.decoderErr +} + +func cachedWriter(typ reflect.Type) (writer, error) { + info := theTC.info(typ) + return info.writer, info.writerErr +} + +func (c *typeCache) info(typ reflect.Type) *typeinfo { + key := typekey{Type: typ} + if info := c.cur.Load().(map[typekey]*typeinfo)[key]; info != nil { + return info + } + + // Not in the cache, need to generate info for this type. + return c.generate(typ, rlpstruct.Tags{}) +} + +func (c *typeCache) generate(typ reflect.Type, tags rlpstruct.Tags) *typeinfo { + c.mu.Lock() + defer c.mu.Unlock() + + cur := c.cur.Load().(map[typekey]*typeinfo) + if info := cur[typekey{typ, tags}]; info != nil { + return info + } + + // Copy cur to next. + c.next = make(map[typekey]*typeinfo, len(cur)+1) + for k, v := range cur { + c.next[k] = v + } + + // Generate. + info := c.infoWhileGenerating(typ, tags) + + // next -> cur + c.cur.Store(c.next) + c.next = nil + return info +} + +func (c *typeCache) infoWhileGenerating(typ reflect.Type, tags rlpstruct.Tags) *typeinfo { + key := typekey{typ, tags} + if info := c.next[key]; info != nil { + return info + } + // Put a dummy value into the cache before generating. + // If the generator tries to lookup itself, it will get + // the dummy value and won't call itself recursively. + info := new(typeinfo) + c.next[key] = info + info.generate(typ, tags) + return info +} + +type field struct { + index int + info *typeinfo + optional bool +} + +// structFields resolves the typeinfo of all public fields in a struct type. +func structFields(typ reflect.Type) (fields []field, err error) { + // Convert fields to rlpstruct.Field. + var allStructFields []rlpstruct.Field + for i := 0; i < typ.NumField(); i++ { + rf := typ.Field(i) + allStructFields = append(allStructFields, rlpstruct.Field{ + Name: rf.Name, + Index: i, + Exported: rf.PkgPath == "", + Tag: string(rf.Tag), + Type: *rtypeToStructType(rf.Type, nil), + }) + } + + // Filter/validate fields. + structFields, structTags, err := rlpstruct.ProcessFields(allStructFields) + if err != nil { + if tagErr, ok := err.(rlpstruct.TagError); ok { + tagErr.StructType = typ.String() + return nil, tagErr + } + return nil, err + } + + // Resolve typeinfo. + for i, sf := range structFields { + typ := typ.Field(sf.Index).Type + tags := structTags[i] + info := theTC.infoWhileGenerating(typ, tags) + fields = append(fields, field{sf.Index, info, tags.Optional}) + } + return fields, nil +} + +// firstOptionalField returns the index of the first field with "optional" tag. +func firstOptionalField(fields []field) int { + for i, f := range fields { + if f.optional { + return i + } + } + return len(fields) +} + +type structFieldError struct { + typ reflect.Type + field int + err error +} + +func (e structFieldError) Error() string { + return fmt.Sprintf("%v (struct field %v.%s)", e.err, e.typ, e.typ.Field(e.field).Name) +} + +func (i *typeinfo) generate(typ reflect.Type, tags rlpstruct.Tags) { + i.decoder, i.decoderErr = makeDecoder(typ, tags) + i.writer, i.writerErr = makeWriter(typ, tags) +} + +// rtypeToStructType converts typ to rlpstruct.Type. +func rtypeToStructType(typ reflect.Type, rec map[reflect.Type]*rlpstruct.Type) *rlpstruct.Type { + k := typ.Kind() + if k == reflect.Invalid { + panic("invalid kind") + } + + if prev := rec[typ]; prev != nil { + return prev // short-circuit for recursive types + } + if rec == nil { + rec = make(map[reflect.Type]*rlpstruct.Type) + } + + t := &rlpstruct.Type{ + Name: typ.String(), + Kind: k, + IsEncoder: typ.Implements(encoderInterface), + IsDecoder: typ.Implements(decoderInterface), + } + rec[typ] = t + if k == reflect.Array || k == reflect.Slice || k == reflect.Ptr { + t.Elem = rtypeToStructType(typ.Elem(), rec) + } + return t +} + +// typeNilKind gives the RLP value kind for nil pointers to 'typ'. +func typeNilKind(typ reflect.Type, tags rlpstruct.Tags) Kind { + styp := rtypeToStructType(typ, nil) + + var nk rlpstruct.NilKind + if tags.NilOK { + nk = tags.NilKind + } else { + nk = styp.DefaultNilValue() + } + switch nk { + case rlpstruct.NilKindString: + return String + case rlpstruct.NilKindList: + return List + default: + panic("invalid nil kind value") + } +} + +func isUint(k reflect.Kind) bool { + return k >= reflect.Uint && k <= reflect.Uintptr +} + +func isByte(typ reflect.Type) bool { + return typ.Kind() == reflect.Uint8 && !typ.Implements(encoderInterface) +} diff --git a/rlp/unsafe.go b/rlp/unsafe.go new file mode 100644 index 0000000000..2152ba35fc --- /dev/null +++ b/rlp/unsafe.go @@ -0,0 +1,35 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +//go:build !nacl && !js && cgo +// +build !nacl,!js,cgo + +package rlp + +import ( + "reflect" + "unsafe" +) + +// byteArrayBytes returns a slice of the byte array v. +func byteArrayBytes(v reflect.Value, length int) []byte { + var s []byte + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s)) + hdr.Data = v.UnsafeAddr() + hdr.Cap = length + hdr.Len = length + return s +} diff --git a/scripts/build.sh b/scripts/build.sh index 4ca9b2e808..4e8609eeae 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -50,4 +50,4 @@ coreth_commit=${CORETH_COMMIT:-$( git rev-list -1 HEAD )} # Build Coreth, which is run as a subprocess echo "Building Coreth Version: $coreth_version; GitCommit: $coreth_commit" -go build -ldflags "-X github.com/ava-labs/coreth/plugin/evm.GitCommit=$coreth_commit -X github.com/ava-labs/coreth/plugin/evm.Version=$coreth_version" -o "$binary_path" "plugin/"*.go +go build -ldflags "-X github.com/tenderly/coreth/plugin/evm.GitCommit=$coreth_commit -X github.com/tenderly/coreth/plugin/evm.Version=$coreth_version" -o "$binary_path" "plugin/"*.go diff --git a/sync/client/mock_client.go b/sync/client/mock_client.go index e23cdf4322..ec9d481b00 100644 --- a/sync/client/mock_client.go +++ b/sync/client/mock_client.go @@ -10,11 +10,11 @@ import ( "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/ids" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/rlp" "github.com/tenderly/coreth/sync/handlers" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" ) var ( diff --git a/sync/handlers/block_request_test.go b/sync/handlers/block_request_test.go index e261e1da84..286ca37efa 100644 --- a/sync/handlers/block_request_test.go +++ b/sync/handlers/block_request_test.go @@ -8,16 +8,16 @@ import ( "testing" "github.com/ava-labs/avalanchego/ids" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" "github.com/tenderly/coreth/consensus/dummy" "github.com/tenderly/coreth/core" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb/memorydb" "github.com/tenderly/coreth/params" "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/rlp" "github.com/tenderly/coreth/sync/handlers/stats" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" - "github.com/stretchr/testify/assert" ) func TestBlockRequestHandler(t *testing.T) { diff --git a/sync/handlers/leafs_request_test.go b/sync/handlers/leafs_request_test.go index 36d4bf2871..49d9aa72c5 100644 --- a/sync/handlers/leafs_request_test.go +++ b/sync/handlers/leafs_request_test.go @@ -10,18 +10,18 @@ import ( "testing" "github.com/ava-labs/avalanchego/ids" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state/snapshot" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/ethdb/memorydb" "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/rlp" "github.com/tenderly/coreth/sync/handlers/stats" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" - "github.com/stretchr/testify/assert" ) func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { diff --git a/sync/statesync/state_syncer.go b/sync/statesync/state_syncer.go index 1a59c60cbb..218367d41f 100644 --- a/sync/statesync/state_syncer.go +++ b/sync/statesync/state_syncer.go @@ -9,15 +9,15 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/rlp" syncclient "github.com/tenderly/coreth/sync/client" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) const defaultNumThreads int = 4 diff --git a/sync/statesync/sync_test.go b/sync/statesync/sync_test.go index 87a8f9d6d7..8efef299ab 100644 --- a/sync/statesync/sync_test.go +++ b/sync/statesync/sync_test.go @@ -10,20 +10,20 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state/snapshot" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/ethdb/memorydb" "github.com/tenderly/coreth/plugin/evm/message" + "github.com/tenderly/coreth/rlp" statesyncclient "github.com/tenderly/coreth/sync/client" "github.com/tenderly/coreth/sync/handlers" handlerstats "github.com/tenderly/coreth/sync/handlers/stats" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" - "github.com/stretchr/testify/assert" ) const testSyncTimeout = 20 * time.Second diff --git a/sync/statesync/test_sync.go b/sync/statesync/test_sync.go index 0c9523efbe..3c9145e481 100644 --- a/sync/statesync/test_sync.go +++ b/sync/statesync/test_sync.go @@ -8,15 +8,15 @@ import ( "math/rand" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" "github.com/tenderly/coreth/accounts/keystore" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state/snapshot" "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/rlp" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" - "github.com/stretchr/testify/assert" ) // assertDBConsistency checks [serverTrieDB] and [clientTrieDB] have the same EVM state trie at [root], diff --git a/tests/rlp_test_util.go b/tests/rlp_test_util.go index 686264cd45..ca5993fb10 100644 --- a/tests/rlp_test_util.go +++ b/tests/rlp_test_util.go @@ -34,7 +34,7 @@ import ( "math/big" "strings" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) // RLPTest is the JSON structure of a single RLP test. diff --git a/trie/committer.go b/trie/committer.go index 228ea60bd7..535f2cf0d4 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -8,7 +8,7 @@ // // Much love to the original authors for their work. // ********** -// Copyright 2019 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -27,73 +27,50 @@ package trie import ( - "errors" "fmt" - "sync" "github.com/ethereum/go-ethereum/common" ) -// leafChanSize is the size of the leafCh. It's a pretty arbitrary number, to allow -// some parallelism but not incur too much memory overhead. -const leafChanSize = 200 - -// leaf represents a trie leaf value +// leaf represents a trie leaf node type leaf struct { - size int // size of the rlp data (estimate) - hash common.Hash // hash of rlp data - node node // the node to commit + blob []byte // raw blob of leaf + parent common.Hash // the hash of parent node } -// committer is a type used for the trie Commit operation. A committer has some -// internal preallocated temp space, and also a callback that is invoked when -// leaves are committed. The leafs are passed through the `leafCh`, to allow -// some level of parallelism. -// By 'some level' of parallelism, it's still the case that all leaves will be -// processed sequentially - onleaf will never be called in parallel or out of order. +// committer is the tool used for the trie Commit operation. The committer will +// capture all dirty nodes during the commit process and keep them cached in +// insertion order. type committer struct { - onleaf LeafCallback - leafCh chan *leaf -} - -// committers live in a global sync.Pool -var committerPool = sync.Pool{ - New: func() interface{} { - return &committer{} - }, + nodes *NodeSet + collectLeaf bool } // newCommitter creates a new committer or picks one from the pool. -func newCommitter() *committer { - return committerPool.Get().(*committer) -} - -func returnCommitterToPool(h *committer) { - h.onleaf = nil - h.leafCh = nil - committerPool.Put(h) +func newCommitter(owner common.Hash, collectLeaf bool) *committer { + return &committer{ + nodes: NewNodeSet(owner), + collectLeaf: collectLeaf, + } } // Commit collapses a node down into a hash node and inserts it into the database -func (c *committer) Commit(n node, db *Database) (hashNode, int, error) { - if db == nil { - return nil, 0, errors.New("no db provided") - } - h, committed, err := c.commit(n, db) +func (c *committer) Commit(n node) (hashNode, *NodeSet, error) { + h, err := c.commit(nil, n) if err != nil { - return nil, 0, err + return nil, nil, err } - return h.(hashNode), committed, nil + return h.(hashNode), c.nodes, nil } // commit collapses a node down into a hash node and inserts it into the database -func (c *committer) commit(n node, db *Database) (node, int, error) { +func (c *committer) commit(path []byte, n node) (node, error) { // if this path is clean, use available cached data hash, dirty := n.cache() if hash != nil && !dirty { - return hash, 0, nil + return hash, nil } - // Commit children, then parent, and remove remove the dirty flag. + // Commit children, then parent, and remove the dirty flag. switch cn := n.(type) { case *shortNode: // Commit child @@ -101,36 +78,35 @@ func (c *committer) commit(n node, db *Database) (node, int, error) { // If the child is fullNode, recursively commit, // otherwise it can only be hashNode or valueNode. - var childCommitted int if _, ok := cn.Val.(*fullNode); ok { - childV, committed, err := c.commit(cn.Val, db) + childV, err := c.commit(append(path, cn.Key...), cn.Val) if err != nil { - return nil, 0, err + return nil, err } - collapsed.Val, childCommitted = childV, committed + collapsed.Val = childV } // The key needs to be copied, since we're delivering it to database collapsed.Key = hexToCompact(cn.Key) - hashedNode := c.store(collapsed, db) + hashedNode := c.store(path, collapsed) if hn, ok := hashedNode.(hashNode); ok { - return hn, childCommitted + 1, nil + return hn, nil } - return collapsed, childCommitted, nil + return collapsed, nil case *fullNode: - hashedKids, childCommitted, err := c.commitChildren(cn, db) + hashedKids, err := c.commitChildren(path, cn) if err != nil { - return nil, 0, err + return nil, err } collapsed := cn.copy() collapsed.Children = hashedKids - hashedNode := c.store(collapsed, db) + hashedNode := c.store(path, collapsed) if hn, ok := hashedNode.(hashNode); ok { - return hn, childCommitted + 1, nil + return hn, nil } - return collapsed, childCommitted, nil + return collapsed, nil case hashNode: - return cn, 0, nil + return cn, nil default: // nil, valuenode shouldn't be committed panic(fmt.Sprintf("%T: invalid node: %v", n, n)) @@ -138,11 +114,8 @@ func (c *committer) commit(n node, db *Database) (node, int, error) { } // commitChildren commits the children of the given fullnode -func (c *committer) commitChildren(n *fullNode, db *Database) ([17]node, int, error) { - var ( - committed int - children [17]node - ) +func (c *committer) commitChildren(path []byte, n *fullNode) ([17]node, error) { + var children [17]node for i := 0; i < 16; i++ { child := n.Children[i] if child == nil { @@ -158,87 +131,62 @@ func (c *committer) commitChildren(n *fullNode, db *Database) ([17]node, int, er // Commit the child recursively and store the "hashed" value. // Note the returned node can be some embedded nodes, so it's // possible the type is not hashNode. - hashed, childCommitted, err := c.commit(child, db) + hashed, err := c.commit(append(path, byte(i)), child) if err != nil { - return children, 0, err + return children, err } children[i] = hashed - committed += childCommitted } // For the 17th child, it's possible the type is valuenode. if n.Children[16] != nil { children[16] = n.Children[16] } - return children, committed, nil + return children, nil } // store hashes the node n and if we have a storage layer specified, it writes // the key/value pair to it and tracks any node->child references as well as any // node->external trie references. -func (c *committer) store(n node, db *Database) node { +func (c *committer) store(path []byte, n node) node { // Larger nodes are replaced by their hash and stored in the database. - var ( - hash, _ = n.cache() - size int - ) + var hash, _ = n.cache() + // This was not generated - must be a small node stored in the parent. + // In theory, we should check if the node is leaf here (embedded node + // usually is leaf node). But small value(less than 32bytes) is not + // our target(leaves in account trie only). if hash == nil { - // This was not generated - must be a small node stored in the parent. - // In theory, we should apply the leafCall here if it's not nil(embedded - // node usually contains value). But small value(less than 32bytes) is - // not our target. return n - } else { - // We have the hash already, estimate the RLP encoding-size of the node. - // The size is used for mem tracking, does not need to be exact - size = estimateSize(n) } - // If we're using channel-based leaf-reporting, send to channel. - // The leaf channel will be active only when there an active leaf-callback - if c.leafCh != nil { - c.leafCh <- &leaf{ - size: size, - hash: common.BytesToHash(hash), - node: n, + // We have the hash already, estimate the RLP encoding-size of the node. + // The size is used for mem tracking, does not need to be exact + var ( + size = estimateSize(n) + nhash = common.BytesToHash(hash) + mnode = &memoryNode{ + hash: nhash, + node: simplifyNode(n), + size: uint16(size), } - } else if db != nil { - // No leaf-callback used, but there's still a database. Do serial - // insertion - db.Insert(common.BytesToHash(hash), size, n) - } - return hash -} - -// commitLoop does the actual insert + leaf callback for nodes. -func (c *committer) commitLoop(db *Database) { - for item := range c.leafCh { - var ( - hash = item.hash - size = item.size - n = item.node - ) - // We are pooling the trie nodes into an intermediate memory cache - db.Insert(hash, size, n) - - if c.onleaf != nil { - switch n := n.(type) { - case *shortNode: - if child, ok := n.Val.(valueNode); ok { - c.onleaf(nil, nil, child, hash) - } - case *fullNode: - // For children in range [0, 15], it's impossible - // to contain valueNode. Only check the 17th child. - if n.Children[16] != nil { - c.onleaf(nil, nil, n.Children[16].(valueNode), hash) - } + ) + // Collect the dirty node to nodeset for return. + c.nodes.add(string(path), mnode) + + // Collect the corresponding leaf node if it's required. We don't check + // full node since it's impossible to store value in fullNode. The key + // length of leaves should be exactly same. + if c.collectLeaf { + if sn, ok := n.(*shortNode); ok { + if val, ok := sn.Val.(valueNode); ok { + c.nodes.addLeaf(&leaf{blob: val, parent: nhash}) } } } + return hash } // estimateSize estimates the size of an rlp-encoded node, without actually // rlp-encoding it (zero allocs). This method has been experimentally tried, and with a trie -// with 1000 leafs, the only errors above 1% are on small shortnodes, where this +// with 1000 leaves, the only errors above 1% are on small shortnodes, where this // method overestimates by 2 or 3 bytes (e.g. 37 instead of 35) func estimateSize(n node) int { switch n := n.(type) { diff --git a/trie/database.go b/trie/database.go index 8458a66976..69989ccb5b 100644 --- a/trie/database.go +++ b/trie/database.go @@ -35,16 +35,13 @@ import ( "time" "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/ethdb" "github.com/tenderly/coreth/metrics" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" -) - -const ( - defaultPreimagesLimit = 4 * 1024 * 1024 // 4 MB + "github.com/tenderly/coreth/rlp" ) var ( @@ -87,15 +84,10 @@ var ( type Database struct { diskdb ethdb.KeyValueStore // Persistent storage for matured trie nodes - preimagesLock sync.RWMutex // Used to gate acess to [preimagesSize] and [preimages] - preimagesSize common.StorageSize // Storage size of the preimages cache - preimages map[common.Hash][]byte // Preimages of nodes from the secure trie - - dirtiesLock sync.RWMutex // Used to gate access to all trie node data structures and metrics (everything below) - cleans *fastcache.Cache // GC friendly memory cache of clean node RLPs - dirties map[common.Hash]*cachedNode // Data and references relationships of dirty trie nodes - oldest common.Hash // Oldest tracked node, flush-list head - newest common.Hash // Newest tracked node, flush-list tail + cleans *fastcache.Cache // GC friendly memory cache of clean node RLPs + dirties map[common.Hash]*cachedNode // Data and references relationships of dirty trie nodes + oldest common.Hash // Oldest tracked node, flush-list head + newest common.Hash // Newest tracked node, flush-list tail gctime time.Duration // Time spent on garbage collection since last commit gcnodes uint64 // Nodes garbage collected since last commit @@ -107,6 +99,9 @@ type Database struct { dirtiesSize common.StorageSize // Storage size of the dirty node cache (exc. metadata) childrenSize common.StorageSize // Storage size of the external children tracking + preimages *preimageStore // The store for caching preimages + + lock sync.RWMutex } // rawNode is a simple binary blob used to differentiate between collapsed trie @@ -131,16 +126,9 @@ func (n rawFullNode) cache() (hashNode, bool) { panic("this should never end u func (n rawFullNode) fstring(ind string) string { panic("this should never end up in a live trie") } func (n rawFullNode) EncodeRLP(w io.Writer) error { - var nodes [17]node - - for i, child := range n { - if child != nil { - nodes[i] = child - } else { - nodes[i] = nilValueNode - } - } - return rlp.Encode(w, nodes) + eb := rlp.NewEncoderBuffer(w) + n.encode(eb) + return eb.Flush() } // rawShortNode represents only the useful data content of a short node, with the @@ -182,18 +170,17 @@ func (n *cachedNode) rlp() []byte { if node, ok := n.node.(rawNode); ok { return node } - blob, err := rlp.EncodeToBytes(n.node) - if err != nil { - panic(err) - } - return blob + return nodeToBytes(n.node) } // obj returns the decoded and expanded trie node, either directly from the cache, // or by regenerating it from the rlp encoded blob. func (n *cachedNode) obj(hash common.Hash) node { if node, ok := n.node.(rawNode); ok { - return mustDecodeNode(hash[:], node) + // The raw-blob format nodes are loaded either from the + // clean cache or the database, they are all in their own + // copy and safe to use unsafe decoder. + return mustDecodeNodeUnsafe(hash[:], node) } return expandNode(hash[:], n.node) } @@ -311,15 +298,17 @@ func NewDatabaseWithConfig(diskdb ethdb.KeyValueStore, config *Config) *Database if config != nil && config.Cache > 0 { cleans = fastcache.New(config.Cache * 1024 * 1024) } + var preimage *preimageStore + if config != nil && config.Preimages { + preimage = newPreimageStore(diskdb) + } db := &Database{ diskdb: diskdb, cleans: cleans, dirties: map[common.Hash]*cachedNode{{}: { children: make(map[common.Hash]uint16), }}, - } - if config == nil || config.Preimages { // TODO(karalabe): Flip to default off in the future - db.preimages = make(map[common.Hash][]byte) + preimages: preimage, } return db } @@ -329,14 +318,10 @@ func (db *Database) DiskDB() ethdb.KeyValueStore { return db.diskdb } -// insert inserts a collapsed trie node into the memory database. -// The blob size must be specified to allow proper size tracking. +// insert inserts a simplified trie node into the memory database. // All nodes inserted by this function will be reference tracked // and in theory should only used for **trie nodes** insertion. -func (db *Database) Insert(hash common.Hash, size int, node node) { - db.dirtiesLock.Lock() - defer db.dirtiesLock.Unlock() - +func (db *Database) insert(hash common.Hash, size int, node node) { // If the node's already cached, skip if _, ok := db.dirties[hash]; ok { return @@ -345,7 +330,7 @@ func (db *Database) Insert(hash common.Hash, size int, node node) { // Create the cached entry for this node entry := &cachedNode{ - node: simplifyNode(node), + node: node, size: uint16(size), flushPrev: db.newest, } @@ -365,46 +350,6 @@ func (db *Database) Insert(hash common.Hash, size int, node node) { db.dirtiesSize += common.StorageSize(common.HashLength + entry.size) } -// InsertPreimages writes a map of new trie node preimages to the -// memory database if it's yet unknown. -// -// The method will NOT make a copy of the provided slice, -// only use if the preimage will NOT be changed later on. -func (db *Database) InsertPreimages(preimages map[string][]byte) { - // Short circuit if preimage collection is disabled - if db.preimages == nil { - return - } - // Track the preimage if a yet unknown one - db.preimagesLock.Lock() - defer db.preimagesLock.Unlock() - for hk, preimage := range preimages { - hash := common.BytesToHash([]byte(hk)) - if _, ok := db.preimages[hash]; ok { - continue - } - db.preimages[hash] = preimage - db.preimagesSize += common.StorageSize(common.HashLength + len(preimage)) - } -} - -// Preimage retrieves a cached trie node pre-image from memory. If it cannot be -// found cached, the method queries the persistent database for the content. -func (db *Database) Preimage(hash common.Hash) []byte { - // Short circuit if preimage collection is disabled - if db.preimages == nil { - return nil - } - // Retrieve the node from cache if available - db.preimagesLock.RLock() - preimage := db.preimages[hash] - db.preimagesLock.RUnlock() - if preimage != nil { - return preimage - } - return rawdb.ReadPreimage(db.diskdb, hash) -} - // RawNode retrieves an encoded cached trie node from memory. If it cannot be found // cached, the method queries the persistent database for the content. This function // will not return the metaroot. @@ -450,9 +395,9 @@ func (db *Database) node(hash common.Hash) ([]byte, *cachedNode, error) { } } // Retrieve the node from the dirty cache if available - db.dirtiesLock.RLock() + db.lock.RLock() dirty := db.dirties[hash] - db.dirtiesLock.RUnlock() + db.lock.RUnlock() if dirty != nil { memcacheDirtyHitMeter.Mark(1) @@ -478,8 +423,8 @@ func (db *Database) node(hash common.Hash) ([]byte, *cachedNode, error) { // This method is extremely expensive and should only be used to validate internal // states in test code. func (db *Database) Nodes() []common.Hash { - db.dirtiesLock.RLock() - defer db.dirtiesLock.RUnlock() + db.lock.RLock() + defer db.lock.RUnlock() var hashes = make([]common.Hash, 0, len(db.dirties)) for hash := range db.dirties { @@ -495,9 +440,13 @@ func (db *Database) Nodes() []common.Hash { // and external node(e.g. storage trie root), all internal trie nodes // are referenced together by database itself. func (db *Database) Reference(child common.Hash, parent common.Hash) { - db.dirtiesLock.Lock() - defer db.dirtiesLock.Unlock() + db.lock.Lock() + defer db.lock.Unlock() + + db.reference(child, parent) +} +func (db *Database) reference(child common.Hash, parent common.Hash) { // If the node does not exist, it's a node pulled from disk, skip node, ok := db.dirties[child] if !ok { @@ -525,8 +474,8 @@ func (db *Database) Dereference(root common.Hash) { return } - db.dirtiesLock.Lock() - defer db.dirtiesLock.Unlock() + db.lock.Lock() + defer db.lock.Unlock() nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now() db.dereference(root, common.Hash{}) @@ -596,49 +545,6 @@ func (db *Database) dereference(child common.Hash, parent common.Hash) { } } -// WritePreimages writes all preimages to disk if more than [limit] are currently in -// memory. -func (db *Database) WritePreimages(limit common.StorageSize) error { - // Short circuit if preimage collection is disabled - if db.preimages == nil { - return nil - } - - // If the preimage cache got large enough, push to disk. If it's still small - // leave for later to deduplicate writes. - db.preimagesLock.RLock() - if db.preimagesSize <= limit { - db.preimagesLock.RUnlock() - return nil - } - toFlush := make(map[common.Hash][]byte) - for k, v := range db.preimages { - toFlush[k] = v - } - db.preimagesLock.RUnlock() - - // Short circuit if nothing to do - if len(toFlush) == 0 { - return nil - } - - // Write preimages to disk - batch := db.diskdb.NewBatch() - rawdb.WritePreimages(batch, toFlush) - if err := batch.Write(); err != nil { - return err - } - - // Write successful, clear out the flushed data - db.preimagesLock.Lock() - defer db.preimagesLock.Unlock() - for hash, preimage := range toFlush { - delete(db.preimages, hash) - db.preimagesSize -= common.StorageSize(common.HashLength + len(preimage)) - } - return nil -} - // flushItem is used to track all [cachedNode]s that must be written to disk type flushItem struct { hash common.Hash @@ -679,15 +585,19 @@ func (db *Database) writeFlushItems(toFlush []flushItem) error { // memory usage goes below the given threshold. func (db *Database) Cap(limit common.StorageSize) error { start := time.Now() - if err := db.WritePreimages(defaultPreimagesLimit); err != nil { - return err + // If the preimage cache got large enough, push to disk. If it's still small + // leave for later to deduplicate writes. + if db.preimages != nil { + if err := db.preimages.commit(false); err != nil { + return err + } } // It is important that outside code doesn't see an inconsistent state // (referenced data removed from memory cache during commit but not yet // in persistent storage). This is ensured by only uncaching existing // data when the database write finalizes. - db.dirtiesLock.RLock() + db.lock.RLock() lockStart := time.Now() nodes, storage := len(db.dirties), db.dirtiesSize @@ -697,7 +607,7 @@ func (db *Database) Cap(limit common.StorageSize) error { pendingSize := db.dirtiesSize + common.StorageSize((len(db.dirties)-1)*cachedNodeSize) pendingSize += db.childrenSize - common.StorageSize(len(db.dirties[common.Hash{}].children)*(common.HashLength+2)) if pendingSize <= limit { - db.dirtiesLock.RUnlock() + db.lock.RUnlock() return nil } @@ -718,7 +628,7 @@ func (db *Database) Cap(limit common.StorageSize) error { } oldest = node.flushNext } - db.dirtiesLock.RUnlock() + db.lock.RUnlock() lockTime := time.Since(lockStart) // Write nodes to disk @@ -730,8 +640,8 @@ func (db *Database) Cap(limit common.StorageSize) error { // // NOTE: The order of the flushlist may have changed while the lock was not // held, so we cannot just iterate to [oldest]. - db.dirtiesLock.Lock() - defer db.dirtiesLock.Unlock() + db.lock.Lock() + defer db.lock.Unlock() lockStart = time.Now() for _, item := range toFlush { // [item.rlp] is populated in [writeFlushItems] @@ -761,14 +671,16 @@ func (db *Database) Cap(limit common.StorageSize) error { // effect, all pre-images accumulated up to this point are also written. func (db *Database) Commit(node common.Hash, report bool, callback func(common.Hash)) error { start := time.Now() - if err := db.WritePreimages(0); err != nil { - return err + if db.preimages != nil { + if err := db.preimages.commit(true); err != nil { + return err + } } // It is important that outside code doesn't see an inconsistent state (referenced // data removed from memory cache during commit but not yet in persistent storage). // This is ensured by only uncaching existing data when the database write finalizes. - db.dirtiesLock.RLock() + db.lock.RLock() lockStart := time.Now() nodes, storage := len(db.dirties), db.dirtiesSize toFlush, err := db.commit(node, make([]flushItem, 0, 128), callback) @@ -776,7 +688,7 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H log.Error("Failed to commit trie from trie database", "err", err) return err } - db.dirtiesLock.RUnlock() + db.lock.RUnlock() lockTime := time.Since(lockStart) // Write nodes to disk @@ -785,8 +697,8 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H } // Flush all written items from dirites - db.dirtiesLock.Lock() - defer db.dirtiesLock.Unlock() + db.lock.Lock() + defer db.lock.Unlock() lockStart = time.Now() for _, item := range toFlush { // [item.rlp] is populated in [writeFlushItems] @@ -888,19 +800,98 @@ func (db *Database) removeFromDirties(hash common.Hash, rlp []byte) { } } +// Update inserts the dirty nodes in provided nodeset into database and +// links the account trie with multiple storage tries if necessary. +func (db *Database) Update(nodes *MergedNodeSet) error { + db.lock.Lock() + defer db.lock.Unlock() + + return db.update(nodes) +} + +// UpdateAndReferenceRoot inserts the dirty nodes in provided nodeset into +// database and links the account trie with multiple storage tries if necessary, +// then adds a reference [from] root to the metaroot while holding the db's lock. +func (db *Database) UpdateAndReferenceRoot(nodes *MergedNodeSet, root common.Hash) error { + db.lock.Lock() + defer db.lock.Unlock() + + if err := db.update(nodes); err != nil { + return err + } + db.reference(root, common.Hash{}) + return nil +} + +func (db *Database) update(nodes *MergedNodeSet) error { + // Insert dirty nodes into the database. In the same tree, it must be + // ensured that children are inserted first, then parent so that children + // can be linked with their parent correctly. + // + // Note, the storage tries must be flushed before the account trie to + // retain the invariant that children go into the dirty cache first. + var order []common.Hash + for owner := range nodes.sets { + if owner == (common.Hash{}) { + continue + } + order = append(order, owner) + } + if _, ok := nodes.sets[common.Hash{}]; ok { + order = append(order, common.Hash{}) + } + for _, owner := range order { + subset := nodes.sets[owner] + for _, path := range subset.paths { + n, ok := subset.nodes[path] + if !ok { + return fmt.Errorf("missing node %x %v", owner, path) + } + db.insert(n.hash, int(n.size), n.node) + } + } + // Link up the account trie and storage trie if the node points + // to an account trie leaf. + if set, present := nodes.sets[common.Hash{}]; present { + for _, n := range set.leaves { + var account types.StateAccount + if err := rlp.DecodeBytes(n.blob, &account); err != nil { + return err + } + if account.Root != emptyRoot { + db.reference(account.Root, n.parent) + } + } + } + return nil +} + // Size returns the current storage size of the memory cache in front of the // persistent database layer. func (db *Database) Size() (common.StorageSize, common.StorageSize) { - db.preimagesLock.RLock() - preimagesSize := db.preimagesSize - db.preimagesLock.RUnlock() - // db.dirtiesSize only contains the useful data in the cache, but when reporting // the total memory consumption, the maintenance metadata is also needed to be // counted. - db.dirtiesLock.RLock() - defer db.dirtiesLock.RUnlock() + db.lock.RLock() + defer db.lock.RUnlock() var metadataSize = common.StorageSize((len(db.dirties) - 1) * cachedNodeSize) var metarootRefs = common.StorageSize(len(db.dirties[common.Hash{}].children) * (common.HashLength + 2)) - return db.dirtiesSize + db.childrenSize + metadataSize - metarootRefs, preimagesSize + var preimageSize common.StorageSize + if db.preimages != nil { + preimageSize = db.preimages.size() + } + return db.dirtiesSize + db.childrenSize + metadataSize - metarootRefs, preimageSize +} + +// CommitPreimages flushes the dangling preimages to disk. It is meant to be +// called when closing the blockchain object, so that preimages are persisted +// to the database. +func (db *Database) CommitPreimages() error { + db.lock.Lock() + defer db.lock.Unlock() + + if db.preimages == nil { + return nil + } + return db.preimages.commit(true) } diff --git a/trie/database_test.go b/trie/database_test.go index c14fd87642..8a0354f90f 100644 --- a/trie/database_test.go +++ b/trie/database_test.go @@ -29,8 +29,8 @@ package trie import ( "testing" - "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/ethdb/memorydb" ) // Tests that the trie database returns a missing trie node error if attempting diff --git a/trie/errors.go b/trie/errors.go index 0323666cf7..d3a9af4c88 100644 --- a/trie/errors.go +++ b/trie/errors.go @@ -36,10 +36,21 @@ import ( // in the case where a trie node is not present in the local database. It contains // information necessary for retrieving the missing node. type MissingNodeError struct { + Owner common.Hash // owner of the trie if it's 2-layered trie NodeHash common.Hash // hash of the missing node Path []byte // hex-encoded path to the missing node + err error // concrete error for missing trie node +} + +// Unwrap returns the concrete error for missing trie node which +// allows us for further analysis outside. +func (err *MissingNodeError) Unwrap() error { + return err.err } func (err *MissingNodeError) Error() string { - return fmt.Sprintf("missing trie node %x (path %x)", err.NodeHash, err.Path) + if err.Owner == (common.Hash{}) { + return fmt.Sprintf("missing trie node %x (path %x) %v", err.NodeHash, err.Path, err.err) + } + return fmt.Sprintf("missing trie node %x (owner %x) (path %x) %v", err.NodeHash, err.Owner, err.Path, err.err) } diff --git a/trie/hasher.go b/trie/hasher.go index 76c2cdc7b1..58d68c3641 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -8,7 +8,7 @@ // // Much love to the original authors for their work. // ********** -// Copyright 2019 The go-ethereum Authors +// Copyright 2016 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -30,35 +30,26 @@ import ( "sync" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" "golang.org/x/crypto/sha3" ) -type sliceBuffer []byte - -func (b *sliceBuffer) Write(data []byte) (n int, err error) { - *b = append(*b, data...) - return len(data), nil -} - -func (b *sliceBuffer) Reset() { - *b = (*b)[:0] -} - // hasher is a type used for the trie Hash operation. A hasher has some // internal preallocated temp space type hasher struct { sha crypto.KeccakState - tmp sliceBuffer - parallel bool // Whether to use paralallel threads when hashing + tmp []byte + encbuf rlp.EncoderBuffer + parallel bool // Whether to use parallel threads when hashing } // hasherPool holds pureHashers var hasherPool = sync.Pool{ New: func() interface{} { return &hasher{ - tmp: make(sliceBuffer, 0, 550), // cap is as large as a full fullNode. - sha: sha3.NewLegacyKeccak256().(crypto.KeccakState), + tmp: make([]byte, 0, 550), // cap is as large as a full fullNode. + sha: sha3.NewLegacyKeccak256().(crypto.KeccakState), + encbuf: rlp.NewEncoderBuffer(nil), } }, } @@ -163,30 +154,41 @@ func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached // into compact form for RLP encoding. // If the rlp data is smaller than 32 bytes, `nil` is returned. func (h *hasher) shortnodeToHash(n *shortNode, force bool) node { - h.tmp.Reset() - if err := rlp.Encode(&h.tmp, n); err != nil { - panic("encode error: " + err.Error()) - } + n.encode(h.encbuf) + enc := h.encodedBytes() - if len(h.tmp) < 32 && !force { + if len(enc) < 32 && !force { return n // Nodes smaller than 32 bytes are stored inside their parent } - return h.hashData(h.tmp) + return h.hashData(enc) } // shortnodeToHash is used to creates a hashNode from a set of hashNodes, (which // may contain nil values) func (h *hasher) fullnodeToHash(n *fullNode, force bool) node { - h.tmp.Reset() - // Generate the RLP encoding of the node - if err := n.EncodeRLP(&h.tmp); err != nil { - panic("encode error: " + err.Error()) - } + n.encode(h.encbuf) + enc := h.encodedBytes() - if len(h.tmp) < 32 && !force { + if len(enc) < 32 && !force { return n // Nodes smaller than 32 bytes are stored inside their parent } - return h.hashData(h.tmp) + return h.hashData(enc) +} + +// encodedBytes returns the result of the last encoding operation on h.encbuf. +// This also resets the encoder buffer. +// +// All node encoding must be done like this: +// +// node.encode(h.encbuf) +// enc := h.encodedBytes() +// +// This convention exists because node.encode can only be inlined/escape-analyzed when +// called on a concrete receiver type. +func (h *hasher) encodedBytes() []byte { + h.tmp = h.encbuf.AppendToBytes(h.tmp[:0]) + h.encbuf.Reset(nil) + return h.tmp } // hashData hashes the provided data @@ -199,7 +201,7 @@ func (h *hasher) hashData(data []byte) hashNode { } // proofHash is used to construct trie proofs, and returns the 'collapsed' -// node (for later RLP encoding) aswell as the hashed node -- unless the +// node (for later RLP encoding) as well as the hashed node -- unless the // node is smaller than 32 bytes, in which case it will be returned as is. // This method does not do anything on value- or hash-nodes. func (h *hasher) proofHash(original node) (collapsed, hashed node) { diff --git a/trie/iterator.go b/trie/iterator.go index 44af99be06..e4a2a3fd6d 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -31,9 +31,8 @@ import ( "container/heap" "errors" - "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/ethdb" ) // Iterator is a key-value trie iterator that traverses a Trie. @@ -96,6 +95,10 @@ type NodeIterator interface { // For leaf nodes, the last element of the path is the 'terminator symbol' 0x10. Path() []byte + // NodeBlob returns the rlp-encoded value of the current iterated node. + // If the node is an embedded node in its parent, nil is returned then. + NodeBlob() []byte + // Leaf returns true iff the current node is a leaf node. Leaf() bool @@ -223,8 +226,7 @@ func (it *nodeIterator) LeafProof() [][]byte { // Gather nodes that end up as hash nodes (or the root) node, hashed := hasher.proofHash(item.node) if _, ok := hashed.(hashNode); ok || i == 0 { - enc, _ := rlp.EncodeToBytes(node) - proofs = append(proofs, enc) + proofs = append(proofs, nodeToBytes(node)) } } return proofs @@ -237,6 +239,18 @@ func (it *nodeIterator) Path() []byte { return it.path } +func (it *nodeIterator) NodeBlob() []byte { + if it.Hash() == (common.Hash{}) { + return nil // skip the non-standalone node + } + blob, err := it.resolveBlob(it.Hash().Bytes(), it.Path()) + if err != nil { + it.err = err + return nil + } + return blob +} + func (it *nodeIterator) Error() error { if it.err == errIteratorEnd { return nil @@ -371,8 +385,16 @@ func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) { } } } - resolved, err := it.trie.resolveHash(hash, path) - return resolved, err + return it.trie.resolveHash(hash, path) +} + +func (it *nodeIterator) resolveBlob(hash hashNode, path []byte) ([]byte, error) { + if it.resolver != nil { + if blob, err := it.resolver.Get(hash); err == nil && len(blob) > 0 { + return blob, nil + } + } + return it.trie.resolveBlob(hash, path) } func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error { @@ -563,6 +585,10 @@ func (it *differenceIterator) Path() []byte { return it.b.Path() } +func (it *differenceIterator) NodeBlob() []byte { + return it.b.NodeBlob() +} + func (it *differenceIterator) AddResolver(resolver ethdb.KeyValueReader) { panic("not implemented") } @@ -674,6 +700,10 @@ func (it *unionIterator) Path() []byte { return (*it.items)[0].Path() } +func (it *unionIterator) NodeBlob() []byte { + return (*it.items)[0].NodeBlob() +} + func (it *unionIterator) AddResolver(resolver ethdb.KeyValueReader) { panic("not implemented") } diff --git a/trie/iterator_test.go b/trie/iterator_test.go index 75827bb891..4cb5cdff22 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -33,14 +33,15 @@ import ( "math/rand" "testing" - "github.com/tenderly/coreth/ethdb" - "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/memorydb" ) func TestEmptyIterator(t *testing.T) { - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) iter := trie.NodeIterator(nil) seen := make(map[string]struct{}) @@ -53,7 +54,8 @@ func TestEmptyIterator(t *testing.T) { } func TestIterator(t *testing.T) { - trie := newEmpty() + db := NewDatabase(rawdb.NewMemoryDatabase()) + trie := NewEmpty(db) vals := []struct{ k, v string }{ {"do", "verb"}, {"ether", "wookiedoo"}, @@ -68,8 +70,13 @@ func TestIterator(t *testing.T) { all[val.k] = val.v trie.Update([]byte(val.k), []byte(val.v)) } - trie.Commit(nil) + root, nodes, err := trie.Commit(false) + if err != nil { + t.Fatalf("Failed to commit trie %v", err) + } + db.Update(NewWithNodeSet(nodes)) + trie, _ = New(common.Hash{}, root, db) found := make(map[string]string) it := NewIterator(trie.NodeIterator(nil)) for it.Next() { @@ -89,7 +96,7 @@ type kv struct { } func TestIteratorLargeData(t *testing.T) { - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) vals := make(map[string]*kv) for i := byte(0); i < 255; i++ { @@ -182,7 +189,7 @@ var testdata2 = []kvs{ } func TestIteratorSeek(t *testing.T) { - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) for _, val := range testdata1 { trie.Update([]byte(val.k), []byte(val.v)) } @@ -223,17 +230,23 @@ func checkIteratorOrder(want []kvs, it *Iterator) error { } func TestDifferenceIterator(t *testing.T) { - triea := newEmpty() + dba := NewDatabase(rawdb.NewMemoryDatabase()) + triea := NewEmpty(dba) for _, val := range testdata1 { triea.Update([]byte(val.k), []byte(val.v)) } - triea.Commit(nil) + rootA, nodesA, _ := triea.Commit(false) + dba.Update(NewWithNodeSet(nodesA)) + triea, _ = New(common.Hash{}, rootA, dba) - trieb := newEmpty() + dbb := NewDatabase(rawdb.NewMemoryDatabase()) + trieb := NewEmpty(dbb) for _, val := range testdata2 { trieb.Update([]byte(val.k), []byte(val.v)) } - trieb.Commit(nil) + rootB, nodesB, _ := trieb.Commit(false) + dbb.Update(NewWithNodeSet(nodesB)) + trieb, _ = New(common.Hash{}, rootB, dbb) found := make(map[string]string) di, _ := NewDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil)) @@ -259,17 +272,23 @@ func TestDifferenceIterator(t *testing.T) { } func TestUnionIterator(t *testing.T) { - triea := newEmpty() + dba := NewDatabase(rawdb.NewMemoryDatabase()) + triea := NewEmpty(dba) for _, val := range testdata1 { triea.Update([]byte(val.k), []byte(val.v)) } - triea.Commit(nil) + rootA, nodesA, _ := triea.Commit(false) + dba.Update(NewWithNodeSet(nodesA)) + triea, _ = New(common.Hash{}, rootA, dba) - trieb := newEmpty() + dbb := NewDatabase(rawdb.NewMemoryDatabase()) + trieb := NewEmpty(dbb) for _, val := range testdata2 { trieb.Update([]byte(val.k), []byte(val.v)) } - trieb.Commit(nil) + rootB, nodesB, _ := trieb.Commit(false) + dbb.Update(NewWithNodeSet(nodesB)) + trieb, _ = New(common.Hash{}, rootB, dbb) di, _ := NewUnionIterator([]NodeIterator{triea.NodeIterator(nil), trieb.NodeIterator(nil)}) it := NewIterator(di) @@ -306,7 +325,7 @@ func TestUnionIterator(t *testing.T) { } func TestIteratorNoDups(t *testing.T) { - var tr Trie + tr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) for _, val := range testdata1 { tr.Update([]byte(val.k), []byte(val.v)) } @@ -321,11 +340,12 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) { diskdb := memorydb.New() triedb := NewDatabase(diskdb) - tr, _ := New(common.Hash{}, triedb) + tr := NewEmpty(triedb) for _, val := range testdata1 { tr.Update([]byte(val.k), []byte(val.v)) } - tr.Commit(nil) + _, nodes, _ := tr.Commit(false) + triedb.Update(NewWithNodeSet(nodes)) if !memonly { triedb.Commit(tr.Hash(), true, nil) } @@ -346,7 +366,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) { } for i := 0; i < 20; i++ { // Create trie that will load all nodes from DB. - tr, _ := New(tr.Hash(), triedb) + tr, _ := New(common.Hash{}, tr.Hash(), triedb) // Remove a random node from the database. It can't be the root node // because that one is already loaded. @@ -412,11 +432,12 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) { diskdb := memorydb.New() triedb := NewDatabase(diskdb) - ctr, _ := New(common.Hash{}, triedb) + ctr := NewEmpty(triedb) for _, val := range testdata1 { ctr.Update([]byte(val.k), []byte(val.v)) } - root, _, _ := ctr.Commit(nil) + root, nodes, _ := ctr.Commit(false) + triedb.Update(NewWithNodeSet(nodes)) if !memonly { triedb.Commit(root, true, nil) } @@ -434,7 +455,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) { } // Create a new iterator that seeks to "bars". Seeking can't proceed because // the node is missing. - tr, _ := New(root, triedb) + tr, _ := New(common.Hash{}, root, triedb) it := tr.NodeIterator([]byte("bars")) missing, ok := it.Error().(*MissingNodeError) if !ok { @@ -493,10 +514,15 @@ func (l *loggingDb) NewBatch() ethdb.Batch { return l.backend.NewBatch() } +func (l *loggingDb) NewBatchWithSize(size int) ethdb.Batch { + return l.backend.NewBatchWithSize(size) +} + func (l *loggingDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { fmt.Printf("NewIterator\n") return l.backend.NewIterator(prefix, start) } + func (l *loggingDb) Stat(property string) (string, error) { return l.backend.Stat(property) } @@ -510,11 +536,11 @@ func (l *loggingDb) Close() error { } // makeLargeTestTrie create a sample test trie -func makeLargeTestTrie() (*Database, *SecureTrie, *loggingDb) { +func makeLargeTestTrie() (*Database, *StateTrie, *loggingDb) { // Create an empty trie logDb := &loggingDb{0, memorydb.New()} triedb := NewDatabase(logDb) - trie, _ := NewSecure(common.Hash{}, triedb) + trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb) // Fill it with some arbitrary data for i := 0; i < 10000; i++ { @@ -526,7 +552,8 @@ func makeLargeTestTrie() (*Database, *SecureTrie, *loggingDb) { val = crypto.Keccak256(val) trie.Update(key, val) } - trie.Commit(nil) + _, nodes, _ := trie.Commit(false) + triedb.Update(NewWithNodeSet(nodes)) // Return the generated trie return triedb, trie, logDb } @@ -544,3 +571,55 @@ func TestNodeIteratorLargeTrie(t *testing.T) { t.Fatalf("Too many lookups during seek, have %d want %d", have, want) } } + +func TestIteratorNodeBlob(t *testing.T) { + var ( + db = memorydb.New() + triedb = NewDatabase(db) + trie = NewEmpty(triedb) + ) + vals := []struct{ k, v string }{ + {"do", "verb"}, + {"ether", "wookiedoo"}, + {"horse", "stallion"}, + {"shaman", "horse"}, + {"doge", "coin"}, + {"dog", "puppy"}, + {"somethingveryoddindeedthis is", "myothernodedata"}, + } + all := make(map[string]string) + for _, val := range vals { + all[val.k] = val.v + trie.Update([]byte(val.k), []byte(val.v)) + } + _, nodes, _ := trie.Commit(false) + triedb.Update(NewWithNodeSet(nodes)) + triedb.Cap(0) + + found := make(map[common.Hash][]byte) + it := trie.NodeIterator(nil) + for it.Next(true) { + if it.Hash() == (common.Hash{}) { + continue + } + found[it.Hash()] = it.NodeBlob() + } + + dbIter := db.NewIterator(nil, nil) + defer dbIter.Release() + + var count int + for dbIter.Next() { + got, present := found[common.BytesToHash(dbIter.Key())] + if !present { + t.Fatalf("Miss trie node %v", dbIter.Key()) + } + if !bytes.Equal(got, dbIter.Value()) { + t.Fatalf("Unexpected trie node want %v got %v", dbIter.Value(), got) + } + count += 1 + } + if count != len(found) { + t.Fatal("Find extra trie node via iterator") + } +} diff --git a/trie/node.go b/trie/node.go index 162a0c6efd..f0f7e394b4 100644 --- a/trie/node.go +++ b/trie/node.go @@ -32,14 +32,15 @@ import ( "strings" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/rlp" ) var indices = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "[17]"} type node interface { - fstring(string) string cache() (hashNode, bool) + encode(w rlp.EncoderBuffer) + fstring(string) string } type ( @@ -62,16 +63,9 @@ var nilValueNode = valueNode(nil) // EncodeRLP encodes a full node into the consensus RLP format. func (n *fullNode) EncodeRLP(w io.Writer) error { - var nodes [17]node - - for i, child := range &n.Children { - if child != nil { - nodes[i] = child - } else { - nodes[i] = nilValueNode - } - } - return rlp.Encode(w, nodes) + eb := rlp.NewEncoderBuffer(w) + n.encode(eb) + return eb.Flush() } func (n *fullNode) copy() *fullNode { copy := *n; return © } @@ -115,6 +109,7 @@ func (n valueNode) fstring(ind string) string { return fmt.Sprintf("%x ", []byte(n)) } +// mustDecodeNode is a wrapper of decodeNode and panic if any error is encountered. func mustDecodeNode(hash, buf []byte) node { n, err := decodeNode(hash, buf) if err != nil { @@ -123,8 +118,29 @@ func mustDecodeNode(hash, buf []byte) node { return n } -// decodeNode parses the RLP encoding of a trie node. +// mustDecodeNodeUnsafe is a wrapper of decodeNodeUnsafe and panic if any error is +// encountered. +func mustDecodeNodeUnsafe(hash, buf []byte) node { + n, err := decodeNodeUnsafe(hash, buf) + if err != nil { + panic(fmt.Sprintf("node %x: %v", hash, err)) + } + return n +} + +// decodeNode parses the RLP encoding of a trie node. It will deep-copy the passed +// byte slice for decoding, so it's safe to modify the byte slice afterwards. The- +// decode performance of this function is not optimal, but it is suitable for most +// scenarios with low performance requirements and hard to determine whether the +// byte slice be modified or not. func decodeNode(hash, buf []byte) (node, error) { + return decodeNodeUnsafe(hash, common.CopyBytes(buf)) +} + +// decodeNodeUnsafe parses the RLP encoding of a trie node. The passed byte slice +// will be directly referenced by node without bytes deep copy, so the input MUST +// not be changed after. +func decodeNodeUnsafe(hash, buf []byte) (node, error) { if len(buf) == 0 { return nil, io.ErrUnexpectedEOF } @@ -157,7 +173,7 @@ func decodeShort(hash, elems []byte) (node, error) { if err != nil { return nil, fmt.Errorf("invalid value node: %v", err) } - return &shortNode{key, append(valueNode{}, val...), flag}, nil + return &shortNode{key, valueNode(val), flag}, nil } r, _, err := decodeRef(rest) if err != nil { @@ -180,7 +196,7 @@ func decodeFull(hash, elems []byte) (*fullNode, error) { return n, err } if len(val) > 0 { - n.Children[16] = append(valueNode{}, val...) + n.Children[16] = valueNode(val) } return n, nil } @@ -206,7 +222,7 @@ func decodeRef(buf []byte) (node, []byte, error) { // empty node return nil, rest, nil case kind == rlp.String && len(val) == 32: - return append(hashNode{}, val...), rest, nil + return hashNode(val), rest, nil default: return nil, nil, fmt.Errorf("invalid RLP string size %d (want 0 or 32)", len(val)) } diff --git a/trie/node_enc.go b/trie/node_enc.go new file mode 100644 index 0000000000..113b7a367b --- /dev/null +++ b/trie/node_enc.go @@ -0,0 +1,97 @@ +// (c) 2022, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "github.com/tenderly/coreth/rlp" +) + +func nodeToBytes(n node) []byte { + w := rlp.NewEncoderBuffer(nil) + n.encode(w) + result := w.ToBytes() + w.Flush() + return result +} + +func (n *fullNode) encode(w rlp.EncoderBuffer) { + offset := w.List() + for _, c := range n.Children { + if c != nil { + c.encode(w) + } else { + w.Write(rlp.EmptyString) + } + } + w.ListEnd(offset) +} + +func (n *shortNode) encode(w rlp.EncoderBuffer) { + offset := w.List() + w.WriteBytes(n.Key) + if n.Val != nil { + n.Val.encode(w) + } else { + w.Write(rlp.EmptyString) + } + w.ListEnd(offset) +} + +func (n hashNode) encode(w rlp.EncoderBuffer) { + w.WriteBytes(n) +} + +func (n valueNode) encode(w rlp.EncoderBuffer) { + w.WriteBytes(n) +} + +func (n rawFullNode) encode(w rlp.EncoderBuffer) { + offset := w.List() + for _, c := range n { + if c != nil { + c.encode(w) + } else { + w.Write(rlp.EmptyString) + } + } + w.ListEnd(offset) +} + +func (n *rawShortNode) encode(w rlp.EncoderBuffer) { + offset := w.List() + w.WriteBytes(n.Key) + if n.Val != nil { + n.Val.encode(w) + } else { + w.Write(rlp.EmptyString) + } + w.ListEnd(offset) +} + +func (n rawNode) encode(w rlp.EncoderBuffer) { + w.Write(n) +} diff --git a/trie/node_test.go b/trie/node_test.go index 246c74ac34..597d2919b2 100644 --- a/trie/node_test.go +++ b/trie/node_test.go @@ -8,7 +8,7 @@ // // Much love to the original authors for their work. // ********** -// Copyright 2019 The go-ethereum Authors +// Copyright 2016 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -30,7 +30,8 @@ import ( "bytes" "testing" - "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/crypto" + "github.com/tenderly/coreth/rlp" ) func newTestFullNode(v []byte) []interface{} { @@ -102,3 +103,123 @@ func TestDecodeFullNode(t *testing.T) { t.Fatalf("decode full node err: %v", err) } } + +// goos: darwin +// goarch: arm64 +// pkg: github.com/tenderly/coreth/trie +// BenchmarkEncodeShortNode +// BenchmarkEncodeShortNode-8 16878850 70.81 ns/op 48 B/op 1 allocs/op +func BenchmarkEncodeShortNode(b *testing.B) { + node := &shortNode{ + Key: []byte{0x1, 0x2}, + Val: hashNode(randBytes(32)), + } + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + nodeToBytes(node) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/tenderly/coreth/trie +// BenchmarkEncodeFullNode +// BenchmarkEncodeFullNode-8 4323273 284.4 ns/op 576 B/op 1 allocs/op +func BenchmarkEncodeFullNode(b *testing.B) { + node := &fullNode{} + for i := 0; i < 16; i++ { + node.Children[i] = hashNode(randBytes(32)) + } + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + nodeToBytes(node) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/tenderly/coreth/trie +// BenchmarkDecodeShortNode +// BenchmarkDecodeShortNode-8 7925638 151.0 ns/op 157 B/op 4 allocs/op +func BenchmarkDecodeShortNode(b *testing.B) { + node := &shortNode{ + Key: []byte{0x1, 0x2}, + Val: hashNode(randBytes(32)), + } + blob := nodeToBytes(node) + hash := crypto.Keccak256(blob) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + mustDecodeNode(hash, blob) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/tenderly/coreth/trie +// BenchmarkDecodeShortNodeUnsafe +// BenchmarkDecodeShortNodeUnsafe-8 9027476 128.6 ns/op 109 B/op 3 allocs/op +func BenchmarkDecodeShortNodeUnsafe(b *testing.B) { + node := &shortNode{ + Key: []byte{0x1, 0x2}, + Val: hashNode(randBytes(32)), + } + blob := nodeToBytes(node) + hash := crypto.Keccak256(blob) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + mustDecodeNodeUnsafe(hash, blob) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/tenderly/coreth/trie +// BenchmarkDecodeFullNode +// BenchmarkDecodeFullNode-8 1597462 761.9 ns/op 1280 B/op 18 allocs/op +func BenchmarkDecodeFullNode(b *testing.B) { + node := &fullNode{} + for i := 0; i < 16; i++ { + node.Children[i] = hashNode(randBytes(32)) + } + blob := nodeToBytes(node) + hash := crypto.Keccak256(blob) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + mustDecodeNode(hash, blob) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/tenderly/coreth/trie +// BenchmarkDecodeFullNodeUnsafe +// BenchmarkDecodeFullNodeUnsafe-8 1789070 687.1 ns/op 704 B/op 17 allocs/op +func BenchmarkDecodeFullNodeUnsafe(b *testing.B) { + node := &fullNode{} + for i := 0; i < 16; i++ { + node.Children[i] = hashNode(randBytes(32)) + } + blob := nodeToBytes(node) + hash := crypto.Keccak256(blob) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + mustDecodeNodeUnsafe(hash, blob) + } +} diff --git a/trie/nodeset.go b/trie/nodeset.go new file mode 100644 index 0000000000..421ad13435 --- /dev/null +++ b/trie/nodeset.go @@ -0,0 +1,104 @@ +// (c) 2022, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +// memoryNode is all the information we know about a single cached trie node +// in the memory. +type memoryNode struct { + hash common.Hash // Node hash, computed by hashing rlp value + size uint16 // Byte size of the useful cached data + node node // Cached collapsed trie node, or raw rlp data +} + +// NodeSet contains all dirty nodes collected during the commit operation. +// Each node is keyed by path. It's not thread-safe to use. +type NodeSet struct { + owner common.Hash // the identifier of the trie + paths []string // the path of dirty nodes, sort by insertion order + nodes map[string]*memoryNode // the map of dirty nodes, keyed by node path + leaves []*leaf // the list of dirty leaves +} + +// NewNodeSet initializes an empty node set to be used for tracking dirty nodes +// from a specific account or storage trie. The owner is zero for the account +// trie and the owning account address hash for storage tries. +func NewNodeSet(owner common.Hash) *NodeSet { + return &NodeSet{ + owner: owner, + nodes: make(map[string]*memoryNode), + } +} + +// add caches node with provided path and node object. +func (set *NodeSet) add(path string, node *memoryNode) { + set.paths = append(set.paths, path) + set.nodes[path] = node +} + +// addLeaf caches the provided leaf node. +func (set *NodeSet) addLeaf(node *leaf) { + set.leaves = append(set.leaves, node) +} + +// Len returns the number of dirty nodes contained in the set. +func (set *NodeSet) Len() int { + return len(set.nodes) +} + +// MergedNodeSet represents a merged dirty node set for a group of tries. +type MergedNodeSet struct { + sets map[common.Hash]*NodeSet +} + +// NewMergedNodeSet initializes an empty merged set. +func NewMergedNodeSet() *MergedNodeSet { + return &MergedNodeSet{sets: make(map[common.Hash]*NodeSet)} +} + +// NewWithNodeSet constructs a merged nodeset with the provided single set. +func NewWithNodeSet(set *NodeSet) *MergedNodeSet { + merged := NewMergedNodeSet() + merged.Merge(set) + return merged +} + +// Merge merges the provided dirty nodes of a trie into the set. The assumption +// is held that no duplicated set belonging to the same trie will be merged twice. +func (set *MergedNodeSet) Merge(other *NodeSet) error { + _, present := set.sets[other.owner] + if present { + return fmt.Errorf("duplicate trie for owner %#x", other.owner) + } + set.sets[other.owner] = other + return nil +} diff --git a/trie/preimages.go b/trie/preimages.go new file mode 100644 index 0000000000..da6c53baf5 --- /dev/null +++ b/trie/preimages.go @@ -0,0 +1,107 @@ +// (c) 2022, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" +) + +const defaultPreimagesLimit = 4 * 1024 * 1024 // 4 MB + +// preimageStore is the store for caching preimages of node key. +type preimageStore struct { + lock sync.RWMutex + disk ethdb.KeyValueStore + preimages map[common.Hash][]byte // Preimages of nodes from the secure trie + preimagesSize common.StorageSize // Storage size of the preimages cache +} + +// newPreimageStore initializes the store for caching preimages. +func newPreimageStore(disk ethdb.KeyValueStore) *preimageStore { + return &preimageStore{ + disk: disk, + preimages: make(map[common.Hash][]byte), + } +} + +// insertPreimage writes a new trie node pre-image to the memory database if it's +// yet unknown. The method will NOT make a copy of the slice, only use if the +// preimage will NOT be changed later on. +func (store *preimageStore) insertPreimage(preimages map[common.Hash][]byte) { + store.lock.Lock() + defer store.lock.Unlock() + + for hash, preimage := range preimages { + if _, ok := store.preimages[hash]; ok { + continue + } + store.preimages[hash] = preimage + store.preimagesSize += common.StorageSize(common.HashLength + len(preimage)) + } +} + +// preimage retrieves a cached trie node pre-image from memory. If it cannot be +// found cached, the method queries the persistent database for the content. +func (store *preimageStore) preimage(hash common.Hash) []byte { + store.lock.RLock() + preimage := store.preimages[hash] + store.lock.RUnlock() + + if preimage != nil { + return preimage + } + return rawdb.ReadPreimage(store.disk, hash) +} + +// commit flushes the cached preimages into the disk. +func (store *preimageStore) commit(force bool) error { + store.lock.Lock() + defer store.lock.Unlock() + + if store.preimagesSize <= defaultPreimagesLimit && !force { + return nil + } + batch := store.disk.NewBatch() + rawdb.WritePreimages(batch, store.preimages) + if err := batch.Write(); err != nil { + return err + } + store.preimages, store.preimagesSize = make(map[common.Hash][]byte), 0 + return nil +} + +// size returns the current storage size of accumulated preimages. +func (store *preimageStore) size() common.StorageSize { + store.lock.RLock() + defer store.lock.RUnlock() + + return store.preimagesSize +} diff --git a/trie/proof.go b/trie/proof.go index 4d92131eb5..b360aebb32 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -31,11 +31,10 @@ import ( "errors" "fmt" - "github.com/tenderly/coreth/ethdb" - "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" ) // Prove constructs a merkle proof for key. The result contains all encoded nodes @@ -47,9 +46,12 @@ import ( // with the node that proves the absence of the key. func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error { // Collect all nodes on the path to key. + var ( + prefix []byte + nodes []node + tn = t.root + ) key = keybytesToHex(key) - var nodes []node - tn := t.root for len(key) > 0 && tn != nil { switch n := tn.(type) { case *shortNode: @@ -58,16 +60,18 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e tn = nil } else { tn = n.Val + prefix = append(prefix, n.Key...) key = key[len(n.Key):] } nodes = append(nodes, n) case *fullNode: tn = n.Children[key[0]] + prefix = append(prefix, key[0]) key = key[1:] nodes = append(nodes, n) case hashNode: var err error - tn, err = t.resolveHash(n, nil) + tn, err = t.resolveHash(n, prefix) if err != nil { log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) return err @@ -89,7 +93,7 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e if hash, ok := hn.(hashNode); ok || i == 0 { // If the node's database encoding is a hash (or is the // root node), it becomes a proof element. - enc, _ := rlp.EncodeToBytes(n) + enc := nodeToBytes(n) if !ok { hash = hasher.hashData(enc) } @@ -106,7 +110,7 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e // If the trie does not contain a value for key, the returned proof contains all // nodes of the longest existing prefix of the key (at least the root node), ending // with the node that proves the absence of the key. -func (t *SecureTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error { +func (t *StateTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error { return t.trie.Prove(key, fromLevel, proofDb) } @@ -379,11 +383,12 @@ func unset(parent node, child node, key []byte, pos int, removeLeft bool) error // branch. The parent must be a fullnode. fn := parent.(*fullNode) fn.Children[key[pos-1]] = nil - } else { - // The key of fork shortnode is greater than the - // path(it doesn't belong to the range), keep - // it with the cached hash available. } + //else { + // The key of fork shortnode is greater than the + // path(it doesn't belong to the range), keep + // it with the cached hash available. + //} } else { if bytes.Compare(cld.Key, key[pos:]) > 0 { // The key of fork shortnode is greater than the @@ -391,11 +396,12 @@ func unset(parent node, child node, key []byte, pos int, removeLeft bool) error // branch. The parent must be a fullnode. fn := parent.(*fullNode) fn.Children[key[pos-1]] = nil - } else { - // The key of fork shortnode is less than the - // path(it doesn't belong to the range), keep - // it with the cached hash available. } + //else { + // The key of fork shortnode is less than the + // path(it doesn't belong to the range), keep + // it with the cached hash available. + //} } return nil } @@ -563,7 +569,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key } // Rebuild the trie with the leaf stream, the shape of trie // should be same with the original one. - tr := &Trie{root: root, db: NewDatabase(memorydb.New())} + tr := &Trie{root: root, db: NewDatabase(rawdb.NewMemoryDatabase())} if empty { tr.root = nil } diff --git a/trie/proof_test.go b/trie/proof_test.go index ae99be94c6..a53007e654 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -35,9 +35,10 @@ import ( "testing" "time" - "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb/memorydb" ) func init() { @@ -89,7 +90,7 @@ func TestProof(t *testing.T) { } func TestOneElementProof(t *testing.T) { - trie := new(Trie) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) updateString(trie, "k", "v") for i, prover := range makeProvers(trie) { proof := prover([]byte("k")) @@ -140,7 +141,7 @@ func TestBadProof(t *testing.T) { // Tests that missing keys can also be proven. The test explicitly uses a single // entry trie and checks for missing keys both before and after the single entry. func TestMissingKeyProof(t *testing.T) { - trie := new(Trie) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) updateString(trie, "k", "v") for i, key := range []string{"a", "j", "l", "z"} { @@ -214,7 +215,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { proof := memorydb.New() // Short circuit if the decreased key is same with the previous key - first := decreseKey(common.CopyBytes(entries[start].k)) + first := decreaseKey(common.CopyBytes(entries[start].k)) if start != 0 && bytes.Equal(first, entries[start-1].k) { continue } @@ -223,7 +224,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { continue } // Short circuit if the increased key is same with the next key - last := increseKey(common.CopyBytes(entries[end-1].k)) + last := increaseKey(common.CopyBytes(entries[end-1].k)) if end != len(entries) && bytes.Equal(last, entries[end].k) { continue } @@ -283,7 +284,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { // Case 1 start, end := 100, 200 - first := decreseKey(common.CopyBytes(entries[start].k)) + first := decreaseKey(common.CopyBytes(entries[start].k)) proof := memorydb.New() if err := trie.Prove(first, 0, proof); err != nil { @@ -306,7 +307,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { // Case 2 start, end = 100, 200 - last := increseKey(common.CopyBytes(entries[end-1].k)) + last := increaseKey(common.CopyBytes(entries[end-1].k)) proof = memorydb.New() if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) @@ -352,7 +353,7 @@ func TestOneElementRangeProof(t *testing.T) { // One element with left non-existent edge proof start = 1000 - first := decreseKey(common.CopyBytes(entries[start].k)) + first := decreaseKey(common.CopyBytes(entries[start].k)) proof = memorydb.New() if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) @@ -367,7 +368,7 @@ func TestOneElementRangeProof(t *testing.T) { // One element with right non-existent edge proof start = 1000 - last := increseKey(common.CopyBytes(entries[start].k)) + last := increaseKey(common.CopyBytes(entries[start].k)) proof = memorydb.New() if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) @@ -382,7 +383,7 @@ func TestOneElementRangeProof(t *testing.T) { // One element with two non-existent edge proofs start = 1000 - first, last = decreseKey(common.CopyBytes(entries[start].k)), increseKey(common.CopyBytes(entries[start].k)) + first, last = decreaseKey(common.CopyBytes(entries[start].k)), increaseKey(common.CopyBytes(entries[start].k)) proof = memorydb.New() if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) @@ -396,7 +397,7 @@ func TestOneElementRangeProof(t *testing.T) { } // Test the mini trie with only a single element. - tinyTrie := new(Trie) + tinyTrie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) entry := &kv{randBytes(32), randBytes(20), false} tinyTrie.Update(entry.k, entry.v) @@ -468,7 +469,7 @@ func TestAllElementsProof(t *testing.T) { // TestSingleSideRangeProof tests the range starts from zero. func TestSingleSideRangeProof(t *testing.T) { for i := 0; i < 64; i++ { - trie := new(Trie) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) var entries entrySlice for i := 0; i < 4096; i++ { value := &kv{randBytes(32), randBytes(20), false} @@ -503,7 +504,7 @@ func TestSingleSideRangeProof(t *testing.T) { // TestReverseSingleSideRangeProof tests the range ends with 0xffff...fff. func TestReverseSingleSideRangeProof(t *testing.T) { for i := 0; i < 64; i++ { - trie := new(Trie) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) var entries entrySlice for i := 0; i < 4096; i++ { value := &kv{randBytes(32), randBytes(20), false} @@ -610,7 +611,7 @@ func TestBadRangeProof(t *testing.T) { // TestGappedRangeProof focuses on the small trie with embedded nodes. // If the gapped node is embedded in the trie, it should be detected too. func TestGappedRangeProof(t *testing.T) { - trie := new(Trie) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) var entries []*kv // Sorted entries for i := byte(0); i < 10; i++ { value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} @@ -650,9 +651,9 @@ func TestSameSideProofs(t *testing.T) { sort.Sort(entries) pos := 1000 - first := decreseKey(common.CopyBytes(entries[pos].k)) - first = decreseKey(first) - last := decreseKey(common.CopyBytes(entries[pos].k)) + first := decreaseKey(common.CopyBytes(entries[pos].k)) + first = decreaseKey(first) + last := decreaseKey(common.CopyBytes(entries[pos].k)) proof := memorydb.New() if err := trie.Prove(first, 0, proof); err != nil { @@ -666,9 +667,9 @@ func TestSameSideProofs(t *testing.T) { t.Fatalf("Expected error, got nil") } - first = increseKey(common.CopyBytes(entries[pos].k)) - last = increseKey(common.CopyBytes(entries[pos].k)) - last = increseKey(last) + first = increaseKey(common.CopyBytes(entries[pos].k)) + last = increaseKey(common.CopyBytes(entries[pos].k)) + last = increaseKey(last) proof = memorydb.New() if err := trie.Prove(first, 0, proof); err != nil { @@ -684,7 +685,7 @@ func TestSameSideProofs(t *testing.T) { } func TestHasRightElement(t *testing.T) { - trie := new(Trie) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) var entries entrySlice for i := 0; i < 4096; i++ { value := &kv{randBytes(32), randBytes(20), false} @@ -774,7 +775,7 @@ func TestEmptyRangeProof(t *testing.T) { } for _, c := range cases { proof := memorydb.New() - first := increseKey(common.CopyBytes(entries[c.pos].k)) + first := increaseKey(common.CopyBytes(entries[c.pos].k)) if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } @@ -913,7 +914,7 @@ func mutateByte(b []byte) { } } -func increseKey(key []byte) []byte { +func increaseKey(key []byte) []byte { for i := len(key) - 1; i >= 0; i-- { key[i]++ if key[i] != 0x0 { @@ -923,7 +924,7 @@ func increseKey(key []byte) []byte { return key } -func decreseKey(key []byte) []byte { +func decreaseKey(key []byte) []byte { for i := len(key) - 1; i >= 0; i-- { key[i]-- if key[i] != 0xff { @@ -1037,7 +1038,7 @@ func benchmarkVerifyRangeNoProof(b *testing.B, size int) { } func randomTrie(n int) (*Trie, map[string]*kv) { - trie := new(Trie) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) vals := make(map[string]*kv) for i := byte(0); i < 100; i++ { value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} @@ -1062,7 +1063,7 @@ func randBytes(n int) []byte { } func nonRandomTrie(n int) (*Trie, map[string]*kv) { - trie := new(Trie) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) vals := make(map[string]*kv) max := uint64(0xffffffffffffffff) for i := uint64(0); i < uint64(n); i++ { @@ -1087,7 +1088,7 @@ func TestRangeProofKeysWithSharedPrefix(t *testing.T) { common.Hex2Bytes("02"), common.Hex2Bytes("03"), } - trie := new(Trie) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) for i, key := range keys { trie.Update(key, vals[i]) } diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 73e590d4fb..1d2dbab929 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -29,30 +29,41 @@ package trie import ( "fmt" - "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/rlp" ) -// SecureTrie wraps a trie with key hashing. In a secure trie, all +// SecureTrie is the old name of StateTrie. +// Deprecated: use StateTrie. +type SecureTrie = StateTrie + +// NewSecure creates a new StateTrie. +// Deprecated: use NewStateTrie. +func NewSecure(owner common.Hash, root common.Hash, db *Database) (*SecureTrie, error) { + return NewStateTrie(owner, root, db) +} + +// StateTrie wraps a trie with key hashing. In a secure trie, all // access operations hash the key using keccak256. This prevents // calling code from creating long chains of nodes that // increase the access time. // -// Contrary to a regular trie, a SecureTrie can only be created with +// Contrary to a regular trie, a StateTrie can only be created with // New and must have an attached database. The database also stores // the preimage of each key. // -// SecureTrie is not safe for concurrent use. -type SecureTrie struct { +// StateTrie is not safe for concurrent use. +type StateTrie struct { trie Trie + preimages *preimageStore hashKeyBuf [common.HashLength]byte secKeyCache map[string][]byte - secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch + secKeyCacheOwner *StateTrie // Pointer to self, replace the key cache on mismatch } -// NewSecure creates a trie with an existing root node from a backing database +// NewStateTrie creates a trie with an existing root node from a backing database // and optional intermediate in-memory node pool. // // If root is the zero hash or the sha3 hash of an empty string, the @@ -63,20 +74,20 @@ type SecureTrie struct { // Loaded nodes are kept around until their 'cache generation' expires. // A new cache generation is created by each call to Commit. // cachelimit sets the number of past cache generations to keep. -func NewSecure(root common.Hash, db *Database) (*SecureTrie, error) { +func NewStateTrie(owner common.Hash, root common.Hash, db *Database) (*StateTrie, error) { if db == nil { panic("trie.NewSecure called without a database") } - trie, err := New(root, db) + trie, err := New(owner, root, db) if err != nil { return nil, err } - return &SecureTrie{trie: *trie}, nil + return &StateTrie{trie: *trie, preimages: db.preimages}, nil } // Get returns the value for key stored in the trie. // The value bytes must not be modified by the caller. -func (t *SecureTrie) Get(key []byte) []byte { +func (t *StateTrie) Get(key []byte) []byte { res, err := t.TryGet(key) if err != nil { log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) @@ -87,19 +98,50 @@ func (t *SecureTrie) Get(key []byte) []byte { // TryGet returns the value for key stored in the trie. // The value bytes must not be modified by the caller. // If a node was not found in the database, a MissingNodeError is returned. -func (t *SecureTrie) TryGet(key []byte) ([]byte, error) { +func (t *StateTrie) TryGet(key []byte) ([]byte, error) { return t.trie.TryGet(t.hashKey(key)) } +func (t *StateTrie) TryGetAccount(key []byte) (*types.StateAccount, error) { + var ret types.StateAccount + res, err := t.trie.TryGet(t.hashKey(key)) + if err != nil { + log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) + return &ret, err + } + if res == nil { + return nil, nil + } + err = rlp.DecodeBytes(res, &ret) + return &ret, err +} + +// TryGetAccountWithPreHashedKey does the same thing as TryGetAccount, however +// it expects a key that is already hashed. This constitutes an abstraction leak, +// since the client code needs to know the key format. +func (t *StateTrie) TryGetAccountWithPreHashedKey(key []byte) (*types.StateAccount, error) { + var ret types.StateAccount + res, err := t.trie.TryGet(key) + if err != nil { + log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) + return &ret, err + } + if res == nil { + return nil, nil + } + err = rlp.DecodeBytes(res, &ret) + return &ret, err +} + // TryGetNode attempts to retrieve a trie node by compact-encoded path. It is not // possible to use keybyte-encoding as the path might contain odd nibbles. -func (t *SecureTrie) TryGetNode(path []byte) ([]byte, int, error) { +func (t *StateTrie) TryGetNode(path []byte) ([]byte, int, error) { return t.trie.TryGetNode(path) } -// TryUpdate account will abstract the write of an account to the +// TryUpdateAccount account will abstract the write of an account to the // secure trie. -func (t *SecureTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { +func (t *StateTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { hk := t.hashKey(key) data, err := rlp.EncodeToBytes(acc) if err != nil { @@ -118,7 +160,7 @@ func (t *SecureTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error // // The value bytes must not be modified by the caller while they are // stored in the trie. -func (t *SecureTrie) Update(key, value []byte) { +func (t *StateTrie) Update(key, value []byte) { if err := t.TryUpdate(key, value); err != nil { log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) } @@ -132,7 +174,7 @@ func (t *SecureTrie) Update(key, value []byte) { // stored in the trie. // // If a node was not found in the database, a MissingNodeError is returned. -func (t *SecureTrie) TryUpdate(key, value []byte) error { +func (t *StateTrie) TryUpdate(key, value []byte) error { hk := t.hashKey(key) err := t.trie.TryUpdate(hk, value) if err != nil { @@ -143,7 +185,7 @@ func (t *SecureTrie) TryUpdate(key, value []byte) error { } // Delete removes any existing value for key from the trie. -func (t *SecureTrie) Delete(key []byte) { +func (t *StateTrie) Delete(key []byte) { if err := t.TryDelete(key); err != nil { log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) } @@ -151,7 +193,14 @@ func (t *SecureTrie) Delete(key []byte) { // TryDelete removes any existing value for key from the trie. // If a node was not found in the database, a MissingNodeError is returned. -func (t *SecureTrie) TryDelete(key []byte) error { +func (t *StateTrie) TryDelete(key []byte) error { + hk := t.hashKey(key) + delete(t.getSecKeyCache(), string(hk)) + return t.trie.TryDelete(hk) +} + +// TryDeleteAccount abstracts an account deletion from the trie. +func (t *StateTrie) TryDeleteAccount(key []byte) error { hk := t.hashKey(key) delete(t.getSecKeyCache(), string(hk)) return t.trie.TryDelete(hk) @@ -159,50 +208,64 @@ func (t *SecureTrie) TryDelete(key []byte) error { // GetKey returns the sha3 preimage of a hashed key that was // previously used to store a value. -func (t *SecureTrie) GetKey(shaKey []byte) []byte { +func (t *StateTrie) GetKey(shaKey []byte) []byte { if key, ok := t.getSecKeyCache()[string(shaKey)]; ok { return key } - return t.trie.db.Preimage(common.BytesToHash(shaKey)) + if t.preimages == nil { + return nil + } + return t.preimages.preimage(common.BytesToHash(shaKey)) } -// Commit writes all nodes and the secure hash pre-images to the trie's database. -// Nodes are stored with their sha3 hash as the key. -// -// Committing flushes nodes from memory. Subsequent Get calls will load nodes -// from the database. -func (t *SecureTrie) Commit(onleaf LeafCallback) (common.Hash, int, error) { +// Commit collects all dirty nodes in the trie and replace them with the +// corresponding node hash. All collected nodes(including dirty leaves if +// collectLeaf is true) will be encapsulated into a nodeset for return. +// The returned nodeset can be nil if the trie is clean(nothing to commit). +// All cached preimages will be also flushed if preimages recording is enabled. +// Once the trie is committed, it's not usable anymore. A new trie must +// be created with new root and updated trie database for following usage +func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) { // Write all the pre-images to the actual disk database if len(t.getSecKeyCache()) > 0 { - t.trie.db.InsertPreimages(t.secKeyCache) // if preimages are disabled, this returns immediately + if t.preimages != nil { + preimages := make(map[common.Hash][]byte) + for hk, key := range t.secKeyCache { + preimages[common.BytesToHash([]byte(hk))] = key + } + t.preimages.insertPreimage(preimages) + } t.secKeyCache = make(map[string][]byte) } // Commit the trie to its intermediate node database - return t.trie.Commit(onleaf) + return t.trie.Commit(collectLeaf) } // Hash returns the root hash of SecureTrie. It does not write to the // database and can be used even if the trie doesn't have one. -func (t *SecureTrie) Hash() common.Hash { +func (t *StateTrie) Hash() common.Hash { return t.trie.Hash() } // Copy returns a copy of SecureTrie. -func (t *SecureTrie) Copy() *SecureTrie { - cpy := *t - return &cpy +func (t *StateTrie) Copy() *StateTrie { + return &StateTrie{ + trie: *t.trie.Copy(), + preimages: t.preimages, + secKeyCache: t.secKeyCache, + } } // NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration // starts at the key after the given start key. -func (t *SecureTrie) NodeIterator(start []byte) NodeIterator { +func (t *StateTrie) NodeIterator(start []byte) NodeIterator { return t.trie.NodeIterator(start) } // hashKey returns the hash of key as an ephemeral buffer. // The caller must not hold onto the return value because it will become // invalid on the next call to hashKey or secKey. -func (t *SecureTrie) hashKey(key []byte) []byte { +func (t *StateTrie) hashKey(key []byte) []byte { h := newHasher(false) h.sha.Reset() h.sha.Write(key) @@ -214,7 +277,7 @@ func (t *SecureTrie) hashKey(key []byte) []byte { // getSecKeyCache returns the current secure key cache, creating a new one if // ownership changed (i.e. the current secure trie is a copy of another owning // the actual cache). -func (t *SecureTrie) getSecKeyCache() map[string][]byte { +func (t *StateTrie) getSecKeyCache() map[string][]byte { if t != t.secKeyCacheOwner { t.secKeyCacheOwner = t t.secKeyCache = make(map[string][]byte) diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index e81cf10467..af979a9e24 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -28,25 +28,26 @@ package trie import ( "bytes" + "fmt" "runtime" "sync" "testing" - "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/tenderly/coreth/ethdb/memorydb" ) -func newEmptySecure() *SecureTrie { - trie, _ := NewSecure(common.Hash{}, NewDatabase(memorydb.New())) +func newEmptySecure() *StateTrie { + trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, NewDatabase(memorydb.New())) return trie } -// makeTestSecureTrie creates a large enough secure trie for testing. -func makeTestSecureTrie() (*Database, *SecureTrie, map[string][]byte) { +// makeTestStateTrie creates a large enough secure trie for testing. +func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) { // Create an empty trie triedb := NewDatabase(memorydb.New()) - trie, _ := NewSecure(common.Hash{}, triedb) + trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb) // Fill it with some arbitrary data content := make(map[string][]byte) @@ -67,9 +68,15 @@ func makeTestSecureTrie() (*Database, *SecureTrie, map[string][]byte) { trie.Update(key, val) } } - trie.Commit(nil) - - // Return the generated trie + root, nodes, err := trie.Commit(false) + if err != nil { + panic(fmt.Errorf("failed to commit trie %v", err)) + } + if err := triedb.Update(NewWithNodeSet(nodes)); err != nil { + panic(fmt.Errorf("failed to commit db %v", err)) + } + // Re-create the trie based on the new state + trie, _ = NewSecure(common.Hash{}, root, triedb) return triedb, trie, content } @@ -115,15 +122,14 @@ func TestSecureGetKey(t *testing.T) { } } -func TestSecureTrieConcurrency(t *testing.T) { +func TestStateTrieConcurrency(t *testing.T) { // Create an initial trie and copy if for concurrent access - _, trie, _ := makeTestSecureTrie() + _, trie, _ := makeTestStateTrie() threads := runtime.NumCPU() - tries := make([]*SecureTrie, threads) + tries := make([]*StateTrie, threads) for i := 0; i < threads; i++ { - cpy := *trie - tries[i] = &cpy + tries[i] = trie.Copy() } // Start a batch of goroutines interactng with the trie pend := new(sync.WaitGroup) @@ -146,7 +152,7 @@ func TestSecureTrieConcurrency(t *testing.T) { tries[index].Update(key, val) } } - tries[index].Commit(nil) + tries[index].Commit(false) }(i) } // Wait for all threads to finish diff --git a/trie/stacktrie.go b/trie/stacktrie.go index f2ffcf7940..85dff84db3 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -35,10 +35,9 @@ import ( "io" "sync" - "github.com/tenderly/coreth/ethdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/ethdb" ) var ErrCommitDisabled = errors.New("no database for committing") @@ -49,9 +48,10 @@ var stPool = sync.Pool{ }, } -func stackTrieFromPool(db ethdb.KeyValueWriter) *StackTrie { +func stackTrieFromPool(db ethdb.KeyValueWriter, owner common.Hash) *StackTrie { st := stPool.Get().(*StackTrie) st.db = db + st.owner = owner return st } @@ -64,6 +64,7 @@ func returnToPool(st *StackTrie) { // in order. Once it determines that a subtree will no longer be inserted // into, it will hash it and free up the memory it uses. type StackTrie struct { + owner common.Hash // the owner of the trie nodeType uint8 // node type (as in branch, ext, leaf) val []byte // value contained by this node if it's a leaf key []byte // key chunk covered by this (leaf|ext) node @@ -79,6 +80,16 @@ func NewStackTrie(db ethdb.KeyValueWriter) *StackTrie { } } +// NewStackTrieWithOwner allocates and initializes an empty trie, but with +// the additional owner field. +func NewStackTrieWithOwner(db ethdb.KeyValueWriter, owner common.Hash) *StackTrie { + return &StackTrie{ + owner: owner, + nodeType: emptyNode, + db: db, + } +} + // NewFromBinary initialises a serialized stacktrie with the given db. func NewFromBinary(data []byte, db ethdb.KeyValueWriter) (*StackTrie, error) { var st StackTrie @@ -99,10 +110,12 @@ func (st *StackTrie) MarshalBinary() (data []byte, err error) { w = bufio.NewWriter(&b) ) if err := gob.NewEncoder(w).Encode(struct { - Nodetype uint8 + Owner common.Hash + NodeType uint8 Val []byte Key []byte }{ + st.owner, st.nodeType, st.val, st.key, @@ -133,12 +146,14 @@ func (st *StackTrie) UnmarshalBinary(data []byte) error { func (st *StackTrie) unmarshalBinary(r io.Reader) error { var dec struct { - Nodetype uint8 + Owner common.Hash + NodeType uint8 Val []byte Key []byte } gob.NewDecoder(r).Decode(&dec) - st.nodeType = dec.Nodetype + st.owner = dec.Owner + st.nodeType = dec.NodeType st.val = dec.Val st.key = dec.Key @@ -165,16 +180,16 @@ func (st *StackTrie) setDb(db ethdb.KeyValueWriter) { } } -func newLeaf(key, val []byte, db ethdb.KeyValueWriter) *StackTrie { - st := stackTrieFromPool(db) +func newLeaf(owner common.Hash, key, val []byte, db ethdb.KeyValueWriter) *StackTrie { + st := stackTrieFromPool(db, owner) st.nodeType = leafNode st.key = append(st.key, key...) st.val = val return st } -func newExt(key []byte, child *StackTrie, db ethdb.KeyValueWriter) *StackTrie { - st := stackTrieFromPool(db) +func newExt(owner common.Hash, key []byte, child *StackTrie, db ethdb.KeyValueWriter) *StackTrie { + st := stackTrieFromPool(db, owner) st.nodeType = extNode st.key = append(st.key, key...) st.children[0] = child @@ -207,6 +222,7 @@ func (st *StackTrie) Update(key, value []byte) { } func (st *StackTrie) Reset() { + st.owner = common.Hash{} st.db = nil st.key = st.key[:0] st.val = nil @@ -234,6 +250,7 @@ func (st *StackTrie) insert(key, value []byte) { switch st.nodeType { case branchNode: /* Branch */ idx := int(key[0]) + // Unresolve elder siblings for i := idx - 1; i >= 0; i-- { if st.children[i] != nil { @@ -243,12 +260,14 @@ func (st *StackTrie) insert(key, value []byte) { break } } + // Add new child if st.children[idx] == nil { - st.children[idx] = newLeaf(key[1:], value, st.db) + st.children[idx] = newLeaf(st.owner, key[1:], value, st.db) } else { st.children[idx].insert(key[1:], value) } + case extNode: /* Ext */ // Compare both key chunks and see where they differ diffidx := st.getDiffIndex(key) @@ -270,7 +289,7 @@ func (st *StackTrie) insert(key, value []byte) { // node directly. var n *StackTrie if diffidx < len(st.key)-1 { - n = newExt(st.key[diffidx+1:], st.children[0], st.db) + n = newExt(st.owner, st.key[diffidx+1:], st.children[0], st.db) } else { // Break on the last byte, no need to insert // an extension node: reuse the current node @@ -290,12 +309,12 @@ func (st *StackTrie) insert(key, value []byte) { // the common prefix is at least one byte // long, insert a new intermediate branch // node. - st.children[0] = stackTrieFromPool(st.db) + st.children[0] = stackTrieFromPool(st.db, st.owner) st.children[0].nodeType = branchNode p = st.children[0] } // Create a leaf for the inserted part - o := newLeaf(key[diffidx+1:], value, st.db) + o := newLeaf(st.owner, key[diffidx+1:], value, st.db) // Insert both child leaves where they belong: origIdx := st.key[diffidx] @@ -331,159 +350,157 @@ func (st *StackTrie) insert(key, value []byte) { // Convert current node into an ext, // and insert a child branch node. st.nodeType = extNode - st.children[0] = NewStackTrie(st.db) + st.children[0] = NewStackTrieWithOwner(st.db, st.owner) st.children[0].nodeType = branchNode p = st.children[0] } - // Create the two child leaves: the one containing the - // original value and the one containing the new value - // The child leave will be hashed directly in order to - // free up some memory. + // Create the two child leaves: one containing the original + // value and another containing the new value. The child leaf + // is hashed directly in order to free up some memory. origIdx := st.key[diffidx] - p.children[origIdx] = newLeaf(st.key[diffidx+1:], st.val, st.db) + p.children[origIdx] = newLeaf(st.owner, st.key[diffidx+1:], st.val, st.db) p.children[origIdx].hash() newIdx := key[diffidx] - p.children[newIdx] = newLeaf(key[diffidx+1:], value, st.db) + p.children[newIdx] = newLeaf(st.owner, key[diffidx+1:], value, st.db) // Finally, cut off the key part that has been passed // over to the children. st.key = st.key[:diffidx] st.val = nil + case emptyNode: /* Empty */ st.nodeType = leafNode st.key = key st.val = value + case hashedNode: panic("trying to insert into hash") + default: panic("invalid type") } } -// hash() hashes the node 'st' and converts it into 'hashedNode', if possible. -// Possible outcomes: +// hash converts st into a 'hashedNode', if possible. Possible outcomes: +// // 1. The rlp-encoded value was >= 32 bytes: // - Then the 32-byte `hash` will be accessible in `st.val`. // - And the 'st.type' will be 'hashedNode' // 2. The rlp-encoded value was < 32 bytes // - Then the <32 byte rlp-encoded value will be accessible in 'st.val'. // - And the 'st.type' will be 'hashedNode' AGAIN -// -// This method will also: -// set 'st.type' to hashedNode -// clear 'st.key' +// This method also sets 'st.type' to hashedNode, and clears 'st.key'. func (st *StackTrie) hash() { - /* Shortcut if node is already hashed */ - if st.nodeType == hashedNode { - return - } - // The 'hasher' is taken from a pool, but we don't actually - // claim an instance until all children are done with their hashing, - // and we actually need one - var h *hasher + h := newHasher(false) + defer returnHasherToPool(h) + + st.hashRec(h) +} + +func (st *StackTrie) hashRec(hasher *hasher) { + // The switch below sets this to the RLP-encoding of this node. + var encodedNode []byte switch st.nodeType { + case hashedNode: + return + + case emptyNode: + st.val = emptyRoot.Bytes() + st.key = st.key[:0] + st.nodeType = hashedNode + return + case branchNode: - var nodes [17]node + var nodes rawFullNode for i, child := range st.children { if child == nil { nodes[i] = nilValueNode continue } - child.hash() + + child.hashRec(hasher) if len(child.val) < 32 { nodes[i] = rawNode(child.val) } else { nodes[i] = hashNode(child.val) } - st.children[i] = nil // Reclaim mem from subtree + + // Release child back to pool. + st.children[i] = nil returnToPool(child) } - nodes[16] = nilValueNode - h = newHasher(false) - defer returnHasherToPool(h) - h.tmp.Reset() - if err := rlp.Encode(&h.tmp, nodes); err != nil { - panic(err) - } + + nodes.encode(hasher.encbuf) + encodedNode = hasher.encodedBytes() + case extNode: - st.children[0].hash() - h = newHasher(false) - defer returnHasherToPool(h) - h.tmp.Reset() - var valuenode node + st.children[0].hashRec(hasher) + + sz := hexToCompactInPlace(st.key) + n := rawShortNode{Key: st.key[:sz]} if len(st.children[0].val) < 32 { - valuenode = rawNode(st.children[0].val) + n.Val = rawNode(st.children[0].val) } else { - valuenode = hashNode(st.children[0].val) - } - n := struct { - Key []byte - Val node - }{ - Key: hexToCompact(st.key), - Val: valuenode, - } - if err := rlp.Encode(&h.tmp, n); err != nil { - panic(err) + n.Val = hashNode(st.children[0].val) } + + n.encode(hasher.encbuf) + encodedNode = hasher.encodedBytes() + + // Release child back to pool. returnToPool(st.children[0]) - st.children[0] = nil // Reclaim mem from subtree + st.children[0] = nil + case leafNode: - h = newHasher(false) - defer returnHasherToPool(h) - h.tmp.Reset() st.key = append(st.key, byte(16)) sz := hexToCompactInPlace(st.key) - n := [][]byte{st.key[:sz], st.val} - if err := rlp.Encode(&h.tmp, n); err != nil { - panic(err) - } - case emptyNode: - st.val = emptyRoot.Bytes() - st.key = st.key[:0] - st.nodeType = hashedNode - return + n := rawShortNode{Key: st.key[:sz], Val: valueNode(st.val)} + + n.encode(hasher.encbuf) + encodedNode = hasher.encodedBytes() + default: - panic("Invalid node type") + panic("invalid node type") } - st.key = st.key[:0] + st.nodeType = hashedNode - if len(h.tmp) < 32 { - st.val = common.CopyBytes(h.tmp) + st.key = st.key[:0] + if len(encodedNode) < 32 { + st.val = common.CopyBytes(encodedNode) return } + // Write the hash to the 'val'. We allocate a new val here to not mutate // input values - st.val = make([]byte, 32) - h.sha.Reset() - h.sha.Write(h.tmp) - h.sha.Read(st.val) + st.val = hasher.hashData(encodedNode) if st.db != nil { // TODO! Is it safe to Put the slice here? // Do all db implementations copy the value provided? - st.db.Put(st.val, h.tmp) + st.db.Put(st.val, encodedNode) } } -// Hash returns the hash of the current node +// Hash returns the hash of the current node. func (st *StackTrie) Hash() (h common.Hash) { - st.hash() - if len(st.val) != 32 { - // If the node's RLP isn't 32 bytes long, the node will not - // be hashed, and instead contain the rlp-encoding of the - // node. For the top level node, we need to force the hashing. - ret := make([]byte, 32) - h := newHasher(false) - defer returnHasherToPool(h) - h.sha.Reset() - h.sha.Write(st.val) - h.sha.Read(ret) - return common.BytesToHash(ret) + hasher := newHasher(false) + defer returnHasherToPool(hasher) + + st.hashRec(hasher) + if len(st.val) == 32 { + copy(h[:], st.val) + return h } - return common.BytesToHash(st.val) + + // If the node's RLP isn't 32 bytes long, the node will not + // be hashed, and instead contain the rlp-encoding of the + // node. For the top level node, we need to force the hashing. + hasher.sha.Reset() + hasher.sha.Write(st.val) + hasher.sha.Read(h[:]) + return h } // Commit will firstly hash the entrie trie if it's still not hashed @@ -493,23 +510,26 @@ func (st *StackTrie) Hash() (h common.Hash) { // // The associated database is expected, otherwise the whole commit // functionality should be disabled. -func (st *StackTrie) Commit() (common.Hash, error) { +func (st *StackTrie) Commit() (h common.Hash, err error) { if st.db == nil { return common.Hash{}, ErrCommitDisabled } - st.hash() - if len(st.val) != 32 { - // If the node's RLP isn't 32 bytes long, the node will not - // be hashed (and committed), and instead contain the rlp-encoding of the - // node. For the top level node, we need to force the hashing+commit. - ret := make([]byte, 32) - h := newHasher(false) - defer returnHasherToPool(h) - h.sha.Reset() - h.sha.Write(st.val) - h.sha.Read(ret) - st.db.Put(ret, st.val) - return common.BytesToHash(ret), nil + + hasher := newHasher(false) + defer returnHasherToPool(hasher) + + st.hashRec(hasher) + if len(st.val) == 32 { + copy(h[:], st.val) + return h, nil } - return common.BytesToHash(st.val), nil + + // If the node's RLP isn't 32 bytes long, the node will not + // be hashed (and committed), and instead contain the rlp-encoding of the + // node. For the top level node, we need to force the hashing+commit. + hasher.sha.Reset() + hasher.sha.Write(st.val) + hasher.sha.Read(h[:]) + st.db.Put(h[:], st.val) + return h, nil } diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go index 1d68a40688..ff52f60263 100644 --- a/trie/stacktrie_test.go +++ b/trie/stacktrie_test.go @@ -8,7 +8,7 @@ // // Much love to the original authors for their work. // ********** -// Copyright 2015 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -31,9 +31,9 @@ import ( "math/big" "testing" - "github.com/tenderly/coreth/ethdb/memorydb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/tenderly/coreth/ethdb/memorydb" ) func TestStackTrieInsertAndHash(t *testing.T) { @@ -198,7 +198,7 @@ func TestStackTrieInsertAndHash(t *testing.T) { func TestSizeBug(t *testing.T) { st := NewStackTrie(nil) - nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + nt := NewEmpty(NewDatabase(memorydb.New())) leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") @@ -213,7 +213,7 @@ func TestSizeBug(t *testing.T) { func TestEmptyBug(t *testing.T) { st := NewStackTrie(nil) - nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + nt := NewEmpty(NewDatabase(memorydb.New())) //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") @@ -239,7 +239,7 @@ func TestEmptyBug(t *testing.T) { func TestValLength56(t *testing.T) { st := NewStackTrie(nil) - nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + nt := NewEmpty(NewDatabase(memorydb.New())) //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") @@ -264,7 +264,7 @@ func TestValLength56(t *testing.T) { // which causes a lot of node-within-node. This case was found via fuzzing. func TestUpdateSmallNodes(t *testing.T) { st := NewStackTrie(nil) - nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + nt := NewEmpty(NewDatabase(memorydb.New())) kvs := []struct { K string V string @@ -292,7 +292,7 @@ func TestUpdateSmallNodes(t *testing.T) { func TestUpdateVariableKeys(t *testing.T) { t.SkipNow() st := NewStackTrie(nil) - nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + nt := NewEmpty(NewDatabase(memorydb.New())) kvs := []struct { K string V string @@ -353,7 +353,6 @@ func TestStacktrieNotModifyValues(t *testing.T) { if !bytes.Equal(have, want) { t.Fatalf("item %d, have %#x want %#x", i, have, want) } - } } @@ -362,7 +361,7 @@ func TestStacktrieNotModifyValues(t *testing.T) { func TestStacktrieSerialization(t *testing.T) { var ( st = NewStackTrie(nil) - nt, _ = New(common.Hash{}, NewDatabase(memorydb.New())) + nt = NewEmpty(NewDatabase(memorydb.New())) keyB = big.NewInt(1) keyDelta = big.NewInt(1) vals [][]byte diff --git a/trie/sync_test.go b/trie/sync_test.go index 79bc1f3ae9..ef4159f2c6 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -27,15 +27,17 @@ package trie import ( - "github.com/tenderly/coreth/ethdb/memorydb" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/ethdb/memorydb" ) // makeTestTrie create a sample test trie to test node-wise reconstruction. -func makeTestTrie() (*Database, *SecureTrie, map[string][]byte) { +func makeTestTrie() (*Database, *StateTrie, map[string][]byte) { // Create an empty trie triedb := NewDatabase(memorydb.New()) - trie, _ := NewSecure(common.Hash{}, triedb) + trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb) // Fill it with some arbitrary data content := make(map[string][]byte) @@ -56,8 +58,14 @@ func makeTestTrie() (*Database, *SecureTrie, map[string][]byte) { trie.Update(key, val) } } - trie.Commit(nil) - - // Return the generated trie + root, nodes, err := trie.Commit(false) + if err != nil { + panic(fmt.Errorf("failed to commit trie %v", err)) + } + if err := triedb.Update(NewWithNodeSet(nodes)); err != nil { + panic(fmt.Errorf("failed to commit db %v", err)) + } + // Re-create the trie based on the new state + trie, _ = NewSecure(common.Hash{}, root, triedb) return triedb, trie, content } diff --git a/trie/test_trie.go b/trie/test_trie.go index 35952cb4b0..55b760f18d 100644 --- a/trie/test_trie.go +++ b/trie/test_trie.go @@ -15,8 +15,8 @@ import ( "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/assert" + "github.com/tenderly/coreth/rlp" ) // GenerateTrie creates a trie with [numKeys] key-value pairs inside of [trieDB]. @@ -27,13 +27,14 @@ func GenerateTrie(t *testing.T, trieDB *Database, numKeys int, keySize int) (com if keySize < wrappers.LongLen+1 { t.Fatal("key size must be at least 9 bytes (8 bytes for uint64 and 1 random byte)") } - testTrie, err := New(common.Hash{}, trieDB) - assert.NoError(t, err) + testTrie := NewEmpty(trieDB) keys, values := FillTrie(t, numKeys, keySize, testTrie) // Commit the root to [trieDB] - root, _, err := testTrie.Commit(nil) + root, nodes, err := testTrie.Commit(false) + assert.NoError(t, err) + err = trieDB.Update(NewWithNodeSet(nodes)) assert.NoError(t, err) err = trieDB.Commit(root, false, nil) assert.NoError(t, err) @@ -72,11 +73,11 @@ func FillTrie(t *testing.T, numKeys int, keySize int, testTrie *Trie) ([][]byte, // AssertTrieConsistency ensures given trieDB [a] and [b] both have the same // non-empty trie at [root]. (all key/value pairs must be equal) func AssertTrieConsistency(t testing.TB, root common.Hash, a, b *Database, onLeaf func(key, val []byte) error) { - trieA, err := New(root, a) + trieA, err := New(common.Hash{}, root, a) if err != nil { t.Fatalf("error creating trieA, root=%s, err=%v", root, err) } - trieB, err := New(root, b) + trieB, err := New(common.Hash{}, root, b) if err != nil { t.Fatalf("error creating trieB, root=%s, err=%v", root, err) } @@ -106,7 +107,7 @@ func AssertTrieConsistency(t testing.TB, root common.Hash, a, b *Database, onLea func CorruptTrie(t *testing.T, trieDB *Database, root common.Hash, n int) { batch := trieDB.DiskDB().NewBatch() // next delete some trie nodes - tr, err := New(root, trieDB) + tr, err := New(common.Hash{}, root, trieDB) if err != nil { t.Fatal(err) } @@ -144,7 +145,7 @@ func FillAccounts( accounts = make(map[*keystore.Key]*types.StateAccount, numAccounts) ) - tr, err := NewSecure(root, trieDB) + tr, err := NewStateTrie(common.Hash{}, root, trieDB) if err != nil { t.Fatalf("error opening trie: %v", err) } @@ -160,7 +161,7 @@ func FillAccounts( acc = onAccount(t, i, acc) } - accBytes, err := rlp.EncodeToBytes(acc) + accBytes, err := rlp.EncodeToBytes(&acc) if err != nil { t.Fatalf("failed to rlp encode account: %v", err) } @@ -175,10 +176,13 @@ func FillAccounts( accounts[key] = &acc } - newRoot, _, err := tr.Commit(nil) + newRoot, nodes, err := tr.Commit(false) if err != nil { t.Fatalf("error committing trie: %v", err) } + if err := trieDB.Update(NewWithNodeSet(nodes)); err != nil { + t.Fatalf("error updating trieDB: %v", err) + } if err := trieDB.Commit(newRoot, false, nil); err != nil { t.Fatalf("error committing trieDB: %v", err) } diff --git a/trie/trie.go b/trie/trie.go index 9ddf758b0c..c6433bd692 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -31,12 +31,9 @@ import ( "bytes" "errors" "fmt" - "sync" - "github.com/tenderly/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) var ( @@ -47,31 +44,42 @@ var ( // LeafCallback is a callback type invoked when a trie operation reaches a leaf // node. // -// The paths is a path tuple identifying a particular trie node either in a single -// trie (account) or a layered trie (account -> storage). Each path in the tuple +// The keys is a path tuple identifying a particular trie node either in a single +// trie (account) or a layered trie (account -> storage). Each key in the tuple // is in the raw format(32 bytes). // -// The hexpath is a composite hexary path identifying the trie node. All the key +// The path is a composite hexary path identifying the trie node. All the key // bytes are converted to the hexary nibbles and composited with the parent path // if the trie node is in a layered trie. // // It's used by state sync and commit to allow handling external references // between account and storage tries. And also it's used in the state healing // for extracting the raw states(leaf nodes) with corresponding paths. -type LeafCallback func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error +type LeafCallback func(keys [][]byte, path []byte, leaf []byte, parent common.Hash, parentPath []byte) error -// Trie is a Merkle Patricia Trie. -// The zero value is an empty trie with no database. -// Use New to create a trie that sits on top of a database. +// Trie is a Merkle Patricia Trie. Use New to create a trie that sits on +// top of a database. Whenever trie performs a commit operation, the generated +// nodes will be gathered and returned in a set. Once the trie is committed, +// it's not usable anymore. Callers have to re-create the trie with new root +// based on the updated trie database. // // Trie is not safe for concurrent use. type Trie struct { - db *Database - root node - // Keep track of the number leafs which have been inserted since the last + root node + owner common.Hash + + // Keep track of the number leaves which have been inserted since the last // hashing operation. This number will not directly map to the number of - // actually unhashed nodes + // actually unhashed nodes. unhashed int + + // db is the handler trie can retrieve nodes from. It's + // only for reading purpose and not available for writing. + db *Database + + // tracer is the tool to track the trie changes. + // It will be reset after each commit operation. + tracer *tracer } // newFlag returns the cache flag value for a newly created node. @@ -79,18 +87,29 @@ func (t *Trie) newFlag() nodeFlag { return nodeFlag{dirty: true} } -// New creates a trie with an existing root node from db. +// Copy returns a copy of Trie. +func (t *Trie) Copy() *Trie { + return &Trie{ + root: t.root, + owner: t.owner, + unhashed: t.unhashed, + db: t.db, + tracer: t.tracer.copy(), + } +} + +// New creates a trie with an existing root node from db and an assigned +// owner for storage proximity. // // If root is the zero hash or the sha3 hash of an empty string, the // trie is initially empty and does not require a database. Otherwise, // New will panic if db is nil and returns a MissingNodeError if root does // not exist in the database. Accessing the trie loads nodes from db on demand. -func New(root common.Hash, db *Database) (*Trie, error) { - if db == nil { - panic("trie.New called without a database") - } +func New(owner common.Hash, root common.Hash, db *Database) (*Trie, error) { trie := &Trie{ - db: db, + owner: owner, + db: db, + //tracer: newTracer(), } if root != (common.Hash{}) && root != emptyRoot { rootnode, err := trie.resolveHash(root[:], nil) @@ -102,6 +121,12 @@ func New(root common.Hash, db *Database) (*Trie, error) { return trie, nil } +// NewEmpty is a shortcut to create empty tree. It's mostly used in tests. +func NewEmpty(db *Database) *Trie { + tr, _ := New(common.Hash{}, common.Hash{}, db) + return tr +} + // NodeIterator returns an iterator that returns nodes of the trie. Iteration starts at // the key after the given start key. func (t *Trie) NodeIterator(start []byte) NodeIterator { @@ -254,14 +279,6 @@ func (t *Trie) Update(key, value []byte) { } } -func (t *Trie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { - data, err := rlp.EncodeToBytes(acc) - if err != nil { - return fmt.Errorf("can't encode object at %x: %w", key[:], err) - } - return t.TryUpdate(key, data) -} - // TryUpdate associates key with value in the trie. Subsequent calls to // Get will return value. If value has length zero, any existing value // is deleted from the trie and calls to Get will return nil. @@ -271,6 +288,12 @@ func (t *Trie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { // // If a node was not found in the database, a MissingNodeError is returned. func (t *Trie) TryUpdate(key, value []byte) error { + return t.tryUpdate(key, value) +} + +// tryUpdate expects an RLP-encoded value and performs the core function +// for TryUpdate and TryUpdateAccount. +func (t *Trie) tryUpdate(key, value []byte) error { t.unhashed++ k := keybytesToHex(key) if len(value) != 0 { @@ -323,7 +346,12 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error if matchlen == 0 { return true, branch, nil } - // Otherwise, replace it with a short node leading up to the branch. + // New branch node is created as a child of the original short node. + // Track the newly inserted node in the tracer. The node identifier + // passed is the path from the root node. + t.tracer.onInsert(append(prefix, key[:matchlen]...)) + + // Replace it with a short node leading up to the branch. return true, &shortNode{key[:matchlen], branch, t.newFlag()}, nil case *fullNode: @@ -337,6 +365,11 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error return true, n, nil case nil: + // New short node is created and track it in the tracer. The node identifier + // passed is the path from the root node. Note the valueNode won't be tracked + // since it's always embedded in its parent. + t.tracer.onInsert(prefix) + return true, &shortNode{key, value, t.newFlag()}, nil case hashNode: @@ -389,6 +422,11 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { return false, n, nil // don't replace n on mismatch } if matchlen == len(key) { + // The matched short node is deleted entirely and track + // it in the deletion set. The same the valueNode doesn't + // need to be tracked at all since it's always embedded. + t.tracer.onDelete(prefix) + return true, nil, nil // remove n entirely for whole matches } // The key is longer than n.Key. Remove the remaining suffix @@ -401,6 +439,10 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { } switch child := child.(type) { case *shortNode: + // The child shortNode is merged into its parent, track + // is deleted as well. + t.tracer.onDelete(append(prefix, n.Key...)) + // Deleting from the subtrie reduced it to another // short node. Merge the nodes to avoid creating a // shortNode{..., shortNode{...}}. Use concat (which @@ -457,11 +499,16 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { // shortNode{..., shortNode{...}}. Since the entry // might not be loaded yet, resolve it just for this // check. - cnode, err := t.resolve(n.Children[pos], prefix) + cnode, err := t.resolve(n.Children[pos], append(prefix, byte(pos))) if err != nil { return false, nil, err } if cnode, ok := cnode.(*shortNode); ok { + // Replace the entire full node with the short node. + // Mark the original short node as deleted since the + // value is embedded into the parent now. + t.tracer.onDelete(append(prefix, byte(pos))) + k := append([]byte{byte(pos)}, cnode.Key...) return true, &shortNode{k, cnode.Val, t.newFlag()}, nil } @@ -512,12 +559,25 @@ func (t *Trie) resolve(n node, prefix []byte) (node, error) { return n, nil } +// resolveHash loads node from the underlying database with the provided +// node hash and path prefix. func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) { hash := common.BytesToHash(n) if node := t.db.EncodedNode(hash); node != nil { return node, nil } - return nil, &MissingNodeError{NodeHash: hash, Path: prefix} + return nil, &MissingNodeError{Owner: t.owner, NodeHash: hash, Path: prefix} +} + +// resolveHash loads rlp-encoded node blob from the underlying database +// with the provided node hash and path prefix. +func (t *Trie) resolveBlob(n hashNode, prefix []byte) ([]byte, error) { + hash := common.BytesToHash(n) + blob, _ := t.db.RawNode(hash) + if len(blob) != 0 { + return blob, nil + } + return nil, &MissingNodeError{Owner: t.owner, NodeHash: hash, Path: prefix} } // Hash returns the root hash of the trie. It does not write to the @@ -528,51 +588,37 @@ func (t *Trie) Hash() common.Hash { return common.BytesToHash(hash.(hashNode)) } -// Commit writes all nodes to the trie's memory database, tracking the internal -// and external (for account tries) references. -func (t *Trie) Commit(onleaf LeafCallback) (common.Hash, int, error) { - if t.db == nil { - panic("commit called on trie with nil database") - } +// Commit collects all dirty nodes in the trie and replace them with the +// corresponding node hash. All collected nodes(including dirty leaves if +// collectLeaf is true) will be encapsulated into a nodeset for return. +// The returned nodeset can be nil if the trie is clean(nothing to commit). +// Once the trie is committed, it's not usable anymore. A new trie must +// be created with new root and updated trie database for following usage +func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) { + defer t.tracer.reset() + if t.root == nil { - return emptyRoot, 0, nil + return emptyRoot, nil, nil } // Derive the hash for all dirty nodes first. We hold the assumption // in the following procedure that all nodes are hashed. rootHash := t.Hash() - h := newCommitter() - defer returnCommitterToPool(h) - - // Do a quick check if we really need to commit, before we spin - // up goroutines. This can happen e.g. if we load a trie for reading storage - // values, but don't write to it. - if _, dirty := t.root.cache(); !dirty { - return rootHash, 0, nil - } - var wg sync.WaitGroup - if onleaf != nil { - h.onleaf = onleaf - h.leafCh = make(chan *leaf, leafChanSize) - wg.Add(1) - go func() { - defer wg.Done() - h.commitLoop(t.db) - }() - } - newRoot, committed, err := h.Commit(t.root, t.db) - if onleaf != nil { - // The leafch is created in newCommitter if there was an onleaf callback - // provided. The commitLoop only _reads_ from it, and the commit - // operation was the sole writer. Therefore, it's safe to close this - // channel here. - close(h.leafCh) - wg.Wait() + + // Do a quick check if we really need to commit. This can happen e.g. + // if we load a trie for reading storage values, but don't write to it. + if hashedNode, dirty := t.root.cache(); !dirty { + // Replace the root node with the origin hash in order to + // ensure all resolved nodes are dropped after the commit. + t.root = hashedNode + return rootHash, nil, nil } + h := newCommitter(t.owner, collectLeaf) + newRoot, nodes, err := h.Commit(t.root) if err != nil { - return common.Hash{}, 0, err + return common.Hash{}, nil, err } t.root = newRoot - return rootHash, committed, nil + return rootHash, nodes, nil } // hashRoot calculates the root hash of the given trie @@ -591,5 +637,8 @@ func (t *Trie) hashRoot() (node, node, error) { // Reset drops the referenced root node and cleans all internal state. func (t *Trie) Reset() { t.root = nil + t.owner = common.Hash{} t.unhashed = 0 + //t.db = nil + t.tracer.reset() } diff --git a/trie/trie_test.go b/trie/trie_test.go index 10aa01bd8e..3f0c7ec1ce 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -38,13 +38,13 @@ import ( "testing" "testing/quick" - "github.com/tenderly/coreth/core/rawdb" - "github.com/tenderly/coreth/ethdb" - "github.com/tenderly/coreth/ethdb/memorydb" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" + "github.com/tenderly/coreth/core/rawdb" + "github.com/tenderly/coreth/ethdb" + "github.com/tenderly/coreth/ethdb/memorydb" + "github.com/tenderly/coreth/rlp" "golang.org/x/crypto/sha3" ) @@ -53,14 +53,8 @@ func init() { spew.Config.DisableMethods = false } -// Used for testing -func newEmpty() *Trie { - trie, _ := New(common.Hash{}, NewDatabase(memorydb.New())) - return trie -} - func TestEmptyTrie(t *testing.T) { - var trie Trie + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) res := trie.Hash() exp := emptyRoot if res != exp { @@ -69,7 +63,7 @@ func TestEmptyTrie(t *testing.T) { } func TestNull(t *testing.T) { - var trie Trie + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) key := make([]byte, 32) value := []byte("test") trie.Update(key, value) @@ -79,7 +73,7 @@ func TestNull(t *testing.T) { } func TestMissingRoot(t *testing.T) { - trie, err := New(common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), NewDatabase(memorydb.New())) + trie, err := New(common.Hash{}, common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), NewDatabase(memorydb.New())) if trie != nil { t.Error("New returned non-nil trie for invalid root") } @@ -95,35 +89,36 @@ func testMissingNode(t *testing.T, memonly bool) { diskdb := memorydb.New() triedb := NewDatabase(diskdb) - trie, _ := New(common.Hash{}, triedb) + trie := NewEmpty(triedb) updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer") updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf") - root, _, _ := trie.Commit(nil) + root, nodes, _ := trie.Commit(false) + triedb.Update(NewWithNodeSet(nodes)) if !memonly { triedb.Commit(root, true, nil) } - trie, _ = New(root, triedb) + trie, _ = New(common.Hash{}, root, triedb) _, err := trie.TryGet([]byte("120000")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(root, triedb) + trie, _ = New(common.Hash{}, root, triedb) _, err = trie.TryGet([]byte("120099")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(root, triedb) + trie, _ = New(common.Hash{}, root, triedb) _, err = trie.TryGet([]byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(root, triedb) + trie, _ = New(common.Hash{}, root, triedb) err = trie.TryUpdate([]byte("120099"), []byte("zxcvzxcvzxcvzxcvzxcvzxcvzxcvzxcv")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(root, triedb) + trie, _ = New(common.Hash{}, root, triedb) err = trie.TryDelete([]byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) @@ -136,27 +131,27 @@ func testMissingNode(t *testing.T, memonly bool) { diskdb.Delete(hash[:]) } - trie, _ = New(root, triedb) + trie, _ = New(common.Hash{}, root, triedb) _, err = trie.TryGet([]byte("120000")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } - trie, _ = New(root, triedb) + trie, _ = New(common.Hash{}, root, triedb) _, err = trie.TryGet([]byte("120099")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } - trie, _ = New(root, triedb) + trie, _ = New(common.Hash{}, root, triedb) _, err = trie.TryGet([]byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(root, triedb) + trie, _ = New(common.Hash{}, root, triedb) err = trie.TryUpdate([]byte("120099"), []byte("zxcv")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } - trie, _ = New(root, triedb) + trie, _ = New(common.Hash{}, root, triedb) err = trie.TryDelete([]byte("123456")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) @@ -164,7 +159,7 @@ func testMissingNode(t *testing.T, memonly bool) { } func TestInsert(t *testing.T) { - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) updateString(trie, "doe", "reindeer") updateString(trie, "dog", "puppy") @@ -176,11 +171,11 @@ func TestInsert(t *testing.T) { t.Errorf("case 1: exp %x got %x", exp, root) } - trie = newEmpty() + trie = NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab") - root, _, err := trie.Commit(nil) + root, _, err := trie.Commit(false) if err != nil { t.Fatalf("commit error: %v", err) } @@ -190,7 +185,8 @@ func TestInsert(t *testing.T) { } func TestGet(t *testing.T) { - trie := newEmpty() + db := NewDatabase(rawdb.NewMemoryDatabase()) + trie := NewEmpty(db) updateString(trie, "doe", "reindeer") updateString(trie, "dog", "puppy") updateString(trie, "dogglesworth", "cat") @@ -200,21 +196,21 @@ func TestGet(t *testing.T) { if !bytes.Equal(res, []byte("puppy")) { t.Errorf("expected puppy got %x", res) } - unknown := getString(trie, "unknown") if unknown != nil { t.Errorf("expected nil got %x", unknown) } - if i == 1 { return } - trie.Commit(nil) + root, nodes, _ := trie.Commit(false) + db.Update(NewWithNodeSet(nodes)) + trie, _ = New(common.Hash{}, root, db) } } func TestDelete(t *testing.T) { - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) vals := []struct{ k, v string }{ {"do", "verb"}, {"ether", "wookiedoo"}, @@ -241,7 +237,7 @@ func TestDelete(t *testing.T) { } func TestEmptyValues(t *testing.T) { - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) vals := []struct{ k, v string }{ {"do", "verb"}, @@ -265,7 +261,8 @@ func TestEmptyValues(t *testing.T) { } func TestReplication(t *testing.T) { - trie := newEmpty() + triedb := NewDatabase(rawdb.NewMemoryDatabase()) + trie := NewEmpty(triedb) vals := []struct{ k, v string }{ {"do", "verb"}, {"ether", "wookiedoo"}, @@ -278,13 +275,14 @@ func TestReplication(t *testing.T) { for _, val := range vals { updateString(trie, val.k, val.v) } - exp, _, err := trie.Commit(nil) + exp, nodes, err := trie.Commit(false) if err != nil { t.Fatalf("commit error: %v", err) } + triedb.Update(NewWithNodeSet(nodes)) // create a new trie on top of the database and check that lookups work. - trie2, err := New(exp, trie.db) + trie2, err := New(common.Hash{}, exp, triedb) if err != nil { t.Fatalf("can't recreate trie at %x: %v", exp, err) } @@ -293,7 +291,7 @@ func TestReplication(t *testing.T) { t.Errorf("trie2 doesn't have %q => %q", kv.k, kv.v) } } - hash, _, err := trie2.Commit(nil) + hash, nodes, err := trie2.Commit(false) if err != nil { t.Fatalf("commit error: %v", err) } @@ -301,6 +299,14 @@ func TestReplication(t *testing.T) { t.Errorf("root failure. expected %x got %x", exp, hash) } + // recreate the trie after commit + if nodes != nil { + triedb.Update(NewWithNodeSet(nodes)) + } + trie2, err = New(common.Hash{}, hash, triedb) + if err != nil { + t.Fatalf("can't recreate trie at %x: %v", exp, err) + } // perform some insertions on the new trie. vals2 := []struct{ k, v string }{ {"do", "verb"}, @@ -322,7 +328,7 @@ func TestReplication(t *testing.T) { } func TestLargeValue(t *testing.T) { - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) trie.Update([]byte("key1"), []byte{99, 99, 99, 99}) trie.Update([]byte("key2"), bytes.Repeat([]byte{1}, 32)) trie.Hash() @@ -359,7 +365,6 @@ func TestRandomCases(t *testing.T) { {op: 1, key: common.Hex2Bytes("fd"), value: common.Hex2Bytes("")}, // step 25 } runRandTest(rt) - } // randTest performs random trie operations. @@ -377,10 +382,10 @@ const ( opUpdate = iota opDelete opGet - opCommit opHash - opReset + opCommit opItercheckhash + opNodeDiff opMax // boundary value, not an actual op ) @@ -415,14 +420,17 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value { } func runRandTest(rt randTest) bool { - triedb := NewDatabase(memorydb.New()) - - tr, _ := New(common.Hash{}, triedb) - values := make(map[string]string) // tracks content of the trie + var ( + triedb = NewDatabase(memorydb.New()) + tr = NewEmpty(triedb) + values = make(map[string]string) // tracks content of the trie + origTrie = NewEmpty(triedb) + ) + tr.tracer = newTracer() for i, step := range rt { - fmt.Printf("{op: %d, key: common.Hex2Bytes(\"%x\"), value: common.Hex2Bytes(\"%x\")}, // step %d\n", - step.op, step.key, step.value, i) + // fmt.Printf("{op: %d, key: common.Hex2Bytes(\"%x\"), value: common.Hex2Bytes(\"%x\")}, // step %d\n", + // step.op, step.key, step.value, i) switch step.op { case opUpdate: tr.Update(step.key, step.value) @@ -434,26 +442,30 @@ func runRandTest(rt randTest) bool { v := tr.Get(step.key) want := values[string(step.key)] if string(v) != want { - rt[i].err = fmt.Errorf("mismatch for key 0x%x, got 0x%x want 0x%x", step.key, v, want) + rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want) } - case opCommit: - _, _, rt[i].err = tr.Commit(nil) case opHash: tr.Hash() - case opReset: - hash, _, err := tr.Commit(nil) + case opCommit: + hash, nodes, err := tr.Commit(false) if err != nil { rt[i].err = err return false } - newtr, err := New(hash, triedb) + if nodes != nil { + triedb.Update(NewWithNodeSet(nodes)) + } + newtr, err := New(common.Hash{}, hash, triedb) if err != nil { rt[i].err = err return false } tr = newtr + tr.tracer = newTracer() + + origTrie = tr.Copy() case opItercheckhash: - checktr, _ := New(common.Hash{}, triedb) + checktr := NewEmpty(triedb) it := NewIterator(tr.NodeIterator(nil)) for it.Next() { checktr.Update(it.Key, it.Value) @@ -461,6 +473,59 @@ func runRandTest(rt randTest) bool { if tr.Hash() != checktr.Hash() { rt[i].err = fmt.Errorf("hash mismatch in opItercheckhash") } + case opNodeDiff: + var ( + inserted = tr.tracer.insertList() + deleted = tr.tracer.deleteList() + origIter = origTrie.NodeIterator(nil) + curIter = tr.NodeIterator(nil) + origSeen = make(map[string]struct{}) + curSeen = make(map[string]struct{}) + ) + for origIter.Next(true) { + if origIter.Leaf() { + continue + } + origSeen[string(origIter.Path())] = struct{}{} + } + for curIter.Next(true) { + if curIter.Leaf() { + continue + } + curSeen[string(curIter.Path())] = struct{}{} + } + var ( + insertExp = make(map[string]struct{}) + deleteExp = make(map[string]struct{}) + ) + for path := range curSeen { + _, present := origSeen[path] + if !present { + insertExp[path] = struct{}{} + } + } + for path := range origSeen { + _, present := curSeen[path] + if !present { + deleteExp[path] = struct{}{} + } + } + if len(insertExp) != len(inserted) { + rt[i].err = fmt.Errorf("insert set mismatch") + } + if len(deleteExp) != len(deleted) { + rt[i].err = fmt.Errorf("delete set mismatch") + } + for _, insert := range inserted { + if _, present := insertExp[string(insert)]; !present { + rt[i].err = fmt.Errorf("missing inserted node") + } + } + for _, del := range deleted { + if _, present := deleteExp[string(del)]; !present { + rt[i].err = fmt.Errorf("missing deleted node") + } + } } // Abort the test on error. if rt[i].err != nil { @@ -479,28 +544,21 @@ func TestRandom(t *testing.T) { } } -func BenchmarkGet(b *testing.B) { benchGet(b, false) } -func BenchmarkGetDB(b *testing.B) { benchGet(b, true) } +func BenchmarkGet(b *testing.B) { benchGet(b) } func BenchmarkUpdateBE(b *testing.B) { benchUpdate(b, binary.BigEndian) } func BenchmarkUpdateLE(b *testing.B) { benchUpdate(b, binary.LittleEndian) } const benchElemCount = 20000 -func benchGet(b *testing.B, commit bool) { - trie := new(Trie) - if commit { - tmpdb := tempDB() - trie, _ = New(common.Hash{}, tmpdb) - } +func benchGet(b *testing.B) { + triedb := NewDatabase(rawdb.NewMemoryDatabase()) + trie := NewEmpty(triedb) k := make([]byte, 32) for i := 0; i < benchElemCount; i++ { binary.LittleEndian.PutUint64(k, uint64(i)) trie.Update(k, k) } binary.LittleEndian.PutUint64(k, benchElemCount/2) - if commit { - trie.Commit(nil) - } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -510,7 +568,7 @@ func benchGet(b *testing.B, commit bool) { } func benchUpdate(b *testing.B, e binary.ByteOrder) *Trie { - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) k := make([]byte, 32) b.ReportAllocs() for i := 0; i < b.N; i++ { @@ -540,7 +598,7 @@ func BenchmarkHash(b *testing.B) { // entries, then adding N more. addresses, accounts := makeAccounts(2 * b.N) // Insert the accounts into the trie and hash it - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) i := 0 for ; i < len(addresses)/2; i++ { trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i]) @@ -568,23 +626,17 @@ type account struct { // insert into the trie before measuring the hashing. func BenchmarkCommitAfterHash(b *testing.B) { b.Run("no-onleaf", func(b *testing.B) { - benchmarkCommitAfterHash(b, nil) + benchmarkCommitAfterHash(b, false) }) - var a account - onleaf := func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error { - rlp.DecodeBytes(leaf, &a) - return nil - } b.Run("with-onleaf", func(b *testing.B) { - benchmarkCommitAfterHash(b, onleaf) + benchmarkCommitAfterHash(b, true) }) } -func benchmarkCommitAfterHash(b *testing.B, onleaf LeafCallback) { +func benchmarkCommitAfterHash(b *testing.B, collectLeaf bool) { // Make the random benchmark deterministic - addresses, accounts := makeAccounts(b.N) - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) for i := 0; i < len(addresses); i++ { trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i]) } @@ -592,13 +644,13 @@ func benchmarkCommitAfterHash(b *testing.B, onleaf LeafCallback) { trie.Hash() b.ResetTimer() b.ReportAllocs() - trie.Commit(onleaf) + trie.Commit(collectLeaf) } func TestTinyTrie(t *testing.T) { // Create a realistic account trie to hash _, accounts := makeAccounts(5) - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001337"), accounts[3]) if exp, root := common.HexToHash("8c6a85a4d9fda98feff88450299e574e5378e32391f75a055d470ac0653f1005"), trie.Hash(); exp != root { t.Errorf("1: got %x, exp %x", root, exp) @@ -611,7 +663,7 @@ func TestTinyTrie(t *testing.T) { if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root { t.Errorf("3: got %x, exp %x", root, exp) } - checktr, _ := New(common.Hash{}, trie.db) + checktr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) it := NewIterator(trie.NodeIterator(nil)) for it.Next() { checktr.Update(it.Key, it.Value) @@ -624,19 +676,19 @@ func TestTinyTrie(t *testing.T) { func TestCommitAfterHash(t *testing.T) { // Create a realistic account trie to hash addresses, accounts := makeAccounts(1000) - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) for i := 0; i < len(addresses); i++ { trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i]) } // Insert the accounts into the trie and hash it trie.Hash() - trie.Commit(nil) + trie.Commit(false) root := trie.Hash() exp := common.HexToHash("72f9d3f3fe1e1dd7b8936442e7642aef76371472d94319900790053c493f3fe6") if exp != root { t.Errorf("got %x, exp %x", root, exp) } - root, _, _ = trie.Commit(nil) + root, _, _ = trie.Commit(false) if exp != root { t.Errorf("got %x, exp %x", root, exp) } @@ -684,6 +736,7 @@ func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement func (s *spongeDb) Get(key []byte) ([]byte, error) { return nil, errors.New("no such elem") } func (s *spongeDb) Delete(key []byte) error { panic("implement me") } func (s *spongeDb) NewBatch() ethdb.Batch { return &spongeBatch{s} } +func (s *spongeDb) NewBatchWithSize(size int) ethdb.Batch { return &spongeBatch{s} } func (s *spongeDb) Stat(property string) (string, error) { panic("implement me") } func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") } func (s *spongeDb) Close() error { return nil } @@ -735,7 +788,7 @@ func TestCommitSequence(t *testing.T) { // This spongeDb is used to check the sequence of disk-db-writes s := &spongeDb{sponge: sha3.NewLegacyKeccak256()} db := NewDatabase(s) - trie, _ := New(common.Hash{}, db) + trie := NewEmpty(db) // Another sponge is used to check the callback-sequence callbackSponge := sha3.NewLegacyKeccak256() // Fill the trie with elements @@ -743,7 +796,8 @@ func TestCommitSequence(t *testing.T) { trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i]) } // Flush trie -> database - root, _, _ := trie.Commit(nil) + root, nodes, _ := trie.Commit(false) + db.Update(NewWithNodeSet(nodes)) // Flush memdb -> disk (sponge) db.Commit(root, false, func(c common.Hash) { // And spongify the callback-order @@ -777,7 +831,7 @@ func TestCommitSequenceRandomBlobs(t *testing.T) { // This spongeDb is used to check the sequence of disk-db-writes s := &spongeDb{sponge: sha3.NewLegacyKeccak256()} db := NewDatabase(s) - trie, _ := New(common.Hash{}, db) + trie := NewEmpty(db) // Another sponge is used to check the callback-sequence callbackSponge := sha3.NewLegacyKeccak256() // Fill the trie with elements @@ -795,7 +849,8 @@ func TestCommitSequenceRandomBlobs(t *testing.T) { trie.Update(key, val) } // Flush trie -> database - root, _, _ := trie.Commit(nil) + root, nodes, _ := trie.Commit(false) + db.Update(NewWithNodeSet(nodes)) // Flush memdb -> disk (sponge) db.Commit(root, false, func(c common.Hash) { // And spongify the callback-order @@ -816,12 +871,12 @@ func TestCommitSequenceStackTrie(t *testing.T) { // This spongeDb is used to check the sequence of disk-db-writes s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"} db := NewDatabase(s) - trie, _ := New(common.Hash{}, db) + trie := NewEmpty(db) // Another sponge is used for the stacktrie commits stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"} stTrie := NewStackTrie(stackTrieSponge) // Fill the trie with elements - for i := 1; i < count; i++ { + for i := 0; i < count; i++ { // For the stack trie, we need to do inserts in proper order key := make([]byte, 32) binary.BigEndian.PutUint64(key, uint64(i)) @@ -837,8 +892,9 @@ func TestCommitSequenceStackTrie(t *testing.T) { stTrie.TryUpdate(key, val) } // Flush trie -> database - root, _, _ := trie.Commit(nil) + root, nodes, _ := trie.Commit(false) // Flush memdb -> disk (sponge) + db.Update(NewWithNodeSet(nodes)) db.Commit(root, false, nil) // And flush stacktrie -> disk stRoot, err := stTrie.Commit() @@ -872,7 +928,7 @@ func TestCommitSequenceStackTrie(t *testing.T) { func TestCommitSequenceSmallRoot(t *testing.T) { s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"} db := NewDatabase(s) - trie, _ := New(common.Hash{}, db) + trie := NewEmpty(db) // Another sponge is used for the stacktrie commits stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"} stTrie := NewStackTrie(stackTrieSponge) @@ -882,8 +938,9 @@ func TestCommitSequenceSmallRoot(t *testing.T) { trie.TryUpdate(key, []byte{0x1}) stTrie.TryUpdate(key, []byte{0x1}) // Flush trie -> database - root, _, _ := trie.Commit(nil) + root, nodes, _ := trie.Commit(false) // Flush memdb -> disk (sponge) + db.Update(NewWithNodeSet(nodes)) db.Commit(root, false, nil) // And flush stacktrie -> disk stRoot, err := stTrie.Commit() @@ -893,7 +950,8 @@ func TestCommitSequenceSmallRoot(t *testing.T) { if stRoot != root { t.Fatalf("root wrong, got %x exp %x", stRoot, root) } - fmt.Printf("root: %x\n", stRoot) + + t.Logf("root: %x\n", stRoot) if got, exp := stackTrieSponge.sponge.Sum(nil), s.sponge.Sum(nil); !bytes.Equal(got, exp) { t.Fatalf("test, disk write sequence wrong:\ngot %x exp %x\n", got, exp) } @@ -944,7 +1002,7 @@ func BenchmarkHashFixedSize(b *testing.B) { func benchmarkHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) { b.ReportAllocs() - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) for i := 0; i < len(addresses); i++ { trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i]) } @@ -995,14 +1053,14 @@ func BenchmarkCommitAfterHashFixedSize(b *testing.B) { func benchmarkCommitAfterHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) { b.ReportAllocs() - trie := newEmpty() + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) for i := 0; i < len(addresses); i++ { trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i]) } // Insert the accounts into the trie and hash it trie.Hash() b.StartTimer() - trie.Commit(nil) + trie.Commit(false) b.StopTimer() } @@ -1047,22 +1105,19 @@ func BenchmarkDerefRootFixedSize(b *testing.B) { func benchmarkDerefRootFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) { b.ReportAllocs() - trie := newEmpty() + triedb := NewDatabase(rawdb.NewMemoryDatabase()) + trie := NewEmpty(triedb) for i := 0; i < len(addresses); i++ { trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i]) } h := trie.Hash() - trie.Commit(nil) + _, nodes, _ := trie.Commit(false) + triedb.Update(NewWithNodeSet(nodes)) b.StartTimer() - trie.db.Dereference(h) + triedb.Dereference(h) b.StopTimer() } -func tempDB() *Database { - memdb := rawdb.NewMemoryDatabase() - return NewDatabase(memdb) -} - func getString(trie *Trie, k string) []byte { return trie.Get([]byte(k)) } diff --git a/trie/util_test.go b/trie/util_test.go new file mode 100644 index 0000000000..6c924e7fed --- /dev/null +++ b/trie/util_test.go @@ -0,0 +1,134 @@ +// (c) 2022, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/core/rawdb" +) + +// Tests if the trie diffs are tracked correctly. +func TestTrieTracer(t *testing.T) { + db := NewDatabase(rawdb.NewMemoryDatabase()) + trie := NewEmpty(db) + trie.tracer = newTracer() + + // Insert a batch of entries, all the nodes should be marked as inserted + vals := []struct{ k, v string }{ + {"do", "verb"}, + {"ether", "wookiedoo"}, + {"horse", "stallion"}, + {"shaman", "horse"}, + {"doge", "coin"}, + {"dog", "puppy"}, + {"somethingveryoddindeedthis is", "myothernodedata"}, + } + for _, val := range vals { + trie.Update([]byte(val.k), []byte(val.v)) + } + trie.Hash() + + seen := make(map[string]struct{}) + it := trie.NodeIterator(nil) + for it.Next(true) { + if it.Leaf() { + continue + } + seen[string(it.Path())] = struct{}{} + } + inserted := trie.tracer.insertList() + if len(inserted) != len(seen) { + t.Fatalf("Unexpected inserted node tracked want %d got %d", len(seen), len(inserted)) + } + for _, k := range inserted { + _, ok := seen[string(k)] + if !ok { + t.Fatalf("Unexpected inserted node") + } + } + deleted := trie.tracer.deleteList() + if len(deleted) != 0 { + t.Fatalf("Unexpected deleted node tracked %d", len(deleted)) + } + + // Commit the changes and re-create with new root + root, nodes, _ := trie.Commit(false) + db.Update(NewWithNodeSet(nodes)) + trie, _ = New(common.Hash{}, root, db) + trie.tracer = newTracer() + + // Delete all the elements, check deletion set + for _, val := range vals { + trie.Delete([]byte(val.k)) + } + trie.Hash() + + inserted = trie.tracer.insertList() + if len(inserted) != 0 { + t.Fatalf("Unexpected inserted node tracked %d", len(inserted)) + } + deleted = trie.tracer.deleteList() + if len(deleted) != len(seen) { + t.Fatalf("Unexpected deleted node tracked want %d got %d", len(seen), len(deleted)) + } + for _, k := range deleted { + _, ok := seen[string(k)] + if !ok { + t.Fatalf("Unexpected inserted node") + } + } +} + +func TestTrieTracerNoop(t *testing.T) { + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie.tracer = newTracer() + + // Insert a batch of entries, all the nodes should be marked as inserted + vals := []struct{ k, v string }{ + {"do", "verb"}, + {"ether", "wookiedoo"}, + {"horse", "stallion"}, + {"shaman", "horse"}, + {"doge", "coin"}, + {"dog", "puppy"}, + {"somethingveryoddindeedthis is", "myothernodedata"}, + } + for _, val := range vals { + trie.Update([]byte(val.k), []byte(val.v)) + } + for _, val := range vals { + trie.Delete([]byte(val.k)) + } + if len(trie.tracer.insertList()) != 0 { + t.Fatalf("Unexpected inserted node tracked %d", len(trie.tracer.insertList())) + } + if len(trie.tracer.deleteList()) != 0 { + t.Fatalf("Unexpected deleted node tracked %d", len(trie.tracer.deleteList())) + } +} diff --git a/trie/utils.go b/trie/utils.go new file mode 100644 index 0000000000..8d1593ebf3 --- /dev/null +++ b/trie/utils.go @@ -0,0 +1,177 @@ +// (c) 2022, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +// tracer tracks the changes of trie nodes. During the trie operations, +// some nodes can be deleted from the trie, while these deleted nodes +// won't be captured by trie.Hasher or trie.Committer. Thus, these deleted +// nodes won't be removed from the disk at all. Tracer is an auxiliary tool +// used to track all insert and delete operations of trie and capture all +// deleted nodes eventually. +// +// The changed nodes can be mainly divided into two categories: the leaf +// node and intermediate node. The former is inserted/deleted by callers +// while the latter is inserted/deleted in order to follow the rule of trie. +// This tool can track all of them no matter the node is embedded in its +// parent or not, but valueNode is never tracked. +// +// Besides, it's also used for recording the original value of the nodes +// when they are resolved from the disk. The pre-value of the nodes will +// be used to construct reverse-diffs in the future. +// +// Note tracer is not thread-safe, callers should be responsible for handling +// the concurrency issues by themselves. +type tracer struct { + insert map[string]struct{} + delete map[string]struct{} + origin map[string][]byte +} + +// newTracer initializes the tracer for capturing trie changes. +func newTracer() *tracer { + return &tracer{ + insert: make(map[string]struct{}), + delete: make(map[string]struct{}), + origin: make(map[string][]byte), + } +} + +/* +// onRead tracks the newly loaded trie node and caches the rlp-encoded blob internally. +// Don't change the value outside of function since it's not deep-copied. +func (t *tracer) onRead(key []byte, val []byte) { + // Tracer isn't used right now, remove this check later. + if t == nil { + return + } + t.origin[string(key)] = val +} +*/ + +// onInsert tracks the newly inserted trie node. If it's already in the deletion set +// (resurrected node), then just wipe it from the deletion set as the "untouched". +func (t *tracer) onInsert(key []byte) { + // Tracer isn't used right now, remove this check later. + if t == nil { + return + } + if _, present := t.delete[string(key)]; present { + delete(t.delete, string(key)) + return + } + t.insert[string(key)] = struct{}{} +} + +// onDelete tracks the newly deleted trie node. If it's already +// in the addition set, then just wipe it from the addition set +// as it's untouched. +func (t *tracer) onDelete(key []byte) { + // Tracer isn't used right now, remove this check later. + if t == nil { + return + } + if _, present := t.insert[string(key)]; present { + delete(t.insert, string(key)) + return + } + t.delete[string(key)] = struct{}{} +} + +// insertList returns the tracked inserted trie nodes in list format. +func (t *tracer) insertList() [][]byte { + // Tracer isn't used right now, remove this check later. + if t == nil { + return nil + } + var ret [][]byte + for key := range t.insert { + ret = append(ret, []byte(key)) + } + return ret +} + +// deleteList returns the tracked deleted trie nodes in list format. +func (t *tracer) deleteList() [][]byte { + // Tracer isn't used right now, remove this check later. + if t == nil { + return nil + } + var ret [][]byte + for key := range t.delete { + ret = append(ret, []byte(key)) + } + return ret +} + +/* +// getPrev returns the cached original value of the specified node. +func (t *tracer) getPrev(key []byte) []byte { + // Don't panic on uninitialized tracer, it's possible in testing. + if t == nil { + return nil + } + return t.origin[string(key)] +} +*/ + +// reset clears the content tracked by tracer. +func (t *tracer) reset() { + // Tracer isn't used right now, remove this check later. + if t == nil { + return + } + t.insert = make(map[string]struct{}) + t.delete = make(map[string]struct{}) + t.origin = make(map[string][]byte) +} + +// copy returns a deep copied tracer instance. +func (t *tracer) copy() *tracer { + // Tracer isn't used right now, remove this check later. + if t == nil { + return nil + } + var ( + insert = make(map[string]struct{}) + delete = make(map[string]struct{}) + origin = make(map[string][]byte) + ) + for key := range t.insert { + insert[key] = struct{}{} + } + for key := range t.delete { + delete[key] = struct{}{} + } + for key, val := range t.origin { + origin[key] = val + } + return &tracer{ + insert: insert, + delete: delete, + origin: origin, + } +} From b2b0b89d9bb9e1fbc54b636bb11ef8b06cee3515 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Thu, 6 Oct 2022 09:48:06 +0200 Subject: [PATCH 5/9] Update tracer interface. --- eth/api.go | 18 +-- eth/backend.go | 8 +- eth/tracers/logger/access_list_tracer.go | 21 ++- eth/tracers/logger/gen_structlog.go | 12 +- eth/tracers/logger/logger.go | 157 ++++++++++++++++++++--- eth/tracers/logger/logger_json.go | 8 +- eth/tracers/logger/logger_test.go | 37 +++++- eth/tracers/native/4byte.go | 10 +- eth/tracers/native/call.go | 27 +++- eth/tracers/native/noop.go | 10 +- eth/tracers/native/prestate.go | 34 ++--- eth/tracers/native/revertreason.go | 119 +++++++++++++++++ eth/tracers/native/tracer.go | 16 ++- eth/tracers/tracers.go | 8 +- 14 files changed, 390 insertions(+), 95 deletions(-) create mode 100644 eth/tracers/native/revertreason.go diff --git a/eth/api.go b/eth/api.go index 8d6184828f..d7c4eeb85b 100644 --- a/eth/api.go +++ b/eth/api.go @@ -36,6 +36,10 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" "github.com/tenderly/coreth/core" "github.com/tenderly/coreth/core/rawdb" "github.com/tenderly/coreth/core/state" @@ -43,10 +47,6 @@ import ( "github.com/tenderly/coreth/internal/ethapi" "github.com/tenderly/coreth/rpc" "github.com/tenderly/coreth/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) // PublicEthereumAPI provides an API to access Ethereum full node-related @@ -245,9 +245,9 @@ type BadBlockArgs struct { // and returns them as a JSON list of block-hashes func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) { var ( - err error - blocks = api.eth.BlockChain().BadBlocks() - results = make([]*BadBlockArgs, 0, len(blocks)) + err error + blocks, _ = api.eth.BlockChain().BadBlocks() + results = make([]*BadBlockArgs, 0, len(blocks)) ) for _, block := range blocks { var ( @@ -434,11 +434,11 @@ func (api *PrivateDebugAPI) getModifiedAccounts(startBlock, endBlock *types.Bloc } triedb := api.eth.BlockChain().StateCache().TrieDB() - oldTrie, err := trie.NewSecure(startBlock.Root(), triedb) + oldTrie, err := trie.NewSecure(common.Hash{}, startBlock.Root(), triedb) if err != nil { return nil, err } - newTrie, err := trie.NewSecure(endBlock.Root(), triedb) + newTrie, err := trie.NewSecure(common.Hash{}, endBlock.Root(), triedb) if err != nil { return nil, err } diff --git a/eth/backend.go b/eth/backend.go index 1f26bcb3b3..c208ef993a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -34,6 +34,9 @@ import ( "time" "github.com/ava-labs/avalanchego/utils/timer/mockable" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" "github.com/tenderly/coreth/accounts" "github.com/tenderly/coreth/consensus" "github.com/tenderly/coreth/consensus/dummy" @@ -54,9 +57,6 @@ import ( "github.com/tenderly/coreth/node" "github.com/tenderly/coreth/params" "github.com/tenderly/coreth/rpc" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/log" ) // Config contains the configuration options of the ETH protocol. @@ -151,7 +151,7 @@ func New( "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024, ) - chainConfig, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis) + chainConfig, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis, lastAcceptedHash) if genesisErr != nil { return nil, genesisErr } diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index 088c3b1159..171cbe9b13 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -20,9 +20,9 @@ import ( "math/big" "time" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/types" "github.com/tenderly/coreth/core/vm" - "github.com/ethereum/go-ethereum/common" ) // accessList is an accumulator for the set of accounts and storage slots an EVM @@ -62,16 +62,14 @@ func (al accessList) equal(other accessList) bool { if len(al) != len(other) { return false } + // Given that len(al) == len(other), we only need to check that + // all the items from al are in other. for addr := range al { if _, ok := other[addr]; !ok { return false } } - for addr := range other { - if _, ok := al[addr]; !ok { - return false - } - } + // Accounts match, cross reference the storage slots too for addr, slots := range al { otherslots := other[addr] @@ -79,16 +77,13 @@ func (al accessList) equal(other accessList) bool { if len(slots) != len(otherslots) { return false } + // Given that len(slots) == len(otherslots), we only need to check that + // all the items from slots are in otherslots. for hash := range slots { if _, ok := otherslots[hash]; !ok { return false } } - for hash := range otherslots { - if _, ok := slots[hash]; !ok { - return false - } - } } return true } @@ -174,6 +169,10 @@ func (*AccessListTracer) CaptureEnter(typ vm.OpCode, from common.Address, to com func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {} +func (*AccessListTracer) CaptureTxStart(gasLimit uint64) {} + +func (*AccessListTracer) CaptureTxEnd(restGas uint64) {} + // AccessList returns the current accesslist maintained by the tracer. func (a *AccessListTracer) AccessList() types.AccessList { return a.list.accessList() diff --git a/eth/tracers/logger/gen_structlog.go b/eth/tracers/logger/gen_structlog.go index 6c050a32f5..7b3bad8712 100644 --- a/eth/tracers/logger/gen_structlog.go +++ b/eth/tracers/logger/gen_structlog.go @@ -5,11 +5,11 @@ package logger import ( "encoding/json" - "github.com/tenderly/coreth/core/vm" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/holiman/uint256" + "github.com/tenderly/coreth/core/vm" ) var _ = (*structLogMarshaling)(nil) @@ -21,16 +21,16 @@ func (s StructLog) MarshalJSON() ([]byte, error) { Op vm.OpCode `json:"op"` Gas math.HexOrDecimal64 `json:"gas"` GasCost math.HexOrDecimal64 `json:"gasCost"` - Memory hexutil.Bytes `json:"memory"` + Memory hexutil.Bytes `json:"memory,omitempty"` MemorySize int `json:"memSize"` Stack []uint256.Int `json:"stack"` - ReturnData hexutil.Bytes `json:"returnData"` + ReturnData hexutil.Bytes `json:"returnData,omitempty"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` Err error `json:"-"` OpName string `json:"opName"` - ErrorString string `json:"error"` + ErrorString string `json:"error,omitempty"` } var enc StructLog enc.Pc = s.Pc @@ -57,10 +57,10 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { Op *vm.OpCode `json:"op"` Gas *math.HexOrDecimal64 `json:"gas"` GasCost *math.HexOrDecimal64 `json:"gasCost"` - Memory *hexutil.Bytes `json:"memory"` + Memory *hexutil.Bytes `json:"memory,omitempty"` MemorySize *int `json:"memSize"` Stack []uint256.Int `json:"stack"` - ReturnData *hexutil.Bytes `json:"returnData"` + ReturnData *hexutil.Bytes `json:"returnData,omitempty"` Storage map[common.Hash]common.Hash `json:"-"` Depth *int `json:"depth"` RefundCounter *uint64 `json:"refund"` diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 995804669c..a2c18f392e 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -1,4 +1,4 @@ -// Copyright 2015 The go-ethereum Authors +// Copyright 2021 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -18,19 +18,22 @@ package logger import ( "encoding/hex" + "encoding/json" "fmt" "io" "math/big" "strings" + "sync/atomic" "time" - "github.com/tenderly/coreth/core/types" - "github.com/tenderly/coreth/core/vm" - "github.com/tenderly/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/holiman/uint256" + "github.com/tenderly/coreth/core/types" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/params" + "github.com/tenderly/coreth/vmerrs" ) // Storage represents a contract's storage. @@ -38,7 +41,7 @@ type Storage map[common.Hash]common.Hash // Copy duplicates the current storage. func (s Storage) Copy() Storage { - cpy := make(Storage) + cpy := make(Storage, len(s)) for key, value := range s { cpy[key] = value } @@ -66,10 +69,10 @@ type StructLog struct { Op vm.OpCode `json:"op"` Gas uint64 `json:"gas"` GasCost uint64 `json:"gasCost"` - Memory []byte `json:"memory"` + Memory []byte `json:"memory,omitempty"` MemorySize int `json:"memSize"` Stack []uint256.Int `json:"stack"` - ReturnData []byte `json:"returnData"` + ReturnData []byte `json:"returnData,omitempty"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` @@ -82,8 +85,8 @@ type structLogMarshaling struct { GasCost math.HexOrDecimal64 Memory hexutil.Bytes ReturnData hexutil.Bytes - OpName string `json:"opName"` // adds call to OpName() in MarshalJSON - ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON + OpName string `json:"opName"` // adds call to OpName() in MarshalJSON + ErrorString string `json:"error,omitempty"` // adds call to ErrorString() in MarshalJSON } // OpName formats the operand name in a human-readable format. @@ -108,10 +111,15 @@ type StructLogger struct { cfg Config env *vm.EVM - storage map[common.Address]Storage - logs []StructLog - output []byte - err error + storage map[common.Address]Storage + logs []StructLog + output []byte + err error + gasLimit uint64 + usedGas uint64 + + interrupt uint32 // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption } // NewStructLogger returns a new logger @@ -142,13 +150,18 @@ func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common. // // CaptureState also tracks SLOAD/SSTORE ops to track storage change. func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - memory := scope.Memory - stack := scope.Stack - contract := scope.Contract + // If tracing was interrupted, set the error and stop + if atomic.LoadUint32(&l.interrupt) > 0 { + l.env.Cancel() + return + } // check if already accumulated the specified number of logs if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { return } + memory := scope.Memory + stack := scope.Stack + contract := scope.Contract // Copy a snapshot of the current memory state to a new buffer var mem []byte if l.cfg.EnableMemory { @@ -211,7 +224,7 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration l.output = output l.err = err if l.cfg.Debug { - fmt.Printf("0x%x\n", output) + fmt.Printf("%#x\n", output) if err != nil { fmt.Printf(" error: %v\n", err) } @@ -221,7 +234,42 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } -func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} +func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) { +} + +func (l *StructLogger) GetResult() (json.RawMessage, error) { + // Tracing aborted + if l.reason != nil { + return nil, l.reason + } + failed := l.err != nil + returnData := common.CopyBytes(l.output) + // Return data when successful and revert reason when reverted, otherwise empty. + returnVal := fmt.Sprintf("%x", returnData) + if failed && l.err != vmerrs.ErrExecutionReverted { + returnVal = "" + } + return json.Marshal(&ExecutionResult{ + Gas: l.usedGas, + Failed: failed, + ReturnValue: returnVal, + StructLogs: formatLogs(l.StructLogs()), + }) +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (l *StructLogger) Stop(err error) { + l.reason = err + atomic.StoreUint32(&l.interrupt, 1) +} + +func (l *StructLogger) CaptureTxStart(gasLimit uint64) { + l.gasLimit = gasLimit +} + +func (l *StructLogger) CaptureTxEnd(restGas uint64) { + l.usedGas = l.gasLimit - restGas +} // StructLogs returns the captured log entries. func (l *StructLogger) StructLogs() []StructLog { return l.logs } @@ -298,11 +346,11 @@ func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger { func (t *mdLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { t.env = env if !create { - fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", + fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n", from.String(), to.String(), input, gas, value) } else { - fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", + fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n", from.String(), to.String(), input, gas, value) } @@ -339,7 +387,7 @@ func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope } func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) { - fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n", + fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n", output, gasUsed, err) } @@ -347,3 +395,70 @@ func (t *mdLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Ad } func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} + +func (*mdLogger) CaptureTxStart(gasLimit uint64) {} + +func (*mdLogger) CaptureTxEnd(restGas uint64) {} + +// ExecutionResult groups all structured logs emitted by the EVM +// while replaying a transaction in debug mode as well as transaction +// execution status, the amount of gas used and the return value +type ExecutionResult struct { + Gas uint64 `json:"gas"` + Failed bool `json:"failed"` + ReturnValue string `json:"returnValue"` + StructLogs []StructLogRes `json:"structLogs"` +} + +// StructLogRes stores a structured log emitted by the EVM while replaying a +// transaction in debug mode +type StructLogRes struct { + Pc uint64 `json:"pc"` + Op string `json:"op"` + Gas uint64 `json:"gas"` + GasCost uint64 `json:"gasCost"` + Depth int `json:"depth"` + Error string `json:"error,omitempty"` + Stack *[]string `json:"stack,omitempty"` + Memory *[]string `json:"memory,omitempty"` + Storage *map[string]string `json:"storage,omitempty"` + RefundCounter uint64 `json:"refund,omitempty"` +} + +// formatLogs formats EVM returned structured logs for json output +func formatLogs(logs []StructLog) []StructLogRes { + formatted := make([]StructLogRes, len(logs)) + for index, trace := range logs { + formatted[index] = StructLogRes{ + Pc: trace.Pc, + Op: trace.Op.String(), + Gas: trace.Gas, + GasCost: trace.GasCost, + Depth: trace.Depth, + Error: trace.ErrorString(), + RefundCounter: trace.RefundCounter, + } + if trace.Stack != nil { + stack := make([]string, len(trace.Stack)) + for i, stackValue := range trace.Stack { + stack[i] = stackValue.Hex() + } + formatted[index].Stack = &stack + } + if trace.Memory != nil { + memory := make([]string, 0, (len(trace.Memory)+31)/32) + for i := 0; i+32 <= len(trace.Memory); i += 32 { + memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32])) + } + formatted[index].Memory = &memory + } + if trace.Storage != nil { + storage := make(map[string]string) + for i, storageValue := range trace.Storage { + storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) + } + formatted[index].Storage = &storage + } + } + return formatted +} diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index 3a435218cd..d2da786b0a 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -1,4 +1,4 @@ -// Copyright 2017 The go-ethereum Authors +// Copyright 2021 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -22,9 +22,9 @@ import ( "math/big" "time" - "github.com/tenderly/coreth/core/vm" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/tenderly/coreth/core/vm" ) type JSONLogger struct { @@ -98,3 +98,7 @@ func (l *JSONLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common. } func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} + +func (l *JSONLogger) CaptureTxStart(gasLimit uint64) {} + +func (l *JSONLogger) CaptureTxEnd(restGas uint64) {} diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index c723e338fc..2a8809aff5 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 The go-ethereum Authors +// Copyright 2021 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -17,13 +17,15 @@ package logger import ( + "encoding/json" + "fmt" "math/big" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/state" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/params" - "github.com/ethereum/go-ethereum/common" ) type dummyContractRef struct { @@ -72,3 +74,34 @@ func TestStoreCapture(t *testing.T) { t.Errorf("expected %x, got %x", exp, logger.storage[contract.Address()][index]) } } + +// Tests that blank fields don't appear in logs when JSON marshalled, to reduce +// logs bloat and confusion. See https://github.com/ethereum/go-ethereum/issues/24487 +func TestStructLogMarshalingOmitEmpty(t *testing.T) { + tests := []struct { + name string + log *StructLog + want string + }{ + {"empty err and no fields", &StructLog{}, + `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`}, + {"with err", &StructLog{Err: fmt.Errorf("this failed")}, + `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP","error":"this failed"}`}, + {"with mem", &StructLog{Memory: make([]byte, 2), MemorySize: 2}, + `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memory":"0x0000","memSize":2,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`}, + {"with 0-size mem", &StructLog{Memory: make([]byte, 0)}, + `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + blob, err := json.Marshal(tt.log) + if err != nil { + t.Fatal(err) + } + if have, want := string(blob), tt.want; have != want { + t.Fatalf("mismatched results\n\thave: %v\n\twant: %v", have, want) + } + }) + } +} diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index 8d0c0c7713..6e30f9e116 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -33,9 +33,9 @@ import ( "sync/atomic" "time" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/eth/tracers" - "github.com/ethereum/go-ethereum/common" ) func init() { @@ -65,11 +65,11 @@ type fourByteTracer struct { // newFourByteTracer returns a native go tracer which collects // 4 byte-identifiers of a tx, and implements vm.EVMLogger. -func newFourByteTracer() tracers.Tracer { +func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { t := &fourByteTracer{ ids: make(map[string]int), } - return t + return t, nil } // isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go @@ -141,6 +141,10 @@ func (t *fourByteTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, func (t *fourByteTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { } +func (*fourByteTracer) CaptureTxStart(gasLimit uint64) {} + +func (*fourByteTracer) CaptureTxEnd(restGas uint64) {} + // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *fourByteTracer) GetResult() (json.RawMessage, error) { diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 64ed05db33..720b68067d 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -35,9 +35,9 @@ import ( "sync/atomic" "time" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/eth/tracers" - "github.com/ethereum/go-ethereum/common" ) func init() { @@ -60,16 +60,27 @@ type callFrame struct { type callTracer struct { env *vm.EVM callstack []callFrame + config callTracerConfig interrupt uint32 // Atomic flag to signal execution interruption reason error // Textual reason for the interruption } +type callTracerConfig struct { + OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls +} + // newCallTracer returns a native go tracer which tracks // call frames of a tx, and implements vm.EVMLogger. -func newCallTracer() tracers.Tracer { +func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { + var config callTracerConfig + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + } // First callframe contains tx context info // and is populated on start and end. - return &callTracer{callstack: make([]callFrame, 1)} + return &callTracer{callstack: make([]callFrame, 1), config: config}, nil } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. @@ -111,6 +122,9 @@ func (t *callTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ * // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + if t.config.OnlyTopCall { + return + } // Skip if tracing was interrupted if atomic.LoadUint32(&t.interrupt) > 0 { t.env.Cancel() @@ -131,6 +145,9 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common. // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { + if t.config.OnlyTopCall { + return + } size := len(t.callstack) if size <= 1 { return @@ -152,6 +169,10 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) } +func (*callTracer) CaptureTxStart(gasLimit uint64) {} + +func (*callTracer) CaptureTxEnd(restGas uint64) {} + // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *callTracer) GetResult() (json.RawMessage, error) { diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index 267add8cb5..287057856b 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -31,9 +31,9 @@ import ( "math/big" "time" + "github.com/ethereum/go-ethereum/common" "github.com/tenderly/coreth/core/vm" "github.com/tenderly/coreth/eth/tracers" - "github.com/ethereum/go-ethereum/common" ) func init() { @@ -45,8 +45,8 @@ func init() { type noopTracer struct{} // newNoopTracer returns a new noop tracer. -func newNoopTracer() tracers.Tracer { - return &noopTracer{} +func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { + return &noopTracer{}, nil } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. @@ -74,6 +74,10 @@ func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common. func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) { } +func (*noopTracer) CaptureTxStart(gasLimit uint64) {} + +func (*noopTracer) CaptureTxEnd(restGas uint64) {} + // GetResult returns an empty json object. func (t *noopTracer) GetResult() (json.RawMessage, error) { return json.RawMessage(`{}`), nil diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index e3f3120603..cf35b47841 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -32,12 +32,11 @@ import ( "sync/atomic" "time" - "github.com/tenderly/coreth/core" - "github.com/tenderly/coreth/core/vm" - "github.com/tenderly/coreth/eth/tracers" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/tracers" ) func init() { @@ -57,14 +56,15 @@ type prestateTracer struct { prestate prestate create bool to common.Address + gasLimit uint64 // Amount of gas bought for the whole tx interrupt uint32 // Atomic flag to signal execution interruption reason error // Textual reason for the interruption } -func newPrestateTracer() tracers.Tracer { +func newPrestateTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { // First callframe contains tx context info // and is populated on start and end. - return &prestateTracer{prestate: prestate{}} + return &prestateTracer{prestate: prestate{}}, nil } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. @@ -73,14 +73,6 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo t.create = create t.to = to - // Compute intrinsic gas - isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) - isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) - intrinsicGas, err := core.IntrinsicGas(input, nil, create, isHomestead, isIstanbul) - if err != nil { - return - } - t.lookupAccount(from) t.lookupAccount(to) @@ -89,17 +81,11 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo toBal = new(big.Int).Sub(toBal, value) t.prestate[to].Balance = hexutil.EncodeBig(toBal) - // The sender balance is after reducing: value, gasLimit, intrinsicGas. + // The sender balance is after reducing: value and gasLimit. // We need to re-add them to get the pre-tx balance. fromBal := hexutil.MustDecodeBig(t.prestate[from].Balance) gasPrice := env.TxContext.GasPrice - consumedGas := new(big.Int).Mul( - gasPrice, - new(big.Int).Add( - new(big.Int).SetUint64(intrinsicGas), - new(big.Int).SetUint64(gas), - ), - ) + consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit)) fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas)) t.prestate[from].Balance = hexutil.EncodeBig(fromBal) t.prestate[from].Nonce-- @@ -150,6 +136,12 @@ func (t *prestateTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, func (t *prestateTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } +func (t *prestateTracer) CaptureTxStart(gasLimit uint64) { + t.gasLimit = gasLimit +} + +func (t *prestateTracer) CaptureTxEnd(restGas uint64) {} + // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. func (t *prestateTracer) CaptureExit(output []byte, gasUsed uint64, err error) { diff --git a/eth/tracers/native/revertreason.go b/eth/tracers/native/revertreason.go new file mode 100644 index 0000000000..0973a6f1dd --- /dev/null +++ b/eth/tracers/native/revertreason.go @@ -0,0 +1,119 @@ +// (c) 2022, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package native + +import ( + "bytes" + "encoding/json" + "math/big" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/tenderly/coreth/accounts/abi" + "github.com/tenderly/coreth/core/vm" + "github.com/tenderly/coreth/eth/tracers" + "github.com/tenderly/coreth/vmerrs" +) + +func init() { + register("revertReasonTracer", newRevertReasonTracer) +} + +var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4] + +// revertReasonTracer is a go implementation of the Tracer interface which +// track the error message or revert reason return by the contract. +type revertReasonTracer struct { + env *vm.EVM + revertReason string // The revert reason return from the tx, if tx success, empty string return + interrupt uint32 // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption +} + +// newRevertReasonTracer returns a new revert reason tracer. +func newRevertReasonTracer(_ *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { + return &revertReasonTracer{}, nil +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *revertReasonTracer) CaptureStart(env *vm.EVM, _ common.Address, _ common.Address, _ bool, _ []byte, _ uint64, _ *big.Int) { + t.env = env +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (t *revertReasonTracer) CaptureEnd(output []byte, _ uint64, _ time.Duration, err error) { + if err != nil { + if err == vmerrs.ErrExecutionReverted && len(output) > 4 && bytes.Equal(output[:4], revertSelector) { + errMsg, _ := abi.UnpackRevert(output) + t.revertReason = err.Error() + ": " + errMsg + } else { + t.revertReason = err.Error() + } + } +} + +// CaptureState implements the EVMLogger interface to trace a single step of VM execution. +func (t *revertReasonTracer) CaptureState(_ uint64, _ vm.OpCode, _, _ uint64, _ *vm.ScopeContext, _ []byte, _ int, _ error) { +} + +// CaptureFault implements the EVMLogger interface to trace an execution fault. +func (t *revertReasonTracer) CaptureFault(_ uint64, _ vm.OpCode, _, _ uint64, _ *vm.ScopeContext, _ int, _ error) { +} + +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *revertReasonTracer) CaptureEnter(_ vm.OpCode, _ common.Address, _ common.Address, _ []byte, _ uint64, _ *big.Int) { + // Skip if tracing was interrupted + if atomic.LoadUint32(&t.interrupt) > 0 { + t.env.Cancel() + return + } +} + +// CaptureExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *revertReasonTracer) CaptureExit(_ []byte, _ uint64, _ error) {} + +func (t *revertReasonTracer) CaptureTxStart(_ uint64) {} + +func (t *revertReasonTracer) CaptureTxEnd(_ uint64) {} + +// GetResult returns an error message json object. +func (t *revertReasonTracer) GetResult() (json.RawMessage, error) { + res, err := json.Marshal(t.revertReason) + if err != nil { + return nil, err + } + return res, t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *revertReasonTracer) Stop(err error) { + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) +} diff --git a/eth/tracers/native/tracer.go b/eth/tracers/native/tracer.go index fc93fe0817..729b5e477f 100644 --- a/eth/tracers/native/tracer.go +++ b/eth/tracers/native/tracer.go @@ -45,6 +45,7 @@ func init() { package native import ( + "encoding/json" "errors" "github.com/tenderly/coreth/eth/tracers" @@ -55,6 +56,9 @@ func init() { tracers.RegisterLookup(false, lookup) } +// ctorFn is the constructor signature of a native tracer. +type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error) + /* ctors is a map of package-local tracer constructors. @@ -67,23 +71,23 @@ The go spec (https://golang.org/ref/spec#Package_initialization) says Hence, we cannot make the map in init, but must make it upon first use. */ -var ctors map[string]func() tracers.Tracer +var ctors map[string]ctorFn // register is used by native tracers to register their presence. -func register(name string, ctor func() tracers.Tracer) { +func register(name string, ctor ctorFn) { if ctors == nil { - ctors = make(map[string]func() tracers.Tracer) + ctors = make(map[string]ctorFn) } ctors[name] = ctor } // lookup returns a tracer, if one can be matched to the given name. -func lookup(name string, ctx *tracers.Context) (tracers.Tracer, error) { +func lookup(name string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { if ctors == nil { - ctors = make(map[string]func() tracers.Tracer) + ctors = make(map[string]ctorFn) } if ctor, ok := ctors[name]; ok { - return ctor(), nil + return ctor(ctx, cfg) } return nil, errors.New("no tracer found") } diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index 06f6965ee6..4c434fd3f3 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -21,8 +21,8 @@ import ( "encoding/json" "errors" - "github.com/tenderly/coreth/core/vm" "github.com/ethereum/go-ethereum/common" + "github.com/tenderly/coreth/core/vm" ) // Context contains some contextual infos for a transaction execution that is not @@ -42,7 +42,7 @@ type Tracer interface { Stop(err error) } -type lookupFunc func(string, *Context) (Tracer, error) +type lookupFunc func(string, *Context, json.RawMessage) (Tracer, error) var ( lookups []lookupFunc @@ -62,9 +62,9 @@ func RegisterLookup(wildcard bool, lookup lookupFunc) { // New returns a new instance of a tracer, by iterating through the // registered lookups. -func New(code string, ctx *Context) (Tracer, error) { +func New(code string, ctx *Context, cfg json.RawMessage) (Tracer, error) { for _, lookup := range lookups { - if tracer, err := lookup(code, ctx); err == nil { + if tracer, err := lookup(code, ctx, cfg); err == nil { return tracer, nil } } From f4d7626d9a3cd6099fa19d395646be015c777358 Mon Sep 17 00:00:00 2001 From: FuzzysTodd <157565446+FuzzysTodd@users.noreply.github.com> Date: Mon, 11 Mar 2024 21:27:59 -0400 Subject: [PATCH 6/9] Create pypi ds --- pypi | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 pypi diff --git a/pypi b/pypi new file mode 100644 index 0000000000..ebc61603b1 --- /dev/null +++ b/pypi @@ -0,0 +1,29 @@ +import setuptools + +with open("README.md", "r") as fh: + long_description = fh.read() + +with open("requirements.txt", "r") as f: + requirements = f.readlines() + +setuptools.setup( + name="HT", + version="0.0.1", + author="Krunoslav Lehman Pavasovic, Umut Simsekli", + author_email="krunolp@gmail.com", + description="Heavy Tailed Experiments", + long_description=long_description, + long_description_content_type="text/markdown", + url='github.com/krunolp/heavy_tails', + package_dir={'heavy_tails': 'heavy_tails'}, + packages=setuptools.find_packages(), + install_requires=[ + 'numpy', + 'pandas', + 'scipy', + 'sklearn', + 'matplotlib', + 'jax', + 'jaxlib', + ], +) From b21995c7f85a26d0724c5c5d156738696bc572e6 Mon Sep 17 00:00:00 2001 From: Todd Anthony Stephens Date: Sun, 28 Apr 2024 02:56:34 -0400 Subject: [PATCH 7/9] Create devcontainer.json Core --- .devcontainer/devcontainer.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..ad93c14a0f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,5 @@ +{ + "image": "mcr.microsoft.com/devcontainers/universal:2", + "features": { + } +} From a26c0ec5d53702b274ee9ed77f34e066b0a1730a Mon Sep 17 00:00:00 2001 From: Marko Gajin <42877612+g4zyn@users.noreply.github.com> Date: Wed, 29 May 2024 17:13:23 +0200 Subject: [PATCH 8/9] EVMStateTransfer accepts statedb as interface (#11) --- plugin/evm/export_tx.go | 4 ++-- plugin/evm/import_tx.go | 4 ++-- plugin/evm/test_tx.go | 4 ++-- plugin/evm/tx.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index 44da333b49..4f6828ec03 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -7,9 +7,9 @@ import ( "context" "errors" "fmt" + "github.com/ava-labs/coreth/core/vm" "math/big" - "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/params" "github.com/ava-labs/avalanchego/chains/atomic" @@ -369,7 +369,7 @@ func (vm *VM) newExportTx( } // EVMStateTransfer executes the state update from the atomic export transaction -func (utx *UnsignedExportTx) EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error { +func (utx *UnsignedExportTx) EVMStateTransfer(ctx *snow.Context, state vm.StateDB) error { addrs := map[[20]byte]uint64{} for _, from := range utx.Ins { if from.AssetID == ctx.AVAXAssetID { diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index ee2eff0590..cd5d493368 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -7,10 +7,10 @@ import ( "context" "errors" "fmt" + "github.com/ava-labs/coreth/core/vm" "math/big" "slices" - "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/params" "github.com/ava-labs/avalanchego/chains/atomic" @@ -428,7 +428,7 @@ func (vm *VM) newImportTxWithUTXOs( // EVMStateTransfer performs the state transfer to increase the balances of // accounts accordingly with the imported EVMOutputs -func (utx *UnsignedImportTx) EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error { +func (utx *UnsignedImportTx) EVMStateTransfer(ctx *snow.Context, state vm.StateDB) error { for _, to := range utx.Outs { if to.AssetID == ctx.AVAXAssetID { log.Debug("crosschain", "src", utx.SourceChain, "addr", to.Address, "amount", to.Amount, "assetID", "AVAX") diff --git a/plugin/evm/test_tx.go b/plugin/evm/test_tx.go index c057c874ad..96d8cd2b88 100644 --- a/plugin/evm/test_tx.go +++ b/plugin/evm/test_tx.go @@ -4,6 +4,7 @@ package evm import ( + "github.com/ava-labs/coreth/core/vm" "math/big" "math/rand" @@ -16,7 +17,6 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/params" ) @@ -71,7 +71,7 @@ func (t *TestUnsignedTx) SemanticVerify(vm *VM, stx *Tx, parent *Block, baseFee } // EVMStateTransfer implements the UnsignedAtomicTx interface -func (t *TestUnsignedTx) EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error { +func (t *TestUnsignedTx) EVMStateTransfer(ctx *snow.Context, state vm.StateDB) error { return t.EVMStateTransferV } diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go index 5c8497a3a3..0dd6ce62cd 100644 --- a/plugin/evm/tx.go +++ b/plugin/evm/tx.go @@ -7,12 +7,12 @@ import ( "bytes" "errors" "fmt" + "github.com/ava-labs/coreth/core/vm" "math/big" "sort" "github.com/ethereum/go-ethereum/common" - "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/params" "github.com/ava-labs/avalanchego/chains/atomic" @@ -130,7 +130,7 @@ type UnsignedAtomicTx interface { // The set of atomic requests must be returned in a consistent order. AtomicOps() (ids.ID, *atomic.Requests, error) - EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error + EVMStateTransfer(ctx *snow.Context, state vm.StateDB) error } // Tx is a signed transaction From ff2a346cef919e08ad13c3d823909dd8f5f45f80 Mon Sep 17 00:00:00 2001 From: Todd Anthony Stephens Date: Wed, 28 Aug 2024 20:32:15 -0400 Subject: [PATCH 9/9] tas --- .vscode/extensions.json | 7 +++++++ eth/gasprice/gasprice.go | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..cc1008277f --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "fangjin.gist", + "k9982874.github-gist-explorer", + "michaeljolley.vscx-gist" + ] +} \ No newline at end of file diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 394998dc42..f2e091a48c 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -33,11 +33,11 @@ import ( "sync" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/tenderly/coreth/consensus/dummy" - "github.com/tenderly/coreth/core" - "github.com/tenderly/coreth/core/types" - "github.com/tenderly/coreth/params" - "github.com/tenderly/coreth/rpc" + "github.com/ava-labs/coreth/consensus/dummy" + "github.com/ava-labs/coreth/core" + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/event"