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
6 changes: 5 additions & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,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)
// ASK: Should we panic instead?
if err != nil {
return nil, nil, 0, fmt.Errorf("could not configure precompiles: %w", err)
}
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved

blockContext := NewEVMBlockContext(header, p.bc, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
Expand Down
5 changes: 4 additions & 1 deletion miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,10 @@ 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 {
return nil, fmt.Errorf("failed to configure precompiles: %w", err)
}
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved

// 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 @@ -346,13 +346,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 _, key := range precompileKeys { // Note: configure precompiles in a deterministic order.
for _, config := range c.getActivatingPrecompileConfigs(parentTimestamp, blockTimestamp, key, c.PrecompileUpgrades) {
Expand All @@ -368,8 +368,11 @@ func (c *ChainConfig) CheckConfigurePrecompiles(parentTimestamp *big.Int, blockC
statedb.Finalise(true)
} else {
log.Info("Activating new precompile", "name", key, "config", config)
precompile.Configure(c, blockContext, config, statedb)
if err := precompile.Configure(c, blockContext, config, statedb); err != nil {
return 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)
// Change this to be returned as an error after refactoring this precompile
// to use the new precompile template.
ceyonur marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
panic(err)
}
return contract
}

Expand Down
11 changes: 7 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,15 @@ 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)
}
if function == nil {
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
return nil, fmt.Errorf("cannot create stateful precompile with nil 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
}
32 changes: 20 additions & 12 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("invalid feeConfig provided: %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("invalid feeConfig provided in chainConfig: %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 not ever happen. keep this as panic.
ceyonur marked this conversation as resolved.
Show resolved Hide resolved
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 not ever happen. keep this as panic.
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
panic(fmt.Sprintf("unknown fee config key: %d", i))
}
}
Expand Down Expand Up @@ -298,6 +300,7 @@ func StoreFeeConfig(stateDB StateDB, feeConfig commontype.FeeConfig, blockContex
case blockGasCostStepKey:
input = common.BigToHash(feeConfig.BlockGasCostStep)
default:
// this should not ever happen. keep this as panic.
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
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)
// Change this to be returned as an error after refactoring this precompile
ceyonur marked this conversation as resolved.
Show resolved Hide resolved
// to use the new precompile template.
if err != nil {
panic(err)
}
return contract
}
Loading