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

Replace custom serialization and asn.1 with Canoto #46

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

StephenButtolph
Copy link
Collaborator

@StephenButtolph StephenButtolph commented Jan 11, 2025

This PR should not be reviewed in its current state. This is just a spike to show that it can be done.

There were a number of almost unrelated changes that were made in this pass that should be reviewed / discussed in their own right unrelated to the introduction of Canoto.

Ignoring auto-generated code, the diff is (currently) +507 / -769

Copy link
Collaborator Author

@StephenButtolph StephenButtolph left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to factor the non-serialization related changes out of this PR.

metadata.go Outdated
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is now almost entirely replaced with auto-generated code.

msg.go Outdated
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR merged a number of types (rather than implementing custom types, the variables imply the context). I felt like this reduced duplicate code and made things easier to understand... But I suppose this may have been intentional to always enforce passing the correct messages based on type.

record/consts.go Outdated
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are replaced with the Record type

encoding.go Outdated
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is now essentially entirely auto-generated.

Comment on lines -15 to -65
func TestBlockRecord(t *testing.T) {
bh := BlockHeader{
ProtocolMetadata: ProtocolMetadata{
Version: 1,
Round: 2,
Seq: 3,
Epoch: 4,
},
}

_, err := rand.Read(bh.Prev[:])
require.NoError(t, err)

_, err = rand.Read(bh.Digest[:])
require.NoError(t, err)

payload := []byte{11, 12, 13, 14, 15, 16}

record := blockRecord(bh, payload)

md2, payload2, err := blockFromRecord(record)
require.NoError(t, err)

require.Equal(t, bh, md2)
require.Equal(t, payload, payload2)
}

func FuzzBlockRecord(f *testing.F) {
f.Fuzz(func(t *testing.T, version uint8, round, seq, epoch uint64, prevPreimage, digestPreimage []byte, payload []byte) {
prev := sha256.Sum256(prevPreimage)
digest := sha256.Sum256(digestPreimage)
bh := BlockHeader{
ProtocolMetadata: ProtocolMetadata{
Version: version,
Round: round,
Seq: seq,
Epoch: epoch,
Prev: prev,
},
Digest: digest,
}

record := blockRecord(bh, payload)

md2, payload2, err := blockFromRecord(record)
require.NoError(t, err)

require.Equal(t, bh, md2)
require.Equal(t, payload, payload2)
})
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also a change unrelated to the Canoto inclusion. The block record previously included the protocol metadata multiple times (because the payload is expected to be able to be parsed and the result is then expected to provide the metadata as well)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this whole file be deleted? It is testing canoto more than this library.

Comment on lines +98 to +107
case msg.Proposal != nil:
return e.handleBlockMessage(msg.Proposal, from)
case msg.Vote != nil:
return e.handleVoteMessage(msg.Vote, from)
case msg.Notarization != nil:
return e.handleNotarizationMessage(msg, from)
return e.handleNotarizationMessage(msg.Notarization, from)
case msg.Finalize != nil:
return e.handleFinalizeMessage(msg.Finalize, from)
case msg.Finalization != nil:
return e.handleFinalizationMessage(msg, from)
case msg.FinalizationCertificate != nil:
return e.handleFinalizationCertificateMessage(msg, from)
return e.handleFinalizationMessage(msg.Finalization, from)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are unrelated changes.

Comment on lines -693 to -696
if bh.Version != 0 {
e.Logger.Debug("Got block message with wrong version number, expected 0", zap.Uint8("version", bh.Version))
return false
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Canoto supports changes to the serialized format by adding/removing fields. So there isn't a need for an explicit version in the format anymore.

@@ -185,15 +182,25 @@ func (c *testComm) SendMessage(msg *Message, destination NodeID) {
}

func (c *testComm) Broadcast(msg *Message) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This previously assumed that messages were read-only. So now it copies the message so that receiving epoch instances can modify them.

Comment on lines -11 to -19
type NodeID []byte

func (node NodeID) String() string {
return hex.EncodeToString(node)
}

func (node NodeID) Equals(otherNode NodeID) bool {
return bytes.Equal(node, otherNode)
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved into the types file

Message struct {
Proposal *Proposal `canoto:"pointer,1,message"`
Vote *Vote `canoto:"pointer,2,message"`
Notarization *Quorum `canoto:"pointer,3,message"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we keep the original types? A type for notarization and a type for finalization.

I don't want any chance of notarizations and finalizations being mixed by mistake.

Payload: msg,
Context: context,
}
toBeSigned := sm.MarshalCanoto()
Copy link
Collaborator

@yacovm yacovm Jan 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would rather prefer ASN1 to be used when marshaling anything that is being signed.

I understand Canoto is supposed to be deterministic, but for the sake of security I'd prefer if we use a package that had more mileage and adopts a standard that is widely used for signatures.

NodeID []byte
)

func (v *Vote) Sign(nodeID NodeID, signer Signer, context string) error {
Copy link
Collaborator

@yacovm yacovm Jan 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that passing a free and arbitrary context string is too much freedom and is too error prone from a security point of view. That's why I intentionally had different message types, so it isn't possible to sign a notarization as a finalization and vice versa.


Message struct {
Proposal *Proposal `canoto:"pointer,1,message"`
Vote *Vote `canoto:"pointer,2,message"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message struct is not supposed to be serialized. It's only supposed to be used to be passed along function calls.

I think we should only define separately the types we need to persist to disk and not mix them with consensus logic.

I don't want us to leak implementation details to the API that the application uses to interact with Simplex.

Therefore, the Message and all its underlying fields should be clean of anything the API doesn't use.

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