Skip to content
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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

StephenButtolph
Copy link
Contributor

@StephenButtolph StephenButtolph commented Jan 3, 2025

Benchmarks look nice (as expected):

goos: darwin
goarch: arm64
pkg: github.com/ava-labs/hypersdk/x/dsmr
cpu: Apple M2 Max
              │      old       │                 new                 │
              │     sec/op     │   sec/op     vs base                │
ChunkInit-12    6654.0n ± 252%   187.3n ± 1%  -97.18% (p=0.000 n=10)
ParseChunk-12   7943.0n ±   1%   367.8n ± 0%  -95.37% (p=0.000 n=10)
geomean          7.270µ          262.5n       -96.39%

              │      old      │                new                 │
              │     B/op      │    B/op     vs base                │
ChunkInit-12    262209.0 ± 0%   144.0 ± 0%  -99.95% (p=0.000 n=10)
ParseChunk-12   262961.0 ± 0%   248.0 ± 0%  -99.91% (p=0.000 n=10)
geomean          256.4Ki        189.0       -99.93%

              │     old     │                new                 │
              │  allocs/op  │ allocs/op   vs base                │
ChunkInit-12     2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=10)
ParseChunk-12   14.000 ± 0%   3.000 ± 0%  -78.57% (p=0.000 n=10)
geomean          5.292        1.732       -67.27%
¹ all samples are equal

tools.go Outdated
Comment on lines 6 to 10
// 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"
)
Copy link
Contributor Author

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
Copy link
Contributor Author

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.

Comment on lines +20 to +21
type Tx[T any] interface {
canoto.FieldMaker[T]
Copy link
Contributor Author

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.

Comment on lines +49 to 52
func (c *Chunk[T]) init() {
c.bytes = c.MarshalCanoto()
c.id = utils.ToID(c.bytes)

return nil
}
Copy link
Contributor Author

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.

Copy link
Contributor

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"`
Copy link
Contributor Author

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.

Comment on lines 129 to +131
BlockHeader
ID ids.ID
Chunks []Chunk[T] `serialize:"true"`
Chunks []Chunk[T] `canoto:"repeated value,1"`
Copy link
Contributor Author

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?

Copy link
Contributor

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
Copy link
Contributor Author

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?

Copy link
Contributor

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

Comment on lines +26 to 33
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
}
Copy link
Contributor Author

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.

Comment on lines -42 to -44
_ acp118.Verifier = (*ChunkSignatureRequestVerifier[Tx])(nil)
_ p2p.Handler = (*GetChunkHandler[Tx])(nil)
_ p2p.Handler = (*ChunkCertificateGossipHandler[Tx])(nil)
Copy link
Contributor Author

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)

Comment on lines -141 to 138
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)
}
Copy link
Contributor Author

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.

@StephenButtolph StephenButtolph marked this pull request as ready for review January 3, 2025 06:55
go.mod Outdated

require (
github.com/StephenButtolph/canoto v0.9.0
Copy link
Contributor

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.

Copy link
Contributor Author

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 😅

Comment on lines +49 to 52
func (c *Chunk[T]) init() {
c.bytes = c.MarshalCanoto()
c.id = utils.ToID(c.bytes)

return nil
}
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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

Comment on lines 129 to +131
BlockHeader
ID ids.ID
Chunks []Chunk[T] `serialize:"true"`
Chunks []Chunk[T] `canoto:"repeated value,1"`
Copy link
Contributor

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
Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants