Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d6d999a
state modifications as network upgrade
Feb 28, 2023
f8edd2c
remove contract deploy
Feb 28, 2023
e18dec2
Merge branch 'master' of github.com:ava-labs/subnet-evm into state-up…
Feb 28, 2023
0c1eedd
use consolidated struct
Feb 28, 2023
6afe5fc
add support for genesis and test
Feb 28, 2023
2a59979
test improvements
Feb 28, 2023
5c617bc
add configuration tests
Feb 28, 2023
d02102f
nit
Feb 28, 2023
a97ee33
lint
Feb 28, 2023
7ea5d94
nit
Feb 28, 2023
3a1cf82
make json compatible w/ genesis style
Feb 28, 2023
6e921be
add support for neg. balance change
Feb 28, 2023
c6bb954
avoid state upgrades in genesis
Mar 1, 2023
62760ee
Revert apply precompile func name change
aaronbuchwald Mar 1, 2023
bc3a8c7
Update comment
aaronbuchwald Mar 1, 2023
fd25109
Add state upgrade test case
aaronbuchwald Mar 1, 2023
ca2c889
Cleanup
aaronbuchwald Mar 1, 2023
fae7aa3
remove codegen comment
aaronbuchwald Mar 1, 2023
3db8adc
add EIP158 check
Mar 1, 2023
5624d17
Merge branch 'master' of github.com:ava-labs/subnet-evm into state-up…
Mar 1, 2023
9ef281e
start v0.4.12 release cycle
Mar 2, 2023
a111d22
Merge branch 'start-release-cycle' of github.com:ava-labs/subnet-evm …
Mar 2, 2023
2351e24
add setup action to lint ci for proper go version
Mar 2, 2023
0c6d8c3
fix code interpretation as hex
Mar 3, 2023
f6e6c1b
fix test
Mar 3, 2023
7dc8742
bump lint action runner
Mar 3, 2023
0f3dedf
add log
Mar 3, 2023
923ecf7
add log
Mar 3, 2023
f03b40e
add genesis code test case
Mar 4, 2023
eca4551
fix typo
Mar 4, 2023
0cb03a2
bump avalanchego version
Mar 4, 2023
281d3f6
Merge branch 'master' of github.com:ava-labs/subnet-evm into state-up…
Mar 7, 2023
95bb290
fix merge
Mar 7, 2023
c53b094
add check for timestamp 0
Mar 8, 2023
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
25 changes: 25 additions & 0 deletions 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/ava-labs/subnet-evm/precompile/contract"
"github.com/ava-labs/subnet-evm/precompile/modules"
"github.com/ava-labs/subnet-evm/stateupgrade"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
Expand Down Expand Up @@ -86,6 +87,12 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state
log.Error("failed to configure precompiles processing block", "hash", block.Hash(), "number", block.NumberU64(), "timestamp", block.Time(), "err", err)
return nil, nil, 0, err
}
// Configure any state upgrades that should go into effect during this block.
err = ApplyStateUpgrades(p.config, new(big.Int).SetUint64(parent.Time), p.bc, header, statedb, cfg)
if err != nil {
log.Error("failed to configure state upgrades processing block", "hash", block.Hash(), "number", block.NumberU64(), "timestamp", block.Time(), "err", err)
return nil, nil, 0, err
}

blockContext := NewEVMBlockContext(header, p.bc, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
Expand Down Expand Up @@ -218,3 +225,21 @@ func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *big.Int,
}
return nil
}

// ApplyStateUpgrades checks if any of the state upgrades specified by the chain config are activated by the block
// transition from [parentTimestamp] to the timestamp set in [header]. If this is the case, it calls [Configure]
// to apply the necessary state transitions for the upgrade.
// This function is called:
// - during block processing to update the state before processing the given block.
// - during block producing to apply the precompile upgrades before producing the block.
func ApplyStateUpgrades(c *params.ChainConfig, parentTimestamp *big.Int, bc ChainContext, header *types.Header, statedb *state.StateDB, cfg vm.Config) error {
blockContext := NewEVMBlockContext(header, bc, nil)
// Apply state upgrades
for _, upgrade := range c.GetActivatingStateUpgrades(parentTimestamp, blockContext.Timestamp(), c.StateUpgrades) {
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, c, cfg)
if err := stateupgrade.Configure(&upgrade, &blockContext, statedb, vmenv); err != nil {
return fmt.Errorf("could not configure state upgrade, reason: %w", err)
}
}
return nil
}
8 changes: 8 additions & 0 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,5 +604,13 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2)
}

