Skip to content
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

rework on panics in precompiles #418

Merged
merged 11 commits into from
Dec 27, 2022
35 changes: 23 additions & 12 deletions accounts/abi/bind/precompile_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Typically, custom codes are required in only those areas.
package precompile

import (
"encoding/json"
"math/big"
"errors"
"fmt"
Expand Down Expand Up @@ -75,6 +76,7 @@ var (
_ = big.NewInt
_ = strings.NewReader
_ = fmt.Printf
_ = json.Unmarshal
)

{{$contract := .Contract}}
Expand Down Expand Up @@ -148,7 +150,10 @@ func init() {
}
{{.Contract.Type}}ABI = parsed

{{.Contract.Type}}Precompile = create{{.Contract.Type}}Precompile({{.Contract.Type}}Address)
{{.Contract.Type}}Precompile, err = create{{.Contract.Type}}Precompile({{.Contract.Type}}Address)
if err != nil {
panic(err)
}
}

// New{{.Contract.Type}}Config returns a config for a network upgrade at [blockTimestamp] that enables
Expand Down Expand Up @@ -198,9 +203,10 @@ func (c *{{.Contract.Type}}Config) Address() common.Address {
}

// Configure configures [state] with the initial configuration.
func (c *{{.Contract.Type}}Config) Configure(_ ChainConfig, state StateDB, _ BlockContext) {
func (c *{{.Contract.Type}}Config) Configure(_ ChainConfig, state StateDB, _ BlockContext) error {
{{if .Contract.AllowList}}c.AllowListConfig.Configure(state, {{.Contract.Type}}Address){{end}}
// CUSTOM CODE STARTS HERE
return nil
}

// Contract returns the singleton stateful precompiled contract to be used for {{.Contract.Type}}.
Expand Down Expand Up @@ -404,27 +410,32 @@ func {{decapitalise $contract.Type}}Fallback (accessibleState PrecompileAccessib

// create{{.Contract.Type}}Precompile returns a StatefulPrecompiledContract with getters and setters for the precompile.
{{if .Contract.AllowList}} // Access to the getters/setters is controlled by an allow list for [precompileAddr].{{end}}
func create{{.Contract.Type}}Precompile(precompileAddr common.Address) StatefulPrecompiledContract {
func create{{.Contract.Type}}Precompile(precompileAddr common.Address) (StatefulPrecompiledContract, error) {
var functions []*statefulPrecompileFunction
{{- if .Contract.AllowList}}
functions = append(functions, createAllowListFunctions(precompileAddr)...)
{{- end}}

{{range .Contract.Funcs}}
method{{.Normalized.Name}}, ok := {{$contract.Type}}ABI.Methods["{{.Original.Name}}"]
if !ok{
panic("given method does not exist in the ABI")
abiFunctionMap := map[string]RunStatefulPrecompileFunc{
{{- range .Contract.Funcs}}
"{{.Original.Name}}": {{decapitalise .Normalized.Name}},
{{- end}}
}

for name, function := range abiFunctionMap {
method, ok := {{$contract.Type}}ABI.Methods[name]
if !ok {
return nil, fmt.Errorf("given method (%s) does not exist in the ABI", name)
}
functions = append(functions, newStatefulPrecompileFunction(method.ID, function))
}
functions = append(functions, newStatefulPrecompileFunction(method{{.Normalized.Name}}.ID, {{decapitalise .Normalized.Name}}))
{{end}}

{{- if .Contract.Fallback}}
// Construct the contract with the fallback function.
contract := newStatefulPrecompileWithFunctionSelectors({{decapitalise $contract.Type}}Fallback, functions)
return NewStatefulPrecompileContract({{decapitalise $contract.Type}}Fallback, functions)
{{- else}}
// Construct the contract with no fallback function.
contract := newStatefulPrecompileWithFunctionSelectors(nil, functions)
return NewStatefulPrecompileContract(nil, functions)
{{- end}}
return contract
}
`
5 changes: 4 additions & 1 deletion core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,10 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
}

// Configure any stateful precompiles that should be enabled in the genesis.
g.Config.CheckConfigurePrecompiles(nil, types.NewBlockWithHeader(head), statedb)
err = g.Config.ConfigurePrecompiles(nil, types.NewBlockWithHeader(head), statedb)
if err != nil {
panic(fmt.Sprintf("unable to configure precompiles in genesis block: %v", err))
}

// Do custom allocation after airdrop in case an address shows up in standard
// allocation
Expand Down
7 changes: 6 additions & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/ava-labs/subnet-evm/params"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
)

// StateProcessor is a basic Processor, which takes care of transitioning
Expand Down Expand Up @@ -78,7 +79,11 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state
)

// Configure any stateful precompiles that should go into effect during this block.
p.config.CheckConfigurePrecompiles(new(big.Int).SetUint64(parent.Time), block, statedb)
err := p.config.ConfigurePrecompiles(new(big.Int).SetUint64(parent.Time), block, statedb)
if err != nil {
log.Error("failed to process the state changes", "err", err)
ceyonur marked this conversation as resolved.
Show resolved Hide resolved
return nil, nil, 0, err
}

blockContext := NewEVMBlockContext(header, p.bc, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
Expand Down
6 changes: 5 additions & 1 deletion miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,11 @@ func (w *worker) commitNewWork() (*types.Block, error) {
return nil, fmt.Errorf("failed to create new current environment: %w", err)
}
// 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)
err = w.chainConfig.ConfigurePrecompiles(new(big.Int).SetUint64(parent.Time()), types.NewBlockWithHeader(header), env.state)
if err != nil {
log.Error("failed to commit new work", "err", err)
ceyonur marked this conversation as resolved.
Show resolved Hide resolved
return nil, err
}

// Fill the block with all available pending transactions.
pending := w.eth.TxPool().Pending(true)
Expand Down
9 changes: 6 additions & 3 deletions params/precompile_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,13 @@ func (c *ChainConfig) EnabledStatefulPrecompiles(blockTimestamp *big.Int) []prec
return statefulPrecompileConfigs
}

// CheckConfigurePrecompiles checks if any of the precompiles specified by the chain config are enabled or disabled by the block
// ConfigurePrecompiles checks if any of the precompiles specified by the chain config are enabled or disabled by the block
// transition from [parentTimestamp] to the timestamp set in [blockContext]. If this is the case, it calls [Configure]
// or [Deconfigure] 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) {
func (c *ChainConfig) ConfigurePrecompiles(parentTimestamp *big.Int, blockContext precompile.BlockContext, statedb precompile.StateDB) error {
blockTimestamp := blockContext.Timestamp()
for _, address := range precompile.UsedAddresses { // Note: configure precompiles in a deterministic order.
for _, config := range c.getActivatingPrecompileConfigs(parentTimestamp, blockTimestamp, address, c.PrecompileUpgrades) {
Expand All @@ -285,8 +285,11 @@ func (c *ChainConfig) CheckConfigurePrecompiles(parentTimestamp *big.Int, blockC
statedb.Finalise(true)
} else {
log.Info("Activating new precompile", "precompileAddress", address, "config", config)
precompile.Configure(c, blockContext, config, statedb)
if err := precompile.Configure(c, blockContext, config, statedb); err != nil {
return fmt.Errorf("could not configure precompile, precompileAddress: %s, reason: %w", address, err)
}
}
}
}
return nil
}
10 changes: 8 additions & 2 deletions precompile/allow_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,14 @@ type AllowListConfig struct {

// Configure initializes the address space of [precompileAddr] by initializing the role of each of
// the addresses in [AllowListAdmins].
func (c *AllowListConfig) Configure(state StateDB, precompileAddr common.Address) {
func (c *AllowListConfig) Configure(state StateDB, precompileAddr common.Address) error {
for _, enabledAddr := range c.EnabledAddresses {
setAllowListRole(state, precompileAddr, enabledAddr, AllowListEnabled)
}
for _, adminAddr := range c.AllowListAdmins {
setAllowListRole(state, precompileAddr, adminAddr, AllowListAdmin)
}
return nil
}

// Equal returns true iff [other] has the same admins in the same order in its allow list.
Expand Down Expand Up @@ -219,7 +220,12 @@ func createReadAllowList(precompileAddr common.Address) RunStatefulPrecompileFun
func createAllowListPrecompile(precompileAddr common.Address) StatefulPrecompiledContract {
// Construct the contract with no fallback function.
allowListFuncs := createAllowListFunctions(precompileAddr)
contract := newStatefulPrecompileWithFunctionSelectors(nil, allowListFuncs)
contract, err := NewStatefulPrecompileContract(nil, allowListFuncs)
// TODO Change this to be returned as an error after refactoring this precompile
// to use the new precompile template.
if err != nil {
panic(err)
}
return contract
}

Expand Down
8 changes: 4 additions & 4 deletions precompile/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ type statefulPrecompileWithFunctionSelectors struct {
functions map[string]*statefulPrecompileFunction
}

// newStatefulPrecompileWithFunctionSelectors generates new StatefulPrecompile using [functions] as the available functions and [fallback]
// NewStatefulPrecompileContract 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.
func newStatefulPrecompileWithFunctionSelectors(fallback RunStatefulPrecompileFunc, functions []*statefulPrecompileFunction) StatefulPrecompiledContract {
func NewStatefulPrecompileContract(fallback RunStatefulPrecompileFunc, functions []*statefulPrecompileFunction) (StatefulPrecompiledContract, error) {
// Construct the contract and populate [functions].
contract := &statefulPrecompileWithFunctionSelectors{
fallback: fallback,
Expand All @@ -109,12 +109,12 @@ func newStatefulPrecompileWithFunctionSelectors(fallback RunStatefulPrecompileFu
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))
return nil, fmt.Errorf("cannot create stateful precompile with duplicated function selector: %q", function.selector)
}
contract.functions[string(function.selector)] = function
}

return contract
return contract, nil
}

// Run selects the function using the 4 byte function selector at the start of the input and executes the underlying function on the
Expand Down
4 changes: 2 additions & 2 deletions precompile/contract_deployer_allow_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ func (c *ContractDeployerAllowListConfig) Address() common.Address {
}

// Configure configures [state] with the desired admins based on [c].
func (c *ContractDeployerAllowListConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) {
c.AllowListConfig.Configure(state, ContractDeployerAllowListAddress)
func (c *ContractDeployerAllowListConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) error {
return c.AllowListConfig.Configure(state, ContractDeployerAllowListAddress)
}

// Contract returns the singleton stateful precompiled contract to be used for the allow list.
Expand Down
15 changes: 10 additions & 5 deletions precompile/contract_native_minter.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,15 @@ func (c *ContractNativeMinterConfig) Address() common.Address {
}

// Configure configures [state] with the desired admins based on [c].
func (c *ContractNativeMinterConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) {
func (c *ContractNativeMinterConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) error {
for to, amount := range c.InitialMint {
if amount != nil {
bigIntAmount := (*big.Int)(amount)
state.AddBalance(to, bigIntAmount)
}
}

c.AllowListConfig.Configure(state, ContractNativeMinterAddress)
return c.AllowListConfig.Configure(state, ContractNativeMinterAddress)
}

// Contract returns the singleton stateful precompiled contract to be used for the native minter.
Expand Down Expand Up @@ -157,12 +157,12 @@ func SetContractNativeMinterStatus(stateDB StateDB, address common.Address, role
func PackMintInput(address common.Address, amount *big.Int) ([]byte, error) {
// function selector (4 bytes) + input(hash for address + hash for amount)
res := make([]byte, selectorLen+mintInputLen)
packOrderedHashesWithSelector(res, mintSignature, []common.Hash{
err := packOrderedHashesWithSelector(res, mintSignature, []common.Hash{
address.Hash(),
common.BigToHash(amount),
})

return res, nil
return res, err
}

// UnpackMintInput attempts to unpack [input] into the arguments to the mint precompile
Expand Down Expand Up @@ -217,6 +217,11 @@ func createNativeMinterPrecompile(precompileAddr common.Address) StatefulPrecomp

enabledFuncs = append(enabledFuncs, mintFunc)
// Construct the contract with no fallback function.
contract := newStatefulPrecompileWithFunctionSelectors(nil, enabledFuncs)
contract, err := NewStatefulPrecompileContract(nil, enabledFuncs)
// Change this to be returned as an error after refactoring this precompile
// to use the new precompile template.
if err != nil {
panic(err)
}
return contract
}
34 changes: 21 additions & 13 deletions precompile/fee_config_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,20 +109,20 @@ func (c *FeeConfigManagerConfig) Equal(s StatefulPrecompileConfig) bool {
}

// Configure configures [state] with the desired admins based on [c].
func (c *FeeConfigManagerConfig) Configure(chainConfig ChainConfig, state StateDB, blockContext BlockContext) {
func (c *FeeConfigManagerConfig) Configure(chainConfig ChainConfig, state StateDB, blockContext BlockContext) error {
// Store the initial fee config into the state when the fee config manager activates.
if c.InitialFeeConfig != nil {
if err := StoreFeeConfig(state, *c.InitialFeeConfig, blockContext); err != nil {
// This should not happen since we already checked this config with Verify()
panic(fmt.Sprintf("invalid feeConfig provided: %s", err))
return fmt.Errorf("cannot configure given initial fee config: %w", err)
}
} else {
if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil {
// This should not happen since we already checked the chain config in the genesis creation.
panic(fmt.Sprintf("fee config should have been verified in genesis: %s", err))
return fmt.Errorf("cannot configure fee config in chain config: %w", err)
}
}
c.AllowListConfig.Configure(state, FeeConfigManagerAddress)
return c.AllowListConfig.Configure(state, FeeConfigManagerAddress)
}

// Contract returns the singleton stateful precompiled contract to be used for the fee manager.
Expand Down Expand Up @@ -171,16 +171,16 @@ func PackGetLastChangedAtInput() []byte {
// PackFeeConfig packs [feeConfig] without the selector into the appropriate arguments for fee config operations.
func PackFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) {
// input(feeConfig)
return packFeeConfigHelper(feeConfig, false), nil
return packFeeConfigHelper(feeConfig, false)
}

// PackSetFeeConfig packs [feeConfig] with the selector into the appropriate arguments for setting fee config operations.
func PackSetFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) {
// function selector (4 bytes) + input(feeConfig)
return packFeeConfigHelper(feeConfig, true), nil
return packFeeConfigHelper(feeConfig, true)
}

func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) []byte {
func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) ([]byte, error) {
hashes := []common.Hash{
common.BigToHash(feeConfig.GasLimit),
common.BigToHash(new(big.Int).SetUint64(feeConfig.TargetBlockRate)),
Expand All @@ -194,13 +194,13 @@ func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) []byt

if useSelector {
res := make([]byte, len(setFeeConfigSignature)+feeConfigInputLen)
packOrderedHashesWithSelector(res, setFeeConfigSignature, hashes)
return res
err := packOrderedHashesWithSelector(res, setFeeConfigSignature, hashes)
return res, err
}

res := make([]byte, len(hashes)*common.HashLength)
packOrderedHashes(res, hashes)
return res
err := packOrderedHashes(res, hashes)
return res, err
}

// UnpackFeeConfigInput attempts to unpack [input] into the arguments to the fee config precompile
Expand Down Expand Up @@ -231,6 +231,7 @@ func UnpackFeeConfigInput(input []byte) (commontype.FeeConfig, error) {
case blockGasCostStepKey:
feeConfig.BlockGasCostStep = new(big.Int).SetBytes(packedElement)
default:
// This should never encounter an unknown fee config key
panic(fmt.Sprintf("unknown fee config key: %d", i))
}
}
Expand Down Expand Up @@ -260,6 +261,7 @@ func GetStoredFeeConfig(stateDB StateDB) commontype.FeeConfig {
case blockGasCostStepKey:
feeConfig.BlockGasCostStep = new(big.Int).Set(val.Big())
default:
// This should never encounter an unknown fee config key
panic(fmt.Sprintf("unknown fee config key: %d", i))
}
}
Expand All @@ -275,7 +277,7 @@ func GetFeeConfigLastChangedAt(stateDB StateDB) *big.Int {
// A validation on [feeConfig] is done before storing.
func StoreFeeConfig(stateDB StateDB, feeConfig commontype.FeeConfig, blockContext BlockContext) error {
if err := feeConfig.Verify(); err != nil {
return err
return fmt.Errorf("cannot verify fee config: %w", err)
}

for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ {
Expand All @@ -298,6 +300,7 @@ func StoreFeeConfig(stateDB StateDB, feeConfig commontype.FeeConfig, blockContex
case blockGasCostStepKey:
input = common.BigToHash(feeConfig.BlockGasCostStep)
default:
// This should never encounter an unknown fee config key
panic(fmt.Sprintf("unknown fee config key: %d", i))
}
stateDB.SetState(FeeConfigManagerAddress, common.Hash{byte(i)}, input)
Expand Down Expand Up @@ -386,6 +389,11 @@ func createFeeConfigManagerPrecompile(precompileAddr common.Address) StatefulPre

feeConfigManagerFunctions = append(feeConfigManagerFunctions, setFeeConfigFunc, getFeeConfigFunc, getFeeConfigLastChangedAtFunc)
// Construct the contract with no fallback function.
contract := newStatefulPrecompileWithFunctionSelectors(nil, feeConfigManagerFunctions)
contract, err := NewStatefulPrecompileContract(nil, feeConfigManagerFunctions)
// TODO Change this to be returned as an error after refactoring this precompile
// to use the new precompile template.
if err != nil {
panic(err)
}
return contract
}
Loading