-
Notifications
You must be signed in to change notification settings - Fork 232
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
Add handshake codec #762
base: master
Are you sure you want to change the base?
Add handshake codec #762
Conversation
7c0580f
to
a5266ff
Compare
I think the main thing to be done is to write our own codec, that should extend The new codec should also serialize anything Right now this PR works, but it is suboptimal as it uses raw-JSON under the hood. I don't like that. |
As you mentioned, JSON sorting could be potentially dangerous. GoLang JSON marshalling has a sorting mechanism but that's not in the spec and cannot be relied (it's just a implementation detail and can be changed without backward compatibility). We can still try to get advantage of json tags but using json marshalling seems a bit dangerous. Some solutions we can use: 2- Break the
3- Again break the 4- Just encode 5- Use the upgrade file bytes directly as it is (no sorting, no conversion). Means that we treat files as the source of truth (rather than using structs). Obviously there could be some problems like whitespacing in files, extra comas, CR/LF style endings etc. |
a5266ff
to
50741b9
Compare
de94050
to
6740f23
Compare
83cb2b6
to
a3a6471
Compare
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestSerialize(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add more test cases with:
- Enabled/Admin addresses
- Initial configs (like mint config)
- State Upgrades
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to make sure:
- all of our precompiles can be deserializable
- add serialize tags to precompile config template
- if possible add a test to
config_test.go
so that new precompiles can be tested out when generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^^^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still pending.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm writing more tests now
7117339
to
41d7776
Compare
41d7776
to
952f21c
Compare
params/network_upgrades.go
Outdated
// 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"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking this to be something like:
type OptionalNetworkUpgrades struct {
XYZUpgradeTimestamp *uint64 `json:"xyzUpgradeTimestamp,omitempty"` serialize:"true"
...
But I'm not completely against using a list of upgrades here. Seems like with this way we can just include them as elements in a list in the genesis/upgrades to be activated.
However we should not use fork
type here as (we would not need optional + block types in the struct). We can define our custom struct to wire this and revert changes to the fork
type.
Plus this is still wrong as the json tag is serialize
, it should be at least something like json:"optionalNetworkUpgrades,omitempty"
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestSerialize(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^^^
5a1af29
to
b6c0e8a
Compare
Use MarshalBinary and UnmarshalBinary
4591ccd
to
6785dd9
Compare
commontype/fee_config.go
Outdated
func (c *FeeConfig) getBigIntToSerialize() []**big.Int { | ||
return []**big.Int{ | ||
&c.GasLimit, &c.MinBaseFee, &c.TargetGas, &c.BaseFeeChangeDenominator, | ||
&c.MinBlockGasCost, &c.MaxBlockGasCost, &c.BlockGasCostStep, | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this is trying to make the code simpler by passing a list of pointers to pointers so we can serialize and de-serialize with a for loop instead of one by one.
It's not valid for any of these to be nil, so would it be possible to remove the double pointer and enforce that all of them are non-nil instead of allowing a boolean to indicate whether or not it's nil?
params/config.go
Outdated
bytes, err := originalConfig.MarshalBinary() | ||
require.NoError(t, err) | ||
|
||
deserializedConfig := UpgradeConfig{} | ||
require.NoError(t, deserializedConfig.UnmarshalBinary(bytes)) | ||
|
||
twiceDeserialized := UpgradeConfig{} | ||
newBytes, err := deserializedConfig.MarshalBinary() | ||
require.NoError(t, err) | ||
require.NoError(t, twiceDeserialized.UnmarshalBinary(newBytes)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When will this fail because we do it three times instead of two?
If newBytes
is the same as bytes
(which should be confirmed by checking that the hashes match), then I don't think that twiceDeserialized
provides additional coverage. Afaict this will fail if UnmarshalBinary
is itself non-deterministic on the SAME bytes.
}, | ||
}, | ||
} | ||
params.AssertConfigHashesAndSerialization(t, &config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we add an expected hash to these tests, so that the tests will fail if the format changes unexpectedly? (this has the cost of needing to update the expected hash values if we change the format, so may be easiest to make this a follow up)
config := params.UpgradeConfig{ | ||
PrecompileUpgrades: []params.PrecompileUpgrade{ | ||
{ | ||
Config: NewConfig(&t0, []common.Address{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add a test with multiple addresses?
if p.Err != nil { | ||
return p.Err | ||
} | ||
u.RewardAddress = common.BytesToAddress(p.UnpackBytes()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we use fixed bytes of length 20?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in e079719
accounts/abi/bind/precompilebind/precompile_config_test_template.go
Outdated
Show resolved
Hide resolved
{ | ||
BlockTimestamp: &t0, | ||
StateUpgradeAccounts: map[common.Address]StateUpgradeAccount{ | ||
common.BytesToAddress(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000050")): StateUpgradeAccount{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can we create a variable for these addresses instead of hardcoding them into tests?
accounts/abi/bind/precompilebind/precompile_config_test_template.go
Outdated
Show resolved
Hide resolved
// hashing, depite maybe not being identical (some configurations may be in a | ||
// different order, but our hashing algorithm is resilient to those changes, | ||
// thanks for our serialization library, which produces always the same output. | ||
func AssertConfigHashesAndSerialization(t *testing.T, originalConfig *UpgradeConfig) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Requiring this function from params package in precompiles will create a cyclic dependency most likely. I think this assertion can be reduced as suggested. So we can just readd these assertions in relevant test files without requiring to import from params package.
@@ -48,6 +54,14 @@ func NewConfig(blockTimestamp *uint64{{if .Contract.AllowList}}, admins []common | |||
} | |||
} | |||
|
|||
func (c * Config) MarshalBinary() ([]byte, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add some comments here why these are important, how these can be implemented etc?
Co-authored-by: Ceyhun Onur <[email protected]> Signed-off-by: Cesar <[email protected]>
Co-authored-by: aaronbuchwald <[email protected]> Signed-off-by: Cesar <[email protected]>
Co-authored-by: aaronbuchwald <[email protected]> Signed-off-by: Cesar <[email protected]>
Instead loop them together
It generates valid marshal/unmarshal t# Please enter the commit message for your changes. Lines starting
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.It has to be deterministic, otherwise same configs may have different hashes, making them believe they are on different configs.
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)
Why this should be merged
How this works
How this was tested
How is this documented