// CreateAt creates a new contract using code as deployment code,
// and deploys it to the specified address. For use by the state upgrade.
func (evm *EVM) CreateAt(contractAddr common.Address, callerAddr common.Address, code []byte, gas uint64, value *big.Int) ([]byte, common.Address, uint64, error) {
codeAndHash := &codeAndHash{code: code}
caller := AccountRef(callerAddr)
return evm.create(caller, codeAndHash, gas, value, contractAddr, CREATE)
}

// ChainConfig returns the environment's chain configuration
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
6 changes: 6 additions & 0 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ func (w *worker) commitNewWork() (*types.Block, error) {
log.Error("failed to configure precompiles mining new block", "parent", parent.Hash(), "number", header.Number, "timestamp", header.Time, "err", err)
return nil, err
}
// Configure any state upgrades that should go into effect during this block.
err = core.ApplyStateUpgrades(w.chainConfig, new(big.Int).SetUint64(parent.Time()), w.chain, header, env.state, *w.chain.GetVMConfig())
if err != nil {
log.Error("failed to configure state upgrades mining new block", "parent", parent.Hash(), "number", header.Number, "timestamp", header.Time, "err", err)
return nil, err
}

// Fill the block with all available pending transactions.
pending := w.eth.TxPool().Pending(true)
Expand Down
17 changes: 15 additions & 2 deletions params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ type UpgradeConfig struct {
// forks must be present or upgradeBytes will be rejected.
NetworkUpgrades *NetworkUpgrades `json:"networkUpgrades,omitempty"`

// Config for modifying state as a network upgrade.
StateUpgrades []StateUpgrade `json:"stateUpgrades,omitempty"`

// Config for enabling and disabling precompiles as network upgrades.
PrecompileUpgrades []PrecompileUpgrade `json:"precompileUpgrades,omitempty"`
}
Expand Down Expand Up @@ -323,7 +326,7 @@ func (c *ChainConfig) IsSubnetEVM(blockTimestamp *big.Int) bool {

// IsPrecompileEnabled returns whether precompile with [address] is enabled at [blockTimestamp].
func (c *ChainConfig) IsPrecompileEnabled(address common.Address, blockTimestamp *big.Int) bool {
config := c.GetActivePrecompileConfig(address, blockTimestamp)
config := c.getActivePrecompileConfig(address, blockTimestamp)
return config != nil && !config.IsDisabled()
}

Expand Down Expand Up @@ -357,6 +360,11 @@ func (c *ChainConfig) Verify() error {
return fmt.Errorf("invalid precompile upgrades: %w", err)
}

// Verify the state upgrades are internally consistent given the existing chainConfig.
if err := c.verifyStateUpgrades(); err != nil {
return fmt.Errorf("invalid state upgrades: %w", err)
}

return nil
}

Expand Down Expand Up @@ -491,6 +499,11 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, lastHeight *big.Int,
return err
}

// Check that the state upgrades on the new config are compatible with the existing state upgrade config.
if err := c.CheckStateUpgradesCompatible(newcfg.StateUpgrades, lastTimestamp); err != nil {
return err
}

// TODO verify that the fee config is fully compatible between [c] and [newcfg].
return nil
}
Expand Down Expand Up @@ -596,7 +609,7 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules {
// Initialize the stateful precompiles that should be enabled at [blockTimestamp].
rules.ActivePrecompiles = make(map[common.Address]precompileconfig.Config)
for _, module := range modules.RegisteredModules() {
if config := c.GetActivePrecompileConfig(module.Address, blockTimestamp); config != nil && !config.IsDisabled() {
if config := c.getActivePrecompileConfig(module.Address, blockTimestamp); config != nil && !config.IsDisabled() {
rules.ActivePrecompiles[module.Address] = config
}
}
Expand Down
8 changes: 4 additions & 4 deletions params/precompile_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,16 +269,16 @@ func TestGetPrecompileConfig(t *testing.T) {
deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(10), nil, nil),
}

deployerConfig := config.GetActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(0))
deployerConfig := config.getActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(0))
require.Nil(deployerConfig)

deployerConfig = config.GetActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(10))
deployerConfig = config.getActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(10))
require.NotNil(deployerConfig)

deployerConfig = config.GetActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(11))
deployerConfig = config.getActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(11))
require.NotNil(deployerConfig)

txAllowListConfig := config.GetActivePrecompileConfig(txallowlist.ContractAddress, big.NewInt(0))
txAllowListConfig := config.getActivePrecompileConfig(txallowlist.ContractAddress, big.NewInt(0))
require.Nil(txAllowListConfig)
}

