Skip to content

Commit

Permalink
Merge pull request ethereum#2 from OffchainLabs/hooks
Browse files Browse the repository at this point in the history
Add hooks needed for ArbOS
  • Loading branch information
PlasmaPower committed Oct 18, 2021
2 parents cd8f5f4 + 1c2c01e commit 9aae462
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 16 deletions.
4 changes: 2 additions & 2 deletions consensus/clique/clique.go
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
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
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
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
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
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
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
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

0 comments on commit 9aae462

Please sign in to comment.