-
Notifications
You must be signed in to change notification settings - Fork 1
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
base: main
Are you sure you want to change the base?
Conversation
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.
Going to factor the non-serialization related changes out of this PR.
metadata.go
Outdated
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 file is now almost entirely replaced with auto-generated code.
msg.go
Outdated
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 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
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.
These are replaced with the Record
type
encoding.go
Outdated
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 file is now essentially entirely auto-generated.
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) | ||
}) | ||
} |
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 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)
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.
Should this whole file be deleted? It is testing canoto more than this library.
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) |
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.
These are unrelated changes.
if bh.Version != 0 { | ||
e.Logger.Debug("Got block message with wrong version number, expected 0", zap.Uint8("version", bh.Version)) | ||
return false | ||
} |
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.
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) { |
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 previously assumed that messages were read-only. So now it copies the message so that receiving epoch instances can modify them.
type NodeID []byte | ||
|
||
func (node NodeID) String() string { | ||
return hex.EncodeToString(node) | ||
} | ||
|
||
func (node NodeID) Equals(otherNode NodeID) bool { | ||
return bytes.Equal(node, otherNode) | ||
} |
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.
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"` |
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 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() |
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 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 { |
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 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"` |
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 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.
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