Expand Down
78 changes: 71 additions & 7 deletions params/precompile_upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ func (u *PrecompileUpgrade) MarshalJSON() ([]byte, error) {
// verifyPrecompileUpgrades checks [c.PrecompileUpgrades] is well formed:
// - [upgrades] must specify exactly one key per PrecompileUpgrade
// - the specified blockTimestamps must monotonically increase
// - the specified blockTimestamps must be compatible with those
// specified in the chainConfig by genesis.
// - the specified blockTimestamps must be compatible with those specified in the chainConfig by genesis.
// - check a precompile is disabled before it is re-enabled
func (c *ChainConfig) verifyPrecompileUpgrades() error {
// Store this struct to keep track of the last upgrade for each precompile key.
Expand Down Expand Up @@ -149,18 +148,33 @@ func (c *ChainConfig) verifyPrecompileUpgrades() error {
return nil
}

// GetActivePrecompileConfig returns the most recent precompile config corresponding to [address].
// verifyStateUpgrades checks [c.StateUpgrades] is well formed:
// - the specified blockTimestamps must monotonically increase
func (c *ChainConfig) verifyStateUpgrades() error {
var previousUpgradeTimestamp *big.Int
for i, upgrade := range c.StateUpgrades {
upgradeTimestamp := upgrade.blockTimestamp
// Verify specified timestamps are strictly monotonically increasing.
if previousUpgradeTimestamp != nil && upgradeTimestamp.Cmp(previousUpgradeTimestamp) <= 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we decided not to use state upgrades at genesis, should we check if timestamp != 0? cc @aaronbuchwald

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya I think we should add this check since we are not allowing them in the genesis

return fmt.Errorf("StateUpgrade[%d]: config block timestamp (%v) <= previous timestamp (%v)", i, upgradeTimestamp, previousUpgradeTimestamp)
}
previousUpgradeTimestamp = upgradeTimestamp
}
return nil
}

// getActivePrecompileConfig returns the most recent precompile config corresponding to [address].
// If none have occurred, returns nil.
func (c *ChainConfig) GetActivePrecompileConfig(address common.Address, blockTimestamp *big.Int) precompileconfig.Config {
func (c *ChainConfig) getActivePrecompileConfig(address common.Address, blockTimestamp *big.Int) precompileconfig.Config {
configs := c.GetActivatingPrecompileConfigs(address, nil, blockTimestamp, c.PrecompileUpgrades)
if len(configs) == 0 {
return nil
}
return configs[len(configs)-1] // return the most recent config
}

// GetActivatingPrecompileConfigs returns all upgrades configured to activate during the state transition from a block with timestamp [from]
// to a block with timestamp [to].
// GetActivatingPrecompileConfigs returns all precompile upgrades configured to activate during the
// state transition from a block with timestamp [from] to a block with timestamp [to].
func (c *ChainConfig) GetActivatingPrecompileConfigs(address common.Address, from *big.Int, to *big.Int, upgrades []PrecompileUpgrade) []precompileconfig.Config {
// Get key from address.
module, ok := modules.GetPrecompileModuleByAddress(address)
Expand Down Expand Up @@ -188,6 +202,18 @@ func (c *ChainConfig) GetActivatingPrecompileConfigs(address common.Address, fro
return configs
}

// GetActivatingStateUpgrades returns all state upgrades configured to activate during the
// state transition from a block with timestamp [from] to a block with timestamp [to].
func (c *ChainConfig) GetActivatingStateUpgrades(from *big.Int, to *big.Int, upgrades []StateUpgrade) []StateUpgrade {
activating := make([]StateUpgrade, 0)
for _, upgrade := range upgrades {
if utils.IsForkTransition(upgrade.blockTimestamp, from, to) {
activating = append(activating, upgrade)
}
}
return activating
}

// CheckPrecompilesCompatible checks if [precompileUpgrades] are compatible with [c] at [headTimestamp].
// Returns a ConfigCompatError if upgrades already activated at [headTimestamp] are missing from
// [precompileUpgrades]. Upgrades not already activated may be modified or absent from [precompileUpgrades].
Expand Down Expand Up @@ -246,11 +272,49 @@ func (c *ChainConfig) checkPrecompileCompatible(address common.Address, precompi
return nil
}

// CheckStateUpgradesCompatible checks if [stateUpgrades] are compatible with [c] at [headTimestamp].
func (c *ChainConfig) CheckStateUpgradesCompatible(stateUpgrades []StateUpgrade, lastTimestamp *big.Int) *ConfigCompatError {
// All active upgrades (from nil to [lastTimestamp]) must match.
activeUpgrades := c.GetActivatingStateUpgrades(nil, lastTimestamp, c.StateUpgrades)
newUpgrades := c.GetActivatingStateUpgrades(nil, lastTimestamp, stateUpgrades)

// Check activated upgrades are still present.
for i, upgrade := range activeUpgrades {
if len(newUpgrades) <= i {
// missing upgrade
return newCompatError(
fmt.Sprintf("missing StateUpgrade[%d]", i),
upgrade.blockTimestamp,
nil,
)
}
// All upgrades that have activated must be identical.
if !upgrade.Equal(&newUpgrades[i]) {
return newCompatError(
fmt.Sprintf("StateUpgrade[%d]", i),
upgrade.blockTimestamp,
newUpgrades[i].blockTimestamp,
)
}
}
// then, make sure newUpgrades does not have additional upgrades
// that are already activated. (cannot perform retroactive upgrade)
if len(newUpgrades) > len(activeUpgrades) {
return newCompatError(
fmt.Sprintf("cannot retroactively enable StateUpgrade[%d]", len(activeUpgrades)),
nil,
newUpgrades[len(activeUpgrades)].blockTimestamp, // this indexes to the first element in newUpgrades after the end of activeUpgrades
)
}

return nil
}

// EnabledStatefulPrecompiles returns current stateful precompile configs that are enabled at [blockTimestamp].
func (c *ChainConfig) EnabledStatefulPrecompiles(blockTimestamp *big.Int) Precompiles {
statefulPrecompileConfigs := make(Precompiles)
for _, module := range modules.RegisteredModules() {
if config := c.GetActivePrecompileConfig(module.Address, blockTimestamp); config != nil && !config.IsDisabled() {
if config := c.getActivePrecompileConfig(module.Address, blockTimestamp); config != nil && !config.IsDisabled() {
statefulPrecompileConfigs[module.ConfigKey] = config
}
}
Expand Down
44 changes: 44 additions & 0 deletions params/state_upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// (c) 2023 Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package params

import (
"math/big"
"reflect"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
)

type StateUpgrade struct {
blockTimestamp *big.Int

// Adds the specified amount to the balance of the specified address
AddToBalance map[common.Address]*math.HexOrDecimal256 `json:"addToBalance,omitempty"`

// Sets the specified storage slots of the specified addresses
// to the given values. Note that the value of common.Hash{} will
// remove the storage key
SetStorage map[common.Address]map[common.Hash]common.Hash `json:"setStorage,omitempty"`

// Sets the code of the specified contract to the given value
SetCode map[common.Address][]byte `json:"setCode,omitempty"`

// Deploys contracts with the specified creation bytecode to the
// specified addresses, instead of the normal rules for deriving
// the address of a created contract.
DeployContractTo []ContractDeploy `json:"deployContractTo,omitempty"`
}

type ContractDeploy struct {
DeployTo common.Address `json:"deployTo,omitempty"` // The address to deploy the contract to
Caller common.Address `json:"caller,omitempty"` // The address of the caller
Input []byte `json:"input,omitempty"` // The input bytecode to create the contract
Gas uint64 `json:"gas,omitempty"` // The gas to use when creating the contract
Value *big.Int `json:"value,omitempty"` // The value to send when creating the contract
}

func (s *StateUpgrade) Equal(other *StateUpgrade) bool {
return reflect.DeepEqual(s, other)
}
3 changes: 0 additions & 3 deletions precompile/contract/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,11 @@ type StateDB interface {
GetState(common.Address, common.Hash) common.Hash
SetState(common.Address, common.Hash, common.Hash)

SetCode(common.Address, []byte)

SetNonce(common.Address, uint64)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did we remove these functions from precompile?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They were not used, so we should keep the interfaces narrower when possible.
I believe it was used prior to the refactoring.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, makes sense but what if future precompiles want to use these functions? I'm ok either way

GetNonce(common.Address) uint64

GetBalance(common.Address) *big.Int
AddBalance(common.Address, *big.Int)
SubBalance(common.Address, *big.Int)

CreateAccount(common.Address)
Exist(common.Address) bool
Expand Down
35 changes: 35 additions & 0 deletions stateupgrade/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// (c) 2023 Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package stateupgrade

import (
"math/big"

"github.com/ethereum/go-ethereum/common"
)

// StateDB is the interface for accessing EVM state in state upgrades
type StateDB interface {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could also move stateupgrade under precompile or reuse interfaces there but I think it is slightly better to keep them separate

SetState(common.Address, common.Hash, common.Hash)
SetCode(common.Address, []byte)
AddBalance(common.Address, *big.Int)

CreateAccount(common.Address)
Exist(common.Address) bool

Snapshot() int
RevertToSnapshot(int)
}

// BlockContext defines an interface that provides information about the
// block that activates the state upgrade.
type BlockContext interface {
Number() *big.Int
Timestamp() *big.Int
}

// AccessibleState defines the interface exposed to state upgrades
type AccessibleState interface {
CreateAt(contractAddr common.Address, callerAddr common.Address, code []byte, gas uint64, value *big.Int) ([]byte, common.Address, uint64, error)
}
Loading