Skip to content

Add hooks needed for ArbOS #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions consensus/clique/clique.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions consensus/ethash/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
32 changes: 31 additions & 1 deletion core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package core

import (
"errors"
"fmt"
"math"
"math/big"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
//
Expand All @@ -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
//
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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
Expand Down
31 changes: 30 additions & 1 deletion core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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))
Expand Down
46 changes: 42 additions & 4 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'
Expand Down