-
Notifications
You must be signed in to change notification settings - Fork 118
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
x/dsmr: Replace the avalanchego codec with canoto #1849
base: main
Are you sure you want to change the base?
Conversation
tools.go
Outdated
// TODO: Once we update to go1.24, we should register canoto as a tool in the | ||
// go.mod file. | ||
import ( | ||
_ "github.com/StephenButtolph/canoto/generate" | ||
) |
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 allows us to inherit the canoto version from the go.mod
file - rather than needing to specify the version in every file.
@@ -1,69 +1,65 @@ | |||
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. | |||
// See the file LICENSE for licensing terms. | |||
|
|||
//go:generate go run github.com/StephenButtolph/canoto/canoto --concurrent=false $GOFILE |
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.
We specify --concurrent=false
to allow passing structs by value.
type Tx[T any] interface { | ||
canoto.FieldMaker[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.
This is basically the only real change. Rather than expecting the tx to be serializable with the avalanchego codec (which is an undocumented assumption) the interface now specifies canoto.
I had talked with aaron super briefly, but we may want to replace these with []byte
so that the transactions can specify their own encoding.
func (c *Chunk[T]) init() { | ||
c.bytes = c.MarshalCanoto() | ||
c.id = utils.ToID(c.bytes) | ||
|
||
return nil | ||
} |
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 diff warms my heart.
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.
Dropping the error on the signature is very nice here
Timestamp int64 `serialize:"true"` | ||
ParentID ids.ID `canoto:"fixed bytes,1"` | ||
Height uint64 `canoto:"int,2"` | ||
Timestamp int64 `canoto:"int,3"` |
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 decided to use int
rather than sint
here - because I don't think we ever actually expect Timestamp
to be negative.
BlockHeader | ||
ID ids.ID | ||
Chunks []Chunk[T] `serialize:"true"` | ||
Chunks []Chunk[T] `canoto:"repeated value,1"` |
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.
Similarly to Block
- is it correct for us to only serialize the Chunks
here?
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.
We probably don't have to technically serialize this... we know the ID
from the Block
this is derived from
const ( | ||
CodecVersion = 0 | ||
|
||
MaxMessageSize = units.KiB |
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.
Was MaxMessageSize
used to actually enforce something important? Or was this just used to conform to the avalanchego codec?
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 isn't used for anything important afaik. I think hypersdk originally had a 1 kb max message size specified everywhere in earlier parts of the codebase and we ended up continuing to do this for consistency, but afaik it's not needed because avalanchego will handle it for us and IMO we shouldn't be specifying it
type Signature struct { | ||
// Signers is a big-endian byte slice encoding which validators signed this | ||
// message. | ||
Signers []byte `canoto:"bytes,1"` | ||
Signature [bls.SignatureLen]byte `canoto:"fixed bytes,2"` | ||
|
||
canotoData canotoData_Signature | ||
} |
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.
Because *warp.BitSetSignature
is implemented in avalanchego - we needed to re-implement it here to be able to serialize it differently.
_ acp118.Verifier = (*ChunkSignatureRequestVerifier[Tx])(nil) | ||
_ p2p.Handler = (*GetChunkHandler[Tx])(nil) | ||
_ p2p.Handler = (*ChunkCertificateGossipHandler[Tx])(nil) |
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 don't think we have a good type to parameterize Tx
with here to allow for these assertions... So I removed them. (Similar comment for other interface checks)
chunk := Chunk[T]{} | ||
if err := codec.LinearCodec.UnmarshalFrom( | ||
&wrappers.Packer{Bytes: response.Chunk}, | ||
&chunk, | ||
); err != nil { | ||
return Chunk[T]{}, err | ||
} | ||
|
||
return chunk, chunk.init() | ||
return ParseChunk[T](response.Chunk) | ||
} |
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 almost an unrelated change... This could have previously called to ParseChunk
as well... But with removal of the codec.LinearCodec
, I felt like this change made sense here.
go.mod
Outdated
|
||
require ( | ||
github.com/StephenButtolph/canoto v0.9.0 |
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.
Is there a reason this lives under a personal github account as opposed to the org repo? It seems like this will be annoying to manage because it won't inherit org-level perms/settings/teams/etc.
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 had made this as a personal project while OOO. If we wanted to move it under the ava-labs org we could 🤷.
fwiw - I hope we never need to make any changes to it 😅
func (c *Chunk[T]) init() { | ||
c.bytes = c.MarshalCanoto() | ||
c.id = utils.ToID(c.bytes) | ||
|
||
return nil | ||
} |
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.
Dropping the error on the signature is very nice here
const ( | ||
CodecVersion = 0 | ||
|
||
MaxMessageSize = units.KiB |
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 isn't used for anything important afaik. I think hypersdk originally had a 1 kb max message size specified everywhere in earlier parts of the codebase and we ended up continuing to do this for consistency, but afaik it's not needed because avalanchego will handle it for us and IMO we shouldn't be specifying it
Height uint64 `canoto:"int,2"` | ||
Timestamp int64 `canoto:"int,3"` | ||
|
||
canotoData canotoData_BlockHeader | ||
} | ||
|
||
type Block struct { | ||
BlockHeader |
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 this will technically pass tests but we should serialize here because the block id/bytes should be derived from the BlockHeader
BlockHeader | ||
ID ids.ID | ||
Chunks []Chunk[T] `serialize:"true"` | ||
Chunks []Chunk[T] `canoto:"repeated value,1"` |
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.
We probably don't have to technically serialize this... we know the ID
from the Block
this is derived from
"github.com/ava-labs/hypersdk/utils" | ||
) | ||
|
||
const InitialChunkSize = 250 * 1024 |
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 why so much memory was being allocated when initializing the chunk.
Benchmarks look nice (as expected):