-
Notifications
You must be signed in to change notification settings - Fork 886
Complete multi-epoch ownership across Autobahn layers (CON-358) #3929
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,9 +22,30 @@ func NewLaneProposal(block *Block) *LaneProposal { | |
| // Block . | ||
| func (m *LaneProposal) Block() *Block { return m.block } | ||
|
|
||
| // Verify verifies that the LaneProposal is consistent with the Committee. | ||
| func (m *LaneProposal) Verify(c *Committee) error { | ||
| return m.block.Verify(c) | ||
| // VerifyPayload checks that the payload hashes to the header payload hash. | ||
| func (m *LaneProposal) VerifyPayload() error { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please keep it as "Verify()", to have a way of checking full internal integrity. The fact that it boils down to payload consistency check is an implementation detail. Excluding the membership check is ok imo, since lane no longer belongs to a single committee/epoch. |
||
| b := m.block | ||
| if got, want := b.payload.Hash(), b.header.payloadHash; got != want { | ||
| return fmt.Errorf("payload.Hash() = %v, want %v", got, want) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // VerifyCommitteeMembership checks that the proposal's lane is in the committee. | ||
| func (m *LaneProposal) VerifyCommitteeMembership(c *Committee) error { | ||
| return m.block.header.Verify(c) | ||
| } | ||
|
|
||
| // VerifyLaneProposalPayloadAndSignature verifies payload hash and signature. It | ||
| // does not check committee membership. | ||
| func VerifyLaneProposalPayloadAndSignature(p *Signed[*LaneProposal]) error { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: with a name this verbose, one can as well just call VerifyPayload() and VerifySignature separately without loss of readability |
||
| if err := p.Msg().VerifyPayload(); err != nil { | ||
| return fmt.Errorf("VerifyPayload(): %w", err) | ||
| } | ||
| if err := p.VerifySignature(); err != nil { | ||
| return fmt.Errorf("VerifySignature(): %w", err) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // LaneProposalConv is a protobuf converter for LaneProposal. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -188,12 +188,17 @@ func (m *Signed[T]) Sig() *Signature { return m.sig } | |
| // Key returns the key whish signed the message. | ||
| func (m *Signed[T]) Key() PublicKey { return m.sig.key } | ||
|
|
||
| // VerifySig verifies the signature of the message. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. having both "VerifySig" vs "VerifySignature" is a poor naming scheme. |
||
| // VerifySignature verifies the cryptographic signature. | ||
| func (m *Signed[T]) VerifySignature() error { | ||
| return m.sig.key.key.VerifyWithTag(autobahnTag, m.hashed.hash[:], m.sig.sig) | ||
| } | ||
|
|
||
| // VerifySig verifies the signer is a committee replica and the signature. | ||
| func (m *Signed[T]) VerifySig(c *Committee) error { | ||
| if !c.HasReplica(m.sig.key) { | ||
| return fmt.Errorf("%q is not a replica", m.sig.key) | ||
| } | ||
| return m.sig.key.key.VerifyWithTag(autobahnTag, m.hashed.hash[:], m.sig.sig) | ||
| return m.VerifySignature() | ||
| } | ||
|
|
||
| // verifyQC verifies a slice of signatures and checks if they form a quorum. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -125,6 +125,14 @@ func (v View) Next() View { | |
| return v | ||
| } | ||
|
|
||
| // ConsensusSpec is the durable CommitQC tip paired with the epoch of the view | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not "view", RoadIndex |
||
| // that follows it. Avail publishes Option[ConsensusSpec] (None until a tip | ||
| // exists); consensus installs Some values verbatim. | ||
| type ConsensusSpec struct { | ||
| CommitQC *CommitQC | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Option[CommitQC]? ConsensusSpec for index 0 won't have a CommitQC |
||
| Epoch *Epoch | ||
| } | ||
|
|
||
| // ViewSpec is the full local context for starting a view: justification QCs plus | ||
| // the epoch active at that view. Epoch is required; View(), NextGlobalBlock(), and | ||
| // NextTimestamp() panic if it is nil. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| package avail | ||
|
|
||
| import ( | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/sei-protocol/sei-chain/sei-tendermint/autobahn/types" | ||
| "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" | ||
| "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/require" | ||
| ) | ||
|
|
||
| func TestBlockVotes_RecountStayFormsLaneQC(t *testing.T) { | ||
| rng := utils.TestRng() | ||
| a := types.GenSecretKey(rng) | ||
| b := types.GenSecretKey(rng) | ||
| c := types.GenSecretKey(rng) | ||
| d := types.GenSecretKey(rng) | ||
|
|
||
| ep0 := types.NewEpoch(0, types.RoadRange{First: 0, Next: 10}, time.Time{}, | ||
| utils.OrPanic1(types.NewCommittee(map[types.PublicKey]uint64{ | ||
| a.Public(): 1, b.Public(): 1, c.Public(): 1, d.Public(): 1, | ||
| })), 0) | ||
| lane := ep0.Committee().Lane(a.Public()).OrPanic("lane") | ||
| header := types.NewBlock(lane, 0, types.BlockHeaderHash{}, &types.Payload{}).Header() | ||
| vote := func(sk types.SecretKey) *types.Signed[*types.LaneVote] { | ||
| return types.Sign(sk, types.NewLaneVote(header)) | ||
| } | ||
|
|
||
| bv := newBlockVotes() | ||
| require.True(t, bv.pushVote(ep0, vote(a))) | ||
| require.False(t, bv.qc.IsPresent()) | ||
| require.True(t, bv.pushVote(ep0, vote(b))) | ||
| qc, ok := bv.qc.Get() | ||
| require.True(t, ok) | ||
| require.Equal(t, header.Hash(), qc.Header().Hash()) | ||
| require.True(t, bv.pushVote(ep0, vote(d))) | ||
|
|
||
| require.True(t, bv.header(header.Hash()).IsPresent()) | ||
| require.Equal(t, 3, len(bv.byKey)) | ||
|
|
||
| ep1 := types.NewEpoch(1, types.RoadRange{First: 10, Next: 20}, time.Time{}, | ||
| utils.OrPanic1(ep0.Committee().DeriveNext(map[types.PublicKey]uint64{ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we test a reweighting of weights here for a single validator to test reweight logic? This is what was suggested:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| a.Public(): 1, b.Public(): 1, c.Public(): 1, | ||
| }, 1)), 0) | ||
| bv.reweight(ep1) | ||
|
|
||
| qc, ok = bv.qc.Get() | ||
| require.True(t, ok) | ||
| require.Equal(t, header.Hash(), qc.Header().Hash()) | ||
| require.Equal(t, 3, len(bv.byKey)) | ||
| require.True(t, bv.header(header.Hash()).IsPresent()) | ||
| } | ||
|
|
||
| func TestBlockVotes_ZeroWeightNotCreditedUnderApplied(t *testing.T) { | ||
| rng := utils.TestRng() | ||
| stay := types.GenSecretKey(rng) | ||
| leaver := types.GenSecretKey(rng) | ||
| ep0 := types.NewEpoch(0, types.RoadRange{First: 0, Next: 10}, time.Time{}, | ||
| utils.OrPanic1(types.NewCommittee(map[types.PublicKey]uint64{ | ||
| stay.Public(): 1, leaver.Public(): 1, | ||
| types.GenSecretKey(rng).Public(): 1, types.GenSecretKey(rng).Public(): 1, | ||
| })), 0) | ||
| lane := ep0.Committee().Lane(stay.Public()).OrPanic("lane") | ||
| header := types.NewBlock(lane, 0, types.BlockHeaderHash{}, &types.Payload{}).Header() | ||
|
|
||
| bv := newBlockVotes() | ||
| bv.pushVote(ep0, types.Sign(leaver, types.NewLaneVote(header))) | ||
|
|
||
| ep1 := types.NewEpoch(1, types.RoadRange{First: 10, Next: 20}, time.Time{}, | ||
| utils.OrPanic1(ep0.Committee().DeriveNext(map[types.PublicKey]uint64{ | ||
| stay.Public(): 1, | ||
| }, 1)), 0) | ||
| bv.reweight(ep1) | ||
| require.False(t, bv.qc.IsPresent()) | ||
| require.Equal(t, 1, len(bv.byKey)) | ||
| } | ||
|
|
||
| // A alone reaches lane quorum; after a weight cut with the same membership, reweight drops the QC. | ||
| func TestBlockVotes_ReweightInvalidatesLaneQC(t *testing.T) { | ||
| rng := utils.TestRng() | ||
| a := types.GenSecretKey(rng) | ||
| b := types.GenSecretKey(rng) | ||
| c := types.GenSecretKey(rng) | ||
| d := types.GenSecretKey(rng) | ||
|
|
||
| ep0 := types.NewEpoch(0, types.RoadRange{First: 0, Next: 10}, time.Time{}, | ||
| utils.OrPanic1(types.NewCommittee(map[types.PublicKey]uint64{ | ||
| a.Public(): 3, b.Public(): 1, c.Public(): 1, d.Public(): 1, | ||
| })), 0) | ||
| require.Equal(t, uint64(2), ep0.Committee().LaneQuorum()) | ||
| lane := ep0.Committee().Lane(a.Public()).OrPanic("lane") | ||
| header := types.NewBlock(lane, 0, types.BlockHeaderHash{}, &types.Payload{}).Header() | ||
|
|
||
| bv := newBlockVotes() | ||
| require.True(t, bv.pushVote(ep0, types.Sign(a, types.NewLaneVote(header)))) | ||
| require.True(t, bv.qc.IsPresent()) | ||
|
|
||
| ep1 := types.NewEpoch(1, types.RoadRange{First: 10, Next: 20}, time.Time{}, | ||
| utils.OrPanic1(ep0.Committee().DeriveNext(map[types.PublicKey]uint64{ | ||
| a.Public(): 1, b.Public(): 1, c.Public(): 5, d.Public(): 5, | ||
| }, 1)), 0) | ||
| require.Equal(t, uint64(4), ep1.Committee().LaneQuorum()) | ||
| bv.reweight(ep1) | ||
| require.False(t, bv.qc.IsPresent()) | ||
| require.True(t, bv.header(header.Hash()).IsPresent()) | ||
| } | ||
|
|
||
| // A+B are short of lane quorum; after a weight increase with the same membership, reweight forms a QC. | ||
| func TestBlockVotes_ReweightFormsLaneQC(t *testing.T) { | ||
| rng := utils.TestRng() | ||
| a := types.GenSecretKey(rng) | ||
| b := types.GenSecretKey(rng) | ||
| c := types.GenSecretKey(rng) | ||
| d := types.GenSecretKey(rng) | ||
|
|
||
| ep0 := types.NewEpoch(0, types.RoadRange{First: 0, Next: 10}, time.Time{}, | ||
| utils.OrPanic1(types.NewCommittee(map[types.PublicKey]uint64{ | ||
| a.Public(): 1, b.Public(): 1, c.Public(): 5, d.Public(): 5, | ||
| })), 0) | ||
| require.Equal(t, uint64(4), ep0.Committee().LaneQuorum()) | ||
| lane := ep0.Committee().Lane(a.Public()).OrPanic("lane") | ||
| header := types.NewBlock(lane, 0, types.BlockHeaderHash{}, &types.Payload{}).Header() | ||
| vote := func(sk types.SecretKey) *types.Signed[*types.LaneVote] { | ||
| return types.Sign(sk, types.NewLaneVote(header)) | ||
| } | ||
|
|
||
| bv := newBlockVotes() | ||
| require.True(t, bv.pushVote(ep0, vote(a))) | ||
| require.True(t, bv.pushVote(ep0, vote(b))) | ||
| require.False(t, bv.qc.IsPresent()) | ||
|
|
||
| ep1 := types.NewEpoch(1, types.RoadRange{First: 10, Next: 20}, time.Time{}, | ||
| utils.OrPanic1(ep0.Committee().DeriveNext(map[types.PublicKey]uint64{ | ||
| a.Public(): 5, b.Public(): 5, c.Public(): 1, d.Public(): 1, | ||
| }, 1)), 0) | ||
| require.Equal(t, uint64(4), ep1.Committee().LaneQuorum()) | ||
| bv.reweight(ep1) | ||
| qc, ok := bv.qc.Get() | ||
| require.True(t, ok) | ||
| require.Equal(t, header.Hash(), qc.Header().Hash()) | ||
| } | ||
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.
why did you drop it?