diff --git a/plugin/evm/message/handshake/codec.go b/plugin/evm/message/handshake/codec.go new file mode 100644 index 0000000000..9b0cfb57d0 --- /dev/null +++ b/plugin/evm/message/handshake/codec.go @@ -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(UpgradeConfigInternal{}), + + Codec.RegisterCodec(Version, c), + ) + + if errs.Errored() { + panic(errs.Err) + } +} diff --git a/plugin/evm/message/handshake/sorted_marshal.go b/plugin/evm/message/handshake/sorted_marshal.go new file mode 100644 index 0000000000..a37e965d94 --- /dev/null +++ b/plugin/evm/message/handshake/sorted_marshal.go @@ -0,0 +1,53 @@ +package handshake + +import ( + "encoding/json" + "reflect" + "sort" +) + +func sortedMarshal(v interface{}) ([]byte, error) { + rv := reflect.ValueOf(v) + + if rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } + + switch rv.Kind() { + case reflect.Struct: + type field struct { + Name string + Value interface{} + } + + var fields []field + + for i := 0; i < rv.NumField(); i++ { + Name := rv.Type().Field(i).Name + Value := rv.Field(i).Interface() + fields = append(fields, field{Name, Value}) + } + + sort.Slice(fields, func(i, j int) bool { + return fields[i].Name < fields[j].Name + }) + + sortedMap := make(map[string]interface{}) + for _, f := range fields { + if f.Value != nil && reflect.ValueOf(f.Value).Kind() == reflect.Struct { + sortedNested, err := sortedMarshal(f.Value) + if err != nil { + return nil, err + } + sortedMap[f.Name] = json.RawMessage(sortedNested) + } else { + sortedMap[f.Name] = f.Value + } + } + + return json.Marshal(sortedMap) + default: + // There is nothing to sort for non struct + return json.Marshal(v) + } +} diff --git a/plugin/evm/message/handshake/upgrade_config.go b/plugin/evm/message/handshake/upgrade_config.go new file mode 100644 index 0000000000..3341240332 --- /dev/null +++ b/plugin/evm/message/handshake/upgrade_config.go @@ -0,0 +1,63 @@ +package handshake + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + + "github.com/ava-labs/subnet-evm/params" +) + +type UpgradeConfigInternal struct { + Bytes []byte `serialize:"true"` +} + +type UpgradeConfig struct { + config params.UpgradeConfig + bytes []byte +} + +func ParseUpgradeConfig(bytes []byte) (*UpgradeConfig, error) { + var internal UpgradeConfigInternal + version, err := Codec.Unmarshal(bytes, &internal) + if err != nil { + return nil, err + } + if version != Version { + return nil, fmt.Errorf("Invalid version") + } + + var config params.UpgradeConfig + + if err := json.Unmarshal(internal.Bytes, &config); err != nil { + return nil, err + } + + return &UpgradeConfig{config, bytes}, nil +} + +func NewUpgradeConfig(config params.UpgradeConfig) (*UpgradeConfig, error) { + Bytes, err := sortedMarshal(config) + if err != nil { + return nil, err + } + instance := UpgradeConfigInternal{Bytes} + bytes, err := Codec.Marshal(Version, instance) + if err != nil { + return nil, err + } + + return &UpgradeConfig{config, bytes}, 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) +} diff --git a/plugin/evm/message/handshake/upgrade_config_test.go b/plugin/evm/message/handshake/upgrade_config_test.go new file mode 100644 index 0000000000..f18eb19e4b --- /dev/null +++ b/plugin/evm/message/handshake/upgrade_config_test.go @@ -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/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +func TestSerialize(t *testing.T) { + config, err := NewUpgradeConfig(params.UpgradeConfig{ + PrecompileUpgrades: []params.PrecompileUpgrade{ + { + nativeminter.NewConfig(common.Big0, nil, nil, nil), // enable at genesis + }, + { + nativeminter.NewDisableConfig(common.Big1), // 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, config, config2) + assert.Equal(t, config, config3) + assert.Equal(t, config.Hash(), config2.Hash()) + assert.Equal(t, config.Hash(), config3.Hash()) +}