Skip to content

Commit

Permalink
Add handshake codec
Browse files Browse the repository at this point in the history
The goal of the this codec is to serialize `params.UpgradeConfig` in a
deterministic way, to be hashed later. This hash is going to be share by nodes
at handshake, so they can determine if they have the same upgrade config.

This PR leverages the newly introduced support to serilize Maps
ava-labs/avalanchego#1790, which are deterministic (the
values are sorted by keys before serializing)
  • Loading branch information
nytzuga committed Aug 14, 2023
1 parent 309daad commit de94050
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 22 deletions.
16 changes: 8 additions & 8 deletions params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,17 +448,17 @@ func (c *ChainConfig) Verify() error {
return nil
}

type fork struct {
name string
block *big.Int // some go-ethereum forks use block numbers
timestamp *uint64 // Avalanche forks use timestamps
optional bool // if true, the fork may be nil and next fork is still allowed
type Fork struct {
name string `serialize:"true"`
block *big.Int `serialize:"true"` // some go-ethereum forks use block numbers
timestamp *uint64 `serialize:"true"` // Avalanche forks use timestamps
optional bool `serialize:"true"` // if true, the fork may be nil and next fork is still allowed
}

// CheckConfigForkOrder checks that we don't "skip" any forks, geth isn't pluggable enough
// to guarantee that forks can be implemented in a different order than on official networks
func (c *ChainConfig) CheckConfigForkOrder() error {
ethForks := []fork{
ethForks := []Fork{
{name: "homesteadBlock", block: c.HomesteadBlock},
{name: "eip150Block", block: c.EIP150Block},
{name: "eip155Block", block: c.EIP155Block},
Expand Down Expand Up @@ -497,8 +497,8 @@ func (c *ChainConfig) CheckConfigForkOrder() error {

// checkForks checks that forks are enabled in order and returns an error if not
// [blockFork] is true if the fork is a block number fork, false if it is a timestamp fork
func checkForks(forks []fork, blockFork bool) error {
lastFork := fork{}
func checkForks(forks []Fork, blockFork bool) error {
lastFork := Fork{}
for _, cur := range forks {
if blockFork && cur.block != nil && common.Big0.Cmp(cur.block) != 0 {
return errNonGenesisForkByHeight
Expand Down
16 changes: 7 additions & 9 deletions params/network_upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ func (m *MandatoryNetworkUpgrades) CheckMandatoryCompatible(newcfg *MandatoryNet
return nil
}

func (m *MandatoryNetworkUpgrades) mandatoryForkOrder() []fork {
return []fork{
func (m *MandatoryNetworkUpgrades) mandatoryForkOrder() []Fork {
return []Fork{
{name: "subnetEVMTimestamp", timestamp: m.SubnetEVMTimestamp},
{name: "dUpgradeTimestamp", timestamp: m.DUpgradeTimestamp},
}
Expand All @@ -64,16 +64,14 @@ func GetMandatoryNetworkUpgrades(networkID uint32) MandatoryNetworkUpgrades {
}
}

// OptionalNetworkUpgrades includes overridable and optional Subnet-EVM network upgrades.
// These can be specified in genesis and upgrade configs.
// Timestamps can be different for each subnet network.
// TODO: once we add the first optional upgrade here, we should uncomment TestVMUpgradeBytesOptionalNetworkUpgrades
type OptionalNetworkUpgrades struct{}
type OptionalNetworkUpgrades struct {
Updates []Fork `json:"serialize,omitempty" serialize:"true"`
}

func (n *OptionalNetworkUpgrades) CheckOptionalCompatible(newcfg *OptionalNetworkUpgrades, time uint64) *ConfigCompatError {
return nil
}

func (n *OptionalNetworkUpgrades) optionalForkOrder() []fork {
return []fork{}
func (n *OptionalNetworkUpgrades) optionalForkOrder() []Fork {
return n.Updates
}
10 changes: 5 additions & 5 deletions params/state_upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ import (
// StateUpgrade describes the modifications to be made to the state during
// a state upgrade.
type StateUpgrade struct {
BlockTimestamp *uint64 `json:"blockTimestamp,omitempty"`
BlockTimestamp *uint64 `json:"blockTimestamp,omitempty" serialize:"true"`

// map from account address to the modification to be made to the account.
StateUpgradeAccounts map[common.Address]StateUpgradeAccount `json:"accounts"`
StateUpgradeAccounts map[common.Address]StateUpgradeAccount `json:"accounts" serialize:"true"`
}

// StateUpgradeAccount describes the modifications to be made to an account during
// a state upgrade.
type StateUpgradeAccount struct {
Code hexutil.Bytes `json:"code,omitempty"`
Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
BalanceChange *math.HexOrDecimal256 `json:"balanceChange,omitempty"`
Code hexutil.Bytes `json:"code,omitempty" serialize:"true"`
Storage map[common.Hash]common.Hash `json:"storage,omitempty" serialize:"true"`
BalanceChange *math.HexOrDecimal256 `json:"balanceChange,omitempty" serialize:"true"`
}

func (s *StateUpgrade) Equal(other *StateUpgrade) bool {
Expand Down
36 changes: 36 additions & 0 deletions plugin/evm/message/handshake/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// (c) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package handshake

import (
"github.com/ava-labs/avalanchego/codec"
"github.com/ava-labs/avalanchego/codec/linearcodec"
"github.com/ava-labs/avalanchego/utils/units"
"github.com/ava-labs/avalanchego/utils/wrappers"
)

const (
Version = uint16(0)
maxMessageSize = 1 * units.MiB
)

var (
Codec codec.Manager
)

func init() {
Codec = codec.NewManager(maxMessageSize)
c := linearcodec.NewDefault()

errs := wrappers.Errs{}
errs.Add(
c.RegisterType(UpgradeConfig{}),

Codec.RegisterCodec(Version, c),
)

if errs.Errored() {
panic(errs.Err)
}
}
116 changes: 116 additions & 0 deletions plugin/evm/message/handshake/upgrade_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package handshake

import (
"crypto/sha256"
"fmt"

"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile/modules"
"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
)

type PrecompileUpgrade struct {
StructName string `serialize:"true"`
Bytes []byte `serialize:"true"`
}

type UpgradeConfig struct {
OptionalNetworkUpgrades []params.Fork `serialize:"true"`

// Config for modifying state as a network upgrade.
StateUpgrades []params.StateUpgrade `serialize:"true"`

// Config for enabling and disabling precompiles as network upgrades.
PrecompileUpgrades []PrecompileUpgrade `serialize:"true"`
config params.UpgradeConfig
bytes []byte
}

func ParseUpgradeConfig(bytes []byte) (*UpgradeConfig, error) {
var config UpgradeConfig
version, err := Codec.Unmarshal(bytes, &config)
if err != nil {
return nil, err
}
if version != Version {
return nil, fmt.Errorf("Invalid version")
}

var PrecompileUpgrades []params.PrecompileUpgrade

for _, precompileUpgrade := range config.PrecompileUpgrades {
module, ok := modules.GetPrecompileModule(precompileUpgrade.StructName)
if !ok {
return nil, fmt.Errorf("unknown precompile config: %s", precompileUpgrade.StructName)
}
preCompile := module.MakeConfig()

version, err := Codec.Unmarshal(precompileUpgrade.Bytes, preCompile)
if version != Version {
return nil, fmt.Errorf("Invalid version")
}
if err != nil {
return nil, err
}
if Config, ok := preCompile.(precompileconfig.Config); ok {
PrecompileUpgrades = append(PrecompileUpgrades, params.PrecompileUpgrade{Config: Config})
} else {
return nil, fmt.Errorf("Error deserializing precompile %s", precompileUpgrade.StructName)
}
}

config.config = params.UpgradeConfig{
OptionalNetworkUpgrades: &params.OptionalNetworkUpgrades{config.OptionalNetworkUpgrades},
StateUpgrades: config.StateUpgrades,
PrecompileUpgrades: PrecompileUpgrades,
}
config.bytes = bytes

return &config, nil
}

func NewUpgradeConfig(config params.UpgradeConfig) (*UpgradeConfig, error) {
PrecompileUpgrades := make([]PrecompileUpgrade, 0)
for _, precompileConfig := range config.PrecompileUpgrades {
bytes, err := Codec.Marshal(Version, precompileConfig.Config)
if err != nil {
return nil, err
}
PrecompileUpgrades = append(PrecompileUpgrades, PrecompileUpgrade{
StructName: precompileConfig.Key(),
Bytes: bytes,
})
}

optionalNetworkUpgrades := make([]params.Fork, 0)
if config.OptionalNetworkUpgrades != nil {
optionalNetworkUpgrades = config.OptionalNetworkUpgrades.Updates
}

wrappedConfig := UpgradeConfig{
OptionalNetworkUpgrades: optionalNetworkUpgrades,
StateUpgrades: config.StateUpgrades,
PrecompileUpgrades: PrecompileUpgrades,
config: config,
bytes: make([]byte, 0),
}
bytes, err := Codec.Marshal(Version, wrappedConfig)
if err != nil {
return nil, err
}
wrappedConfig.bytes = bytes

return &wrappedConfig, nil
}

func (r *UpgradeConfig) Config() params.UpgradeConfig {
return r.config
}

func (r *UpgradeConfig) Bytes() []byte {
return r.bytes
}

func (r *UpgradeConfig) Hash() [32]byte {
return sha256.Sum256(r.bytes)
}
35 changes: 35 additions & 0 deletions plugin/evm/message/handshake/upgrade_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package handshake

import (
"testing"

"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile/contracts/nativeminter"
"github.com/stretchr/testify/assert"
)

func TestSerialize(t *testing.T) {
var t0 uint64 = 0
var t1 uint64 = 1
config, err := NewUpgradeConfig(params.UpgradeConfig{
PrecompileUpgrades: []params.PrecompileUpgrade{
{
Config: nativeminter.NewConfig(&t0, nil, nil, nil), // enable at genesis
},
{
Config: nativeminter.NewDisableConfig(&t1), // disable at timestamp 1
},
},
})
assert.NoError(t, err)

config2, err := ParseUpgradeConfig(config.Bytes())
assert.NoError(t, err)

config3, err := NewUpgradeConfig(config2.Config())
assert.NoError(t, err)

assert.Equal(t, config2, config3)
assert.Equal(t, config.Hash(), config2.Hash())
assert.Equal(t, config.Hash(), config3.Hash())
}

0 comments on commit de94050

Please sign in to comment.