From 1c2c01e80b82c1425e5cc26a455d6d87a86991f7 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Thu, 7 Oct 2021 12:15:02 -0500 Subject: [PATCH] Add hooks needed for ArbOS --- consensus/clique/clique.go | 4 +-- consensus/consensus.go | 2 +- consensus/ethash/consensus.go | 4 +-- core/state_processor.go | 2 +- core/state_transition.go | 32 +++++++++++++++++++++++- core/vm/contracts.go | 31 ++++++++++++++++++++++- core/vm/contracts_test.go | 8 +++--- core/vm/evm.go | 46 ++++++++++++++++++++++++++++++++--- 8 files changed, 113 insertions(+), 16 deletions(-) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 449095e7230f..558b4f6c6b30 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -568,7 +568,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header // Finalize implements consensus.Engine, ensuring no uncles are set, nor block // rewards given. -func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { +func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) { // No block rewards in PoA, so the state remains as is and uncles are dropped header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) header.UncleHash = types.CalcUncleHash(nil) @@ -578,7 +578,7 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade // nor block rewards given, and returns the final block. func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { // Finalize block - c.Finalize(chain, header, state, txs, uncles) + c.Finalize(chain, header, state, txs, uncles, receipts) // Assemble and return the final block for sealing return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), nil diff --git a/consensus/consensus.go b/consensus/consensus.go index 2a5aac945d9d..20cb3b41a519 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -87,7 +87,7 @@ type Engine interface { // Note: The block header and state database might be updated to reflect any // consensus rules that happen at finalization (e.g. block rewards). Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, - uncles []*types.Header) + uncles []*types.Header, receipts []*types.Receipt) // FinalizeAndAssemble runs any post-transaction state modifications (e.g. block // rewards) and assembles the final block. diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 5743387f6c7f..f588908829a9 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -586,7 +586,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H // Finalize implements consensus.Engine, accumulating the block and uncle rewards, // setting the final state on the header -func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { +func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) { // Accumulate any block and uncle rewards and commit the final state root accumulateRewards(chain.Config(), state, header, uncles) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) @@ -596,7 +596,7 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types. // uncle rewards, setting the final state and assembling the block. func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { // Finalize block - ethash.Finalize(chain, header, state, txs, uncles) + ethash.Finalize(chain, header, state, txs, uncles, receipts) // Header seems complete, assemble into a block and return return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), nil diff --git a/core/state_processor.go b/core/state_processor.go index d4c77ae41042..bc5f95277084 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -87,7 +87,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg allLogs = append(allLogs, receipt.Logs...) } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) - p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles()) + p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), receipts) return receipts, allLogs, *usedGas, nil } diff --git a/core/state_transition.go b/core/state_transition.go index 6a09f6adc441..fd7ca2bab55e 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,6 +17,7 @@ package core import ( + "errors" "fmt" "math" "math/big" @@ -49,6 +50,8 @@ The state transitioning model does all the necessary work to work out a valid ne 6) Derive new state root */ type StateTransition struct { + extraGasUsedByHook uint64 + gp *GasPool msg Message gas uint64 @@ -256,6 +259,9 @@ func (st *StateTransition) preCheck() error { return st.buyGas() } +var ExtraGasChargingHook func(msg Message, txGasRemaining *uint64, gasPool *GasPool, state vm.StateDB) error +var EndTxHook func(msg Message, totalGasUsed uint64, extraGasCharged uint64, gasPool *GasPool, success bool, state vm.StateDB) error + // TransitionDb will transition the state by applying the current message and // returning the evm execution result with following fields. // @@ -269,7 +275,7 @@ func (st *StateTransition) preCheck() error { // // However if any consensus issue encountered, return the error directly with // nil evm execution result. -func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { +func (st *StateTransition) transitionDbImpl() (*ExecutionResult, error) { // First check this message satisfies all consensus rules before // applying the message. The rules include these clauses // @@ -301,6 +307,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } st.gas -= gas + if ExtraGasChargingHook != nil { + start := st.gas + ExtraGasChargingHook(st.msg, &st.gas, st.gp, st.state) + if start > st.gas { + st.extraGasUsedByHook += start - st.gas + } + } + // Check clause 6 if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) { return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex()) @@ -342,6 +356,22 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { }, nil } +func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { + res, err := st.transitionDbImpl() + if err != nil && !errors.Is(err, ErrNonceTooLow) && !errors.Is(err, ErrNonceTooHigh) { + res = &ExecutionResult{ + UsedGas: st.gasUsed(), + Err: err, + ReturnData: nil, + } + err = nil + } + if err == nil && EndTxHook != nil { + EndTxHook(st.msg, st.gas, st.extraGasUsedByHook, st.gp, res.Err == nil, st.state) + } + return res, err +} + func (st *StateTransition) refundGas(refundQuotient uint64) { // Apply refund counter, capped to a refund quotient refund := st.gasUsed() / refundQuotient diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 9210f5486c57..e4775d31ba6d 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -128,8 +128,18 @@ func init() { } } +var ExtraPrecompiles = make(map[common.Address]PrecompiledContract) + // ActivePrecompiles returns the precompiles enabled with the current configuration. func ActivePrecompiles(rules params.Rules) []common.Address { + list := ethereumPrecompiles(rules) + for addr := range ExtraPrecompiles { + list = append(list, addr) + } + return list +} + +func ethereumPrecompiles(rules params.Rules) []common.Address { switch { case rules.IsBerlin: return PrecompiledAddressesBerlin @@ -142,12 +152,31 @@ func ActivePrecompiles(rules params.Rules) []common.Address { } } +type AdvancedPrecompileCall struct { + PrecompileAddress common.Address + ActingAsAddress common.Address + Caller common.Address + Value *big.Int + ReadOnly bool + Evm *EVM +} + +type AdvancedPrecompile interface { + RunAdvanced(input []byte, suppliedGas uint64, advancedInfo *AdvancedPrecompileCall) (ret []byte, remainingGas uint64, err error) + PrecompiledContract +} + // RunPrecompiledContract runs and evaluates the output of a precompiled contract. // It returns // - the returned bytes, // - the _remaining_ gas, // - any error that occurred -func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { +func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, advancedInfo *AdvancedPrecompileCall) (ret []byte, remainingGas uint64, err error) { + advanced, isAdvanced := p.(AdvancedPrecompile) + if isAdvanced { + return advanced.RunAdvanced(input, suppliedGas, advancedInfo) + } + gasCost := p.RequiredGas(input) if suppliedGas < gasCost { return nil, 0, ErrOutOfGas diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 30d9b49f719b..71d43a50ed2e 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -96,7 +96,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - if res, _, err := RunPrecompiledContract(p, in, gas); err != nil { + if res, _, err := RunPrecompiledContract(p, in, gas, nil); err != nil { t.Error(err) } else if common.Bytes2Hex(res) != test.Expected { t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) @@ -118,7 +118,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { gas := p.RequiredGas(in) - 1 t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas) + _, _, err := RunPrecompiledContract(p, in, gas, nil) if err.Error() != "out of gas" { t.Errorf("Expected error [out of gas], got [%v]", err) } @@ -135,7 +135,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(test.Name, func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas) + _, _, err := RunPrecompiledContract(p, in, gas, nil) if err.Error() != test.ExpectedError { t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) } @@ -167,7 +167,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { copy(data, in) - res, _, err = RunPrecompiledContract(p, data, reqGas) + res, _, err = RunPrecompiledContract(p, data, reqGas, nil) } bench.StopTimer() elapsed := uint64(time.Since(start)) diff --git a/core/vm/evm.go b/core/vm/evm.go index 3b4bd69d7572..c275dadf4546 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -42,6 +42,11 @@ type ( ) func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { + extended, hasExtended := ExtraPrecompiles[addr] + if hasExtended { + return extended, true + } + var precompiles map[common.Address]PrecompiledContract switch { case evm.chainRules.IsBerlin: @@ -209,7 +214,15 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } if isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + info := &AdvancedPrecompileCall{ + PrecompileAddress: addr, + ActingAsAddress: addr, + Caller: caller.Address(), + Value: value, + ReadOnly: false, + Evm: evm, + } + ret, gas, err = RunPrecompiledContract(p, input, gas, info) } 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. @@ -275,7 +288,15 @@ 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 = RunPrecompiledContract(p, input, gas) + info := &AdvancedPrecompileCall{ + PrecompileAddress: addr, + ActingAsAddress: caller.Address(), + Caller: caller.Address(), + Value: value, + ReadOnly: false, + Evm: evm, + } + ret, gas, err = RunPrecompiledContract(p, input, gas, info) } else { addrCopy := addr // Initialise a new contract and set the code that is to be used by the EVM. @@ -319,7 +340,16 @@ 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 = RunPrecompiledContract(p, input, gas) + caller := caller.(*Contract) + info := &AdvancedPrecompileCall{ + PrecompileAddress: addr, + ActingAsAddress: caller.Address(), + Caller: caller.CallerAddress, + Value: caller.Value(), + ReadOnly: false, + Evm: evm, + } + ret, gas, err = RunPrecompiledContract(p, input, gas, info) } else { addrCopy := addr // Initialise a new contract and make initialise the delegate values @@ -371,7 +401,15 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte } if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + info := &AdvancedPrecompileCall{ + PrecompileAddress: addr, + ActingAsAddress: addr, + Caller: caller.Address(), + Value: new(big.Int), + ReadOnly: true, + Evm: evm, + } + ret, gas, err = RunPrecompiledContract(p, input, gas, info) } 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'