From d0a989e03986f291ca0f118cc0430454aae4afb5 Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 9 Oct 2024 14:51:29 -0300 Subject: [PATCH 01/15] feat(consensus/grandpa): Add warp sync verification --- dot/network/mock_warp_sync_provider_test.go | 16 +++ dot/network/warp_sync.go | 16 +++ .../client/consensus/grandpa/warp_sync.go | 133 ++++++++++++++++-- .../consensus/grandpa/warp_sync_test.go | 2 +- .../primitives/consensus/grandpa/grandpa.go | 5 + 5 files changed, 160 insertions(+), 12 deletions(-) diff --git a/dot/network/mock_warp_sync_provider_test.go b/dot/network/mock_warp_sync_provider_test.go index 2ec249711f..04280930e7 100644 --- a/dot/network/mock_warp_sync_provider_test.go +++ b/dot/network/mock_warp_sync_provider_test.go @@ -12,6 +12,7 @@ package network import ( reflect "reflect" + grandpa "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" common "github.com/ChainSafe/gossamer/lib/common" gomock "go.uber.org/mock/gomock" ) @@ -53,3 +54,18 @@ func (mr *MockWarpSyncProviderMockRecorder) Generate(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Generate", reflect.TypeOf((*MockWarpSyncProvider)(nil).Generate), arg0) } + +// Verify mocks base method. +func (m *MockWarpSyncProvider) Verify(arg0 []byte, arg1 grandpa.SetID, arg2 grandpa.AuthorityList) (*WarpSyncVerificationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Verify", arg0, arg1, arg2) + ret0, _ := ret[0].(*WarpSyncVerificationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Verify indicates an expected call of Verify. +func (mr *MockWarpSyncProviderMockRecorder) Verify(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*MockWarpSyncProvider)(nil).Verify), arg0, arg1, arg2) +} diff --git a/dot/network/warp_sync.go b/dot/network/warp_sync.go index 8c6978163e..fab951ad7d 100644 --- a/dot/network/warp_sync.go +++ b/dot/network/warp_sync.go @@ -8,6 +8,10 @@ import ( "fmt" "github.com/ChainSafe/gossamer/dot/network/messages" + "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" + "github.com/ChainSafe/gossamer/internal/primitives/core/hash" + "github.com/ChainSafe/gossamer/internal/primitives/runtime" + "github.com/ChainSafe/gossamer/internal/primitives/runtime/generic" "github.com/ChainSafe/gossamer/lib/common" libp2pnetwork "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" @@ -15,11 +19,23 @@ import ( const MaxAllowedSameRequestPerPeer = 5 +type WarpSyncVerificationResult struct { + SetId grandpa.SetID + AuthorityList grandpa.AuthorityList + Header generic.Header[uint64, hash.H256, runtime.BlakeTwo256] + Completed bool +} + // WarpSyncProvider is an interface for generating warp sync proofs type WarpSyncProvider interface { // Generate proof starting at given block hash. The proof is accumulated until maximum proof // size is reached. Generate(start common.Hash) (encodedProof []byte, err error) + Verify( + encodedProof []byte, + setId grandpa.SetID, + authorities grandpa.AuthorityList, + ) (*WarpSyncVerificationResult, error) } func (s *Service) handleWarpSyncRequest(req messages.WarpProofRequest) ([]byte, error) { diff --git a/internal/client/consensus/grandpa/warp_sync.go b/internal/client/consensus/grandpa/warp_sync.go index aa1a5e050b..0878579619 100644 --- a/internal/client/consensus/grandpa/warp_sync.go +++ b/internal/client/consensus/grandpa/warp_sync.go @@ -6,9 +6,12 @@ package grandpa import ( "fmt" + "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" "github.com/ChainSafe/gossamer/internal/primitives/core/hash" "github.com/ChainSafe/gossamer/internal/primitives/runtime" + "github.com/ChainSafe/gossamer/internal/primitives/runtime/generic" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -34,7 +37,7 @@ type GrandpaState interface { type WarpSyncFragment struct { // The last block that the given authority set finalized. This block should contain a digest // signalling an authority set change from which we can fetch the next authority set. - Header types.Header + Header generic.Header[uint64, hash.H256, runtime.BlakeTwo256] // A justification for the header above which proves its finality. In order to validate it the // verifier must be aware of the authorities and set id for which the justification refers to. Justification GrandpaJustification[hash.H256, uint64] @@ -78,9 +81,54 @@ func (w *WarpSyncProof) lastProofBlockNumber() uint64 { return w.Proofs[len(w.Proofs)-1].Justification.Justification.Commit.TargetNumber + 1 } +func (w *WarpSyncProof) verify( + setId grandpa.SetID, + authorities grandpa.AuthorityList, + hardForks map[string]SetIdAuthorityList, +) (*SetIdAuthorityList, error) { + currentSetId := setId + currentAuthorities := authorities + + for fragmentNumber, proof := range w.Proofs { + hash := proof.Header.Hash() + number := proof.Header.Number() + + hardForkKey := fmt.Sprintf("%v-%v", hash, number) + if fork, ok := hardForks[hardForkKey]; ok { + currentSetId = fork.SetID + currentAuthorities = fork.AuthorityList + } else { + err := proof.Justification.Verify(uint64(currentSetId), currentAuthorities) + if err != nil { + return nil, err + } + + if proof.Justification.Target().Hash != hash { + return nil, fmt.Errorf("mismatch between header and justification") + } + + scheduledChange := findScheduledChange(proof.Header) + if scheduledChange != nil { + currentSetId += 1 + currentAuthorities = scheduledChange.NextAuthorities + } else if fragmentNumber != len(w.Proofs)-1 || !w.IsFinished { + return nil, fmt.Errorf("Header is missing authority set change digest") + } + } + } + + return &SetIdAuthorityList{currentSetId, currentAuthorities}, nil +} + +type SetIdAuthorityList struct { + grandpa.SetID + grandpa.AuthorityList +} + type WarpSyncProofProvider struct { blockState BlockState grandpaState GrandpaState + hardForks map[string]SetIdAuthorityList } func NewWarpSyncProofProvider(blockState BlockState, grandpaState GrandpaState) *WarpSyncProofProvider { @@ -91,14 +139,14 @@ func NewWarpSyncProofProvider(blockState BlockState, grandpaState GrandpaState) } // Generate build a warp sync encoded proof starting from the given block hash -func (np *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { +func (p *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { // Get and traverse all GRANDPA authorities changes from the given block hash - beginBlockHeader, err := np.blockState.GetHeader(start) + beginBlockHeader, err := p.blockState.GetHeader(start) if err != nil { return nil, fmt.Errorf("%w: %w", errMissingStartBlock, err) } - lastFinalizedBlockHeader, err := np.blockState.GetHighestFinalisedHeader() + lastFinalizedBlockHeader, err := p.blockState.GetHighestFinalisedHeader() if err != nil { return nil, fmt.Errorf("getting best block header: %w", err) } @@ -107,7 +155,7 @@ func (np *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { return nil, errStartBlockNotFinalized } - authoritySetChanges, err := np.grandpaState.GetAuthoritiesChangesFromBlock(beginBlockHeader.Number) + authoritySetChanges, err := p.grandpaState.GetAuthoritiesChangesFromBlock(beginBlockHeader.Number) if err != nil { return nil, err } @@ -115,12 +163,12 @@ func (np *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { limitReached := false finalProof := NewWarpSyncProof() for _, blockNumber := range authoritySetChanges { - header, err := np.blockState.GetHeaderByNumber(blockNumber) + header, err := p.blockState.GetHeaderByNumber(blockNumber) if err != nil { return nil, err } - encJustification, err := np.blockState.GetJustification(header.Hash()) // get the justification of such block + encJustification, err := p.blockState.GetJustification(header.Hash()) // get the justification of such block if err != nil { return nil, err } @@ -130,7 +178,7 @@ func (np *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { return nil, err } - fragment := WarpSyncFragment{Header: *header, Justification: *justification} + fragment := WarpSyncFragment{Header: headerToGenericHeader(*header), Justification: *justification} // check the proof size limitReached, err = finalProof.addFragment(fragment) @@ -150,11 +198,12 @@ func (np *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { // last authority set change. if we didn't prove any authority set // change then we fallback to make sure it's higher or equal to the // initial warp sync block. - lastFinalizedBlockHeader, err := np.blockState.GetHighestFinalisedHeader() + lastFinalizedBlockHeader, err := p.blockState.GetHighestFinalisedHeader() if err != nil { return nil, fmt.Errorf("getting best block header: %w", err) } - latestJustification, err := np.blockState.GetJustification(lastFinalizedBlockHeader.Hash()) + + latestJustification, err := p.blockState.GetJustification(lastFinalizedBlockHeader.Hash()) if err != nil { return nil, err } @@ -165,7 +214,7 @@ func (np *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { } if justification.Justification.Commit.TargetNumber >= finalProof.lastProofBlockNumber() { - fragment := WarpSyncFragment{Header: *lastFinalizedBlockHeader, Justification: *justification} + fragment := WarpSyncFragment{Header: headerToGenericHeader(*lastFinalizedBlockHeader), Justification: *justification} _, err = finalProof.addFragment(fragment) if err != nil { return nil, err @@ -178,3 +227,65 @@ func (np *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { // Encode and return the proof return scale.Marshal(finalProof) } + +// Verify checks the validity of the given warp sync proof +func (p *WarpSyncProofProvider) Verify( + encodedProof []byte, + setId grandpa.SetID, + authorities grandpa.AuthorityList, +) (*network.WarpSyncVerificationResult, error) { + var proof WarpSyncProof + err := scale.Unmarshal(encodedProof, proof) + if err != nil { + return nil, fmt.Errorf("decoding warp sync proof: %w", err) + } + + if len(proof.Proofs) == 0 { + return nil, fmt.Errorf("empty warp sync proof") + } + + lastProof := proof.Proofs[len(proof.Proofs)-1] + lastHeader := lastProof.Header + + nextSetAndAuthorities, err := proof.verify(setId, authorities, p.hardForks) + if err != nil { + return nil, fmt.Errorf("verifying warp sync proof: %w", err) + } + + if proof.IsFinished { + return &network.WarpSyncVerificationResult{ + SetId: nextSetAndAuthorities.SetID, + AuthorityList: nextSetAndAuthorities.AuthorityList, + Header: lastHeader, + Completed: true, + }, nil + } else { + return &network.WarpSyncVerificationResult{ + SetId: nextSetAndAuthorities.SetID, + AuthorityList: nextSetAndAuthorities.AuthorityList, + Header: lastHeader, + Completed: false, + }, nil + } +} + +func findScheduledChange( + header generic.Header[uint64, hash.H256, runtime.BlakeTwo256], +) *grandpa.ScheduledChange[uint64] { + panic("not implemented") +} + +func headerToGenericHeader(header types.Header) generic.Header[uint64, hash.H256, runtime.BlakeTwo256] { + digest := runtime.Digest{} + for _, digestItem := range header.Digest { + digest.Push(digestItem) + } + + return *generic.NewHeader[uint64, hash.H256, runtime.BlakeTwo256]( + uint64(header.Number), + hash.H256(header.ExtrinsicsRoot.String()), + hash.H256(header.StateRoot.String()), + hash.H256(header.ParentHash.String()), + digest, + ) +} diff --git a/internal/client/consensus/grandpa/warp_sync_test.go b/internal/client/consensus/grandpa/warp_sync_test.go index 3fe109397b..9d238d957a 100644 --- a/internal/client/consensus/grandpa/warp_sync_test.go +++ b/internal/client/consensus/grandpa/warp_sync_test.go @@ -120,7 +120,7 @@ func TestGenerateWarpSyncProofOk(t *testing.T) { assert.NoError(t, err) expectedProof := []byte{ - 0x4, 0x1c, 0xa4, 0x2, 0x25, 0x71, 0x86, 0xee, 0x43, 0x46, 0xfd, 0x2c, 0x9, 0xfe, 0xeb, 0x91, 0x17, 0x10, 0xe5, 0x88, 0x41, 0x89, 0xc3, 0xc7, 0x5f, 0xb5, 0x1, 0x1a, 0x75, 0x21, 0x37, 0x2f, 0xf9, 0x14, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xec, 0x94, 0x1a, 0xe4, 0xe1, 0x86, 0x62, 0x8c, 0x96, 0x9a, 0x17, 0xb9, 0x2b, 0xe7, 0xac, 0xc2, 0x45, 0x96, 0x1b, 0xbf, 0xca, 0x32, 0x6c, 0x5b, 0xdc, 0x39, 0xd6, 0x2f, 0xca, 0x3e, 0x46, 0xee, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0xec, 0x94, 0x1a, 0xe4, 0xe1, 0x86, 0x62, 0x8c, 0x96, 0x9a, 0x17, 0xb9, 0x2b, 0xe7, 0xac, 0xc2, 0x45, 0x96, 0x1b, 0xbf, 0xca, 0x32, 0x6c, 0x5b, 0xdc, 0x39, 0xd6, 0x2f, 0xca, 0x3e, 0x46, 0xee, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc9, 0xe8, 0x1a, 0x88, 0x1f, 0x4d, 0xf, 0xc2, 0x22, 0xc8, 0xf8, 0x2b, 0xdb, 0x94, 0xcf, 0x38, 0xf0, 0xab, 0xd0, 0xdd, 0xa2, 0xca, 0x99, 0xd1, 0x96, 0x1b, 0x47, 0xcf, 0xe3, 0x66, 0x85, 0x20, 0xce, 0x4a, 0x4e, 0x1a, 0x94, 0xa6, 0x12, 0x43, 0xbc, 0x4c, 0xa3, 0xc8, 0x44, 0xf9, 0x86, 0x1c, 0x7a, 0x4a, 0xb6, 0x45, 0x87, 0x5a, 0xc7, 0x34, 0x48, 0x6d, 0x29, 0xc, 0x25, 0x12, 0xa1, 0x4, 0x88, 0xdc, 0x34, 0x17, 0xd5, 0x5, 0x8e, 0xc4, 0xb4, 0x50, 0x3e, 0xc, 0x12, 0xea, 0x1a, 0xa, 0x89, 0xbe, 0x20, 0xf, 0xe9, 0x89, 0x22, 0x42, 0x3d, 0x43, 0x34, 0x1, 0x4f, 0xa6, 0xb0, 0xee, 0x0, 0x1, + 0x4, 0x30, 0x78, 0x31, 0x63, 0x61, 0x34, 0x30, 0x32, 0x32, 0x35, 0x37, 0x31, 0x38, 0x36, 0x65, 0x65, 0x34, 0x33, 0x34, 0x36, 0x66, 0x64, 0x32, 0x63, 0x30, 0x39, 0x66, 0x65, 0x65, 0x62, 0x39, 0x31, 0x14, 0x30, 0x78, 0x30, 0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x30, 0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xec, 0x94, 0x1a, 0xe4, 0xe1, 0x86, 0x62, 0x8c, 0x96, 0x9a, 0x17, 0xb9, 0x2b, 0xe7, 0xac, 0xc2, 0x45, 0x96, 0x1b, 0xbf, 0xca, 0x32, 0x6c, 0x5b, 0xdc, 0x39, 0xd6, 0x2f, 0xca, 0x3e, 0x46, 0xee, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0xec, 0x94, 0x1a, 0xe4, 0xe1, 0x86, 0x62, 0x8c, 0x96, 0x9a, 0x17, 0xb9, 0x2b, 0xe7, 0xac, 0xc2, 0x45, 0x96, 0x1b, 0xbf, 0xca, 0x32, 0x6c, 0x5b, 0xdc, 0x39, 0xd6, 0x2f, 0xca, 0x3e, 0x46, 0xee, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc9, 0xe8, 0x1a, 0x88, 0x1f, 0x4d, 0xf, 0xc2, 0x22, 0xc8, 0xf8, 0x2b, 0xdb, 0x94, 0xcf, 0x38, 0xf0, 0xab, 0xd0, 0xdd, 0xa2, 0xca, 0x99, 0xd1, 0x96, 0x1b, 0x47, 0xcf, 0xe3, 0x66, 0x85, 0x20, 0xce, 0x4a, 0x4e, 0x1a, 0x94, 0xa6, 0x12, 0x43, 0xbc, 0x4c, 0xa3, 0xc8, 0x44, 0xf9, 0x86, 0x1c, 0x7a, 0x4a, 0xb6, 0x45, 0x87, 0x5a, 0xc7, 0x34, 0x48, 0x6d, 0x29, 0xc, 0x25, 0x12, 0xa1, 0x4, 0x88, 0xdc, 0x34, 0x17, 0xd5, 0x5, 0x8e, 0xc4, 0xb4, 0x50, 0x3e, 0xc, 0x12, 0xea, 0x1a, 0xa, 0x89, 0xbe, 0x20, 0xf, 0xe9, 0x89, 0x22, 0x42, 0x3d, 0x43, 0x34, 0x1, 0x4f, 0xa6, 0xb0, 0xee, 0x0, 0x1, } assert.Equal(t, expectedProof, proof) } diff --git a/internal/primitives/consensus/grandpa/grandpa.go b/internal/primitives/consensus/grandpa/grandpa.go index 43bc7cd95f..c04ded8fab 100644 --- a/internal/primitives/consensus/grandpa/grandpa.go +++ b/internal/primitives/consensus/grandpa/grandpa.go @@ -55,6 +55,11 @@ type SignedMessage[H, N any] grandpa.SignedMessage[H, N, AuthoritySignature, Aut // Commit is a commit message for this chain's block type. type Commit[H, N any] grandpa.Commit[H, N, AuthoritySignature, AuthorityID] +type ScheduledChange[N runtime.Number] struct { + NextAuthorities AuthorityList + Delay N +} + // GrandpaJustification is A GRANDPA justification for block finality, it includes // a commit message and an ancestry proof including all headers routing all // precommit target blocks to the commit target block. Due to the current voting From e51021999137e8f3cdececc171007d67f650c761 Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 9 Oct 2024 14:59:06 -0300 Subject: [PATCH 02/15] Add scheduled change check --- internal/client/consensus/grandpa/warp_sync.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internal/client/consensus/grandpa/warp_sync.go b/internal/client/consensus/grandpa/warp_sync.go index 0878579619..be92b5a5d4 100644 --- a/internal/client/consensus/grandpa/warp_sync.go +++ b/internal/client/consensus/grandpa/warp_sync.go @@ -168,6 +168,15 @@ func (p *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { return nil, err } + // the last block in a set is the one that triggers a change to the next set, + // therefore the block must have a digest that signals the authority set change + if findScheduledChange(headerToGenericHeader(*header)) == nil { + // if it doesn't contain a signal for standard change then the set must have changed + // through a forced changed, in which case we stop collecting proofs as the chain of + // trust in authority handoffs was broken. + break + } + encJustification, err := p.blockState.GetJustification(header.Hash()) // get the justification of such block if err != nil { return nil, err From e7344f5b2082f0653fae13dd34835b5cb90f23a8 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 10 Oct 2024 11:28:18 -0300 Subject: [PATCH 03/15] Change generic to uint --- dot/network/warp_sync.go | 2 +- .../client/consensus/grandpa/warp_sync.go | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/dot/network/warp_sync.go b/dot/network/warp_sync.go index fab951ad7d..1c5c61b1ed 100644 --- a/dot/network/warp_sync.go +++ b/dot/network/warp_sync.go @@ -22,7 +22,7 @@ const MaxAllowedSameRequestPerPeer = 5 type WarpSyncVerificationResult struct { SetId grandpa.SetID AuthorityList grandpa.AuthorityList - Header generic.Header[uint64, hash.H256, runtime.BlakeTwo256] + Header generic.Header[uint, hash.H256, runtime.BlakeTwo256] Completed bool } diff --git a/internal/client/consensus/grandpa/warp_sync.go b/internal/client/consensus/grandpa/warp_sync.go index be92b5a5d4..0933b5e780 100644 --- a/internal/client/consensus/grandpa/warp_sync.go +++ b/internal/client/consensus/grandpa/warp_sync.go @@ -37,10 +37,10 @@ type GrandpaState interface { type WarpSyncFragment struct { // The last block that the given authority set finalized. This block should contain a digest // signalling an authority set change from which we can fetch the next authority set. - Header generic.Header[uint64, hash.H256, runtime.BlakeTwo256] + Header generic.Header[uint, hash.H256, runtime.BlakeTwo256] // A justification for the header above which proves its finality. In order to validate it the // verifier must be aware of the authorities and set id for which the justification refers to. - Justification GrandpaJustification[hash.H256, uint64] + Justification GrandpaJustification[hash.H256, uint] } type WarpSyncProof struct { @@ -74,7 +74,7 @@ func (w *WarpSyncProof) addFragment(fragment WarpSyncFragment) (limitReached boo return false, nil } -func (w *WarpSyncProof) lastProofBlockNumber() uint64 { +func (w *WarpSyncProof) lastProofBlockNumber() uint { if len(w.Proofs) == 0 { return 0 } @@ -182,7 +182,7 @@ func (p *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { return nil, err } - justification, err := decodeJustification[hash.H256, uint64, runtime.BlakeTwo256](encJustification) + justification, err := decodeJustification[hash.H256, uint, runtime.BlakeTwo256](encJustification) if err != nil { return nil, err } @@ -217,7 +217,7 @@ func (p *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { return nil, err } - justification, err := decodeJustification[hash.H256, uint64, runtime.BlakeTwo256](latestJustification) + justification, err := decodeJustification[hash.H256, uint, runtime.BlakeTwo256](latestJustification) if err != nil { return nil, err } @@ -279,19 +279,19 @@ func (p *WarpSyncProofProvider) Verify( } func findScheduledChange( - header generic.Header[uint64, hash.H256, runtime.BlakeTwo256], -) *grandpa.ScheduledChange[uint64] { + header generic.Header[uint, hash.H256, runtime.BlakeTwo256], +) *grandpa.ScheduledChange[uint] { panic("not implemented") } -func headerToGenericHeader(header types.Header) generic.Header[uint64, hash.H256, runtime.BlakeTwo256] { +func headerToGenericHeader(header types.Header) generic.Header[uint, hash.H256, runtime.BlakeTwo256] { digest := runtime.Digest{} for _, digestItem := range header.Digest { digest.Push(digestItem) } - return *generic.NewHeader[uint64, hash.H256, runtime.BlakeTwo256]( - uint64(header.Number), + return *generic.NewHeader[uint, hash.H256, runtime.BlakeTwo256]( + header.Number, hash.H256(header.ExtrinsicsRoot.String()), hash.H256(header.StateRoot.String()), hash.H256(header.ParentHash.String()), From 2cfe39e5f0e79cfe6470691f71404a1bfabc5fa1 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 10 Oct 2024 12:20:31 -0300 Subject: [PATCH 04/15] Modify types --- dot/network/mock_warp_sync_provider_test.go | 3 +- dot/network/warp_sync.go | 10 +-- dot/types/authority.go | 2 + .../client/consensus/grandpa/warp_sync.go | 78 ++++++++++++------- 4 files changed, 57 insertions(+), 36 deletions(-) diff --git a/dot/network/mock_warp_sync_provider_test.go b/dot/network/mock_warp_sync_provider_test.go index 04280930e7..38ef2b36b2 100644 --- a/dot/network/mock_warp_sync_provider_test.go +++ b/dot/network/mock_warp_sync_provider_test.go @@ -12,6 +12,7 @@ package network import ( reflect "reflect" + types "github.com/ChainSafe/gossamer/dot/types" grandpa "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" common "github.com/ChainSafe/gossamer/lib/common" gomock "go.uber.org/mock/gomock" @@ -56,7 +57,7 @@ func (mr *MockWarpSyncProviderMockRecorder) Generate(arg0 any) *gomock.Call { } // Verify mocks base method. -func (m *MockWarpSyncProvider) Verify(arg0 []byte, arg1 grandpa.SetID, arg2 grandpa.AuthorityList) (*WarpSyncVerificationResult, error) { +func (m *MockWarpSyncProvider) Verify(arg0 []byte, arg1 grandpa.SetID, arg2 types.AuthorityList) (*WarpSyncVerificationResult, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Verify", arg0, arg1, arg2) ret0, _ := ret[0].(*WarpSyncVerificationResult) diff --git a/dot/network/warp_sync.go b/dot/network/warp_sync.go index 1c5c61b1ed..825b0919a4 100644 --- a/dot/network/warp_sync.go +++ b/dot/network/warp_sync.go @@ -8,10 +8,8 @@ import ( "fmt" "github.com/ChainSafe/gossamer/dot/network/messages" + "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" - "github.com/ChainSafe/gossamer/internal/primitives/core/hash" - "github.com/ChainSafe/gossamer/internal/primitives/runtime" - "github.com/ChainSafe/gossamer/internal/primitives/runtime/generic" "github.com/ChainSafe/gossamer/lib/common" libp2pnetwork "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" @@ -21,8 +19,8 @@ const MaxAllowedSameRequestPerPeer = 5 type WarpSyncVerificationResult struct { SetId grandpa.SetID - AuthorityList grandpa.AuthorityList - Header generic.Header[uint, hash.H256, runtime.BlakeTwo256] + AuthorityList types.AuthorityList + Header types.Header Completed bool } @@ -34,7 +32,7 @@ type WarpSyncProvider interface { Verify( encodedProof []byte, setId grandpa.SetID, - authorities grandpa.AuthorityList, + authorities types.AuthorityList, ) (*WarpSyncVerificationResult, error) } diff --git a/dot/types/authority.go b/dot/types/authority.go index 66a7ccdd54..2fa1b554ce 100644 --- a/dot/types/authority.go +++ b/dot/types/authority.go @@ -167,3 +167,5 @@ func AuthoritiesRawToAuthorityAsAddress(authsRaw []AuthorityRaw, kt crypto.KeyTy } return auths, nil } + +type AuthorityList []Authority diff --git a/internal/client/consensus/grandpa/warp_sync.go b/internal/client/consensus/grandpa/warp_sync.go index 0933b5e780..70c70d3cce 100644 --- a/internal/client/consensus/grandpa/warp_sync.go +++ b/internal/client/consensus/grandpa/warp_sync.go @@ -4,14 +4,15 @@ package grandpa import ( + "bytes" "fmt" "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" + primitives "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" "github.com/ChainSafe/gossamer/internal/primitives/core/hash" "github.com/ChainSafe/gossamer/internal/primitives/runtime" - "github.com/ChainSafe/gossamer/internal/primitives/runtime/generic" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -37,7 +38,7 @@ type GrandpaState interface { type WarpSyncFragment struct { // The last block that the given authority set finalized. This block should contain a digest // signalling an authority set change from which we can fetch the next authority set. - Header generic.Header[uint, hash.H256, runtime.BlakeTwo256] + Header types.Header // A justification for the header above which proves its finality. In order to validate it the // verifier must be aware of the authorities and set id for which the justification refers to. Justification GrandpaJustification[hash.H256, uint] @@ -83,7 +84,7 @@ func (w *WarpSyncProof) lastProofBlockNumber() uint { func (w *WarpSyncProof) verify( setId grandpa.SetID, - authorities grandpa.AuthorityList, + authorities types.AuthorityList, hardForks map[string]SetIdAuthorityList, ) (*SetIdAuthorityList, error) { currentSetId := setId @@ -91,26 +92,44 @@ func (w *WarpSyncProof) verify( for fragmentNumber, proof := range w.Proofs { hash := proof.Header.Hash() - number := proof.Header.Number() + number := proof.Header.Number hardForkKey := fmt.Sprintf("%v-%v", hash, number) if fork, ok := hardForks[hardForkKey]; ok { currentSetId = fork.SetID currentAuthorities = fork.AuthorityList } else { - err := proof.Justification.Verify(uint64(currentSetId), currentAuthorities) + // Convert authorities to the format expected by the justification + var authorities primitives.AuthorityList + for _, auth := range currentAuthorities { + authorities = append(authorities, primitives.AuthorityIDWeight{ + AuthorityID: auth.ToRaw().Key, + AuthorityWeight: primitives.AuthorityWeight(auth.Weight), + }) + } + + err := proof.Justification.Verify(uint64(currentSetId), authorities) if err != nil { return nil, err } - if proof.Justification.Target().Hash != hash { + if !bytes.Equal(proof.Justification.Target().Hash.Bytes(), hash.ToBytes()) { return nil, fmt.Errorf("mismatch between header and justification") } - scheduledChange := findScheduledChange(proof.Header) + scheduledChange, err := findScheduledChange(proof.Header) + if err != nil { + return nil, fmt.Errorf("finding scheduled change: %w", err) + } + if scheduledChange != nil { + auths, err := types.GrandpaAuthoritiesRawToAuthorities(scheduledChange.Auths) + if err != nil { + return nil, fmt.Errorf("cannot parse GRANPDA raw authorities: %w", err) + } + currentSetId += 1 - currentAuthorities = scheduledChange.NextAuthorities + currentAuthorities = auths } else if fragmentNumber != len(w.Proofs)-1 || !w.IsFinished { return nil, fmt.Errorf("Header is missing authority set change digest") } @@ -122,7 +141,7 @@ func (w *WarpSyncProof) verify( type SetIdAuthorityList struct { grandpa.SetID - grandpa.AuthorityList + types.AuthorityList } type WarpSyncProofProvider struct { @@ -168,9 +187,14 @@ func (p *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { return nil, err } + scheduledChange, err := findScheduledChange(*header) + if err != nil { + return nil, fmt.Errorf("finding scheduled change: %w", err) + } + // the last block in a set is the one that triggers a change to the next set, // therefore the block must have a digest that signals the authority set change - if findScheduledChange(headerToGenericHeader(*header)) == nil { + if scheduledChange == nil { // if it doesn't contain a signal for standard change then the set must have changed // through a forced changed, in which case we stop collecting proofs as the chain of // trust in authority handoffs was broken. @@ -187,7 +211,7 @@ func (p *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { return nil, err } - fragment := WarpSyncFragment{Header: headerToGenericHeader(*header), Justification: *justification} + fragment := WarpSyncFragment{Header: *header, Justification: *justification} // check the proof size limitReached, err = finalProof.addFragment(fragment) @@ -223,7 +247,7 @@ func (p *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { } if justification.Justification.Commit.TargetNumber >= finalProof.lastProofBlockNumber() { - fragment := WarpSyncFragment{Header: headerToGenericHeader(*lastFinalizedBlockHeader), Justification: *justification} + fragment := WarpSyncFragment{Header: *lastFinalizedBlockHeader, Justification: *justification} _, err = finalProof.addFragment(fragment) if err != nil { return nil, err @@ -241,7 +265,7 @@ func (p *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { func (p *WarpSyncProofProvider) Verify( encodedProof []byte, setId grandpa.SetID, - authorities grandpa.AuthorityList, + authorities types.AuthorityList, ) (*network.WarpSyncVerificationResult, error) { var proof WarpSyncProof err := scale.Unmarshal(encodedProof, proof) @@ -279,22 +303,18 @@ func (p *WarpSyncProofProvider) Verify( } func findScheduledChange( - header generic.Header[uint, hash.H256, runtime.BlakeTwo256], -) *grandpa.ScheduledChange[uint] { - panic("not implemented") -} - -func headerToGenericHeader(header types.Header) generic.Header[uint, hash.H256, runtime.BlakeTwo256] { - digest := runtime.Digest{} + header types.Header, +) (*types.GrandpaScheduledChange, error) { for _, digestItem := range header.Digest { - digest.Push(digestItem) - } + digestValue, err := digestItem.Value() + if err != nil { + return nil, fmt.Errorf("getting digest value: %w", err) + } - return *generic.NewHeader[uint, hash.H256, runtime.BlakeTwo256]( - header.Number, - hash.H256(header.ExtrinsicsRoot.String()), - hash.H256(header.StateRoot.String()), - hash.H256(header.ParentHash.String()), - digest, - ) + switch val := digestValue.(type) { + case types.GrandpaScheduledChange: + return &val, nil + } + } + return nil, nil } From 7fbd92cd749178d9becbcc6a03c457863d789883 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 14 Oct 2024 10:47:37 -0300 Subject: [PATCH 05/15] Finish findScheduledChange method --- .../client/consensus/grandpa/warp_sync.go | 18 ++++- .../consensus/grandpa/warp_sync_test.go | 72 ++++++++++++++++--- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/internal/client/consensus/grandpa/warp_sync.go b/internal/client/consensus/grandpa/warp_sync.go index 70c70d3cce..4f8f6da176 100644 --- a/internal/client/consensus/grandpa/warp_sync.go +++ b/internal/client/consensus/grandpa/warp_sync.go @@ -312,8 +312,22 @@ func findScheduledChange( } switch val := digestValue.(type) { - case types.GrandpaScheduledChange: - return &val, nil + case types.ConsensusDigest: + consensusDigest := types.GrandpaConsensusDigest{} + if val.ConsensusEngineID == types.GrandpaEngineID { + err := scale.Unmarshal(val.Data, &consensusDigest) + if err != nil { + return nil, err + } + + scheduledChange, err := consensusDigest.Value() + if err != nil { + return nil, err + } + + parsedScheduledChange, _ := scheduledChange.(types.GrandpaScheduledChange) + return &parsedScheduledChange, nil + } } } return nil, nil diff --git a/internal/client/consensus/grandpa/warp_sync_test.go b/internal/client/consensus/grandpa/warp_sync_test.go index 9d238d957a..f348f73dc5 100644 --- a/internal/client/consensus/grandpa/warp_sync_test.go +++ b/internal/client/consensus/grandpa/warp_sync_test.go @@ -9,6 +9,7 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" ) @@ -84,28 +85,43 @@ func TestGenerateWarpSyncProofOk(t *testing.T) { parentHash = blockHeaders[blockNumber-2].Hash() } + digest := &types.Digest{} + + if blockNumber == 4 { + // Last block in a set must contain a grandpa scheduled change + // So we create an empty one just to pass the nil check + scheduledChange := createGRANDPAConsensusDigest(t, types.GrandpaScheduledChange{ + Auths: []types.GrandpaAuthoritiesRaw{}, + Delay: 2, + }) + consensuDigest := []types.ConsensusDigest{ + scheduledChange, + } + digest.Add(consensuDigest) + } + header = types.NewHeader( parentHash, common.Hash{byte(blockNumber)}, common.Hash{byte(blockNumber)}, blockNumber, - types.Digest{}, + *digest, ) - blockHeaders = append(blockHeaders, header) - - // Mock block state responses - blockStateMock.EXPECT().GetHeader(header.Hash()).Return(header, nil).AnyTimes() - blockStateMock.EXPECT().GetHeaderByNumber(blockNumber).Return(header, nil).AnyTimes() - // authorities set changes happens only in block 5 if blockNumber < 5 { - grandpaStateMock.EXPECT().GetAuthoritiesChangesFromBlock(blockNumber).Return([]uint{5}, nil).AnyTimes() + grandpaStateMock.EXPECT().GetAuthoritiesChangesFromBlock(blockNumber).Return([]uint{4}, nil).AnyTimes() } else if blockNumber == 5 { blockStateMock.EXPECT().GetJustification(header.Hash()).Return(encodedJustification1, nil).AnyTimes() } else { grandpaStateMock.EXPECT().GetAuthoritiesChangesFromBlock(blockNumber).Return([]uint{}, nil).AnyTimes() } + + blockHeaders = append(blockHeaders, header) + + // Mock block state responses + blockStateMock.EXPECT().GetHeader(header.Hash()).Return(header, nil).AnyTimes() + blockStateMock.EXPECT().GetHeaderByNumber(blockNumber).Return(header, nil).AnyTimes() } blockStateMock.EXPECT().GetHighestFinalisedHeader().Return(blockHeaders[len(blockHeaders)-1], nil).AnyTimes() @@ -124,3 +140,43 @@ func TestGenerateWarpSyncProofOk(t *testing.T) { } assert.Equal(t, expectedProof, proof) } + +func TestFindScheduledChange(t *testing.T) { + t.Parallel() + + scheduledChange := createGRANDPAConsensusDigest(t, types.GrandpaScheduledChange{ + Auths: []types.GrandpaAuthoritiesRaw{}, + Delay: 2, + }) + + digest := types.NewDigest() + digestAddArgs := make([]any, 1) + digestAddArgs[0] = types.ConsensusDigest(scheduledChange) + digest.Add(digestAddArgs...) + + blockHeader := &types.Header{ + ParentHash: common.Hash{0x00}, + Number: 1, + Digest: digest, + } + + // Find scheduled change in block header + scheduledChangeDigest, err := findScheduledChange(*blockHeader) + assert.NoError(t, err) + assert.NotNil(t, scheduledChangeDigest) +} + +func createGRANDPAConsensusDigest(t *testing.T, digestData any) types.ConsensusDigest { + t.Helper() + + grandpaConsensusDigest := types.NewGrandpaConsensusDigest() + assert.NoError(t, grandpaConsensusDigest.SetValue(digestData)) + + marshaledData, err := scale.Marshal(grandpaConsensusDigest) + assert.NoError(t, err) + + return types.ConsensusDigest{ + ConsensusEngineID: types.GrandpaEngineID, + Data: marshaledData, + } +} From 5c70a053766ec3e1e656f1a2000c34a098c2dfff Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 14 Oct 2024 11:09:34 -0300 Subject: [PATCH 06/15] Reuse uint64 for block numbers --- .../client/consensus/grandpa/warp_sync.go | 18 +++++++-------- .../consensus/grandpa/warp_sync_test.go | 23 +++++++++++-------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/internal/client/consensus/grandpa/warp_sync.go b/internal/client/consensus/grandpa/warp_sync.go index 4f8f6da176..35af64dab2 100644 --- a/internal/client/consensus/grandpa/warp_sync.go +++ b/internal/client/consensus/grandpa/warp_sync.go @@ -41,7 +41,7 @@ type WarpSyncFragment struct { Header types.Header // A justification for the header above which proves its finality. In order to validate it the // verifier must be aware of the authorities and set id for which the justification refers to. - Justification GrandpaJustification[hash.H256, uint] + Justification GrandpaJustification[hash.H256, uint64] } type WarpSyncProof struct { @@ -75,7 +75,7 @@ func (w *WarpSyncProof) addFragment(fragment WarpSyncFragment) (limitReached boo return false, nil } -func (w *WarpSyncProof) lastProofBlockNumber() uint { +func (w *WarpSyncProof) lastProofBlockNumber() uint64 { if len(w.Proofs) == 0 { return 0 } @@ -139,11 +139,6 @@ func (w *WarpSyncProof) verify( return &SetIdAuthorityList{currentSetId, currentAuthorities}, nil } -type SetIdAuthorityList struct { - grandpa.SetID - types.AuthorityList -} - type WarpSyncProofProvider struct { blockState BlockState grandpaState GrandpaState @@ -157,6 +152,11 @@ func NewWarpSyncProofProvider(blockState BlockState, grandpaState GrandpaState) } } +type SetIdAuthorityList struct { + grandpa.SetID + types.AuthorityList +} + // Generate build a warp sync encoded proof starting from the given block hash func (p *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { // Get and traverse all GRANDPA authorities changes from the given block hash @@ -206,7 +206,7 @@ func (p *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { return nil, err } - justification, err := decodeJustification[hash.H256, uint, runtime.BlakeTwo256](encJustification) + justification, err := decodeJustification[hash.H256, uint64, runtime.BlakeTwo256](encJustification) if err != nil { return nil, err } @@ -241,7 +241,7 @@ func (p *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { return nil, err } - justification, err := decodeJustification[hash.H256, uint, runtime.BlakeTwo256](latestJustification) + justification, err := decodeJustification[hash.H256, uint64, runtime.BlakeTwo256](latestJustification) if err != nil { return nil, err } diff --git a/internal/client/consensus/grandpa/warp_sync_test.go b/internal/client/consensus/grandpa/warp_sync_test.go index f348f73dc5..4f71de5128 100644 --- a/internal/client/consensus/grandpa/warp_sync_test.go +++ b/internal/client/consensus/grandpa/warp_sync_test.go @@ -85,7 +85,7 @@ func TestGenerateWarpSyncProofOk(t *testing.T) { parentHash = blockHeaders[blockNumber-2].Hash() } - digest := &types.Digest{} + digest := types.NewDigest() if blockNumber == 4 { // Last block in a set must contain a grandpa scheduled change @@ -94,10 +94,10 @@ func TestGenerateWarpSyncProofOk(t *testing.T) { Auths: []types.GrandpaAuthoritiesRaw{}, Delay: 2, }) - consensuDigest := []types.ConsensusDigest{ - scheduledChange, - } - digest.Add(consensuDigest) + + digestAddArgs := make([]any, 1) + digestAddArgs[0] = types.ConsensusDigest(scheduledChange) + digest.Add(digestAddArgs...) } header = types.NewHeader( @@ -105,14 +105,17 @@ func TestGenerateWarpSyncProofOk(t *testing.T) { common.Hash{byte(blockNumber)}, common.Hash{byte(blockNumber)}, blockNumber, - *digest, + digest, ) - // authorities set changes happens only in block 5 + t.Logf("Header %d: %s", blockNumber, header.Hash().String()) + + // authorities set changes happens only in block 4 if blockNumber < 5 { grandpaStateMock.EXPECT().GetAuthoritiesChangesFromBlock(blockNumber).Return([]uint{4}, nil).AnyTimes() - } else if blockNumber == 5 { - blockStateMock.EXPECT().GetJustification(header.Hash()).Return(encodedJustification1, nil).AnyTimes() + if blockNumber == 4 { + blockStateMock.EXPECT().GetJustification(header.Hash()).Return(encodedJustification1, nil).AnyTimes() + } } else { grandpaStateMock.EXPECT().GetAuthoritiesChangesFromBlock(blockNumber).Return([]uint{}, nil).AnyTimes() } @@ -136,7 +139,7 @@ func TestGenerateWarpSyncProofOk(t *testing.T) { assert.NoError(t, err) expectedProof := []byte{ - 0x4, 0x30, 0x78, 0x31, 0x63, 0x61, 0x34, 0x30, 0x32, 0x32, 0x35, 0x37, 0x31, 0x38, 0x36, 0x65, 0x65, 0x34, 0x33, 0x34, 0x36, 0x66, 0x64, 0x32, 0x63, 0x30, 0x39, 0x66, 0x65, 0x65, 0x62, 0x39, 0x31, 0x14, 0x30, 0x78, 0x30, 0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x30, 0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xec, 0x94, 0x1a, 0xe4, 0xe1, 0x86, 0x62, 0x8c, 0x96, 0x9a, 0x17, 0xb9, 0x2b, 0xe7, 0xac, 0xc2, 0x45, 0x96, 0x1b, 0xbf, 0xca, 0x32, 0x6c, 0x5b, 0xdc, 0x39, 0xd6, 0x2f, 0xca, 0x3e, 0x46, 0xee, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0xec, 0x94, 0x1a, 0xe4, 0xe1, 0x86, 0x62, 0x8c, 0x96, 0x9a, 0x17, 0xb9, 0x2b, 0xe7, 0xac, 0xc2, 0x45, 0x96, 0x1b, 0xbf, 0xca, 0x32, 0x6c, 0x5b, 0xdc, 0x39, 0xd6, 0x2f, 0xca, 0x3e, 0x46, 0xee, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc9, 0xe8, 0x1a, 0x88, 0x1f, 0x4d, 0xf, 0xc2, 0x22, 0xc8, 0xf8, 0x2b, 0xdb, 0x94, 0xcf, 0x38, 0xf0, 0xab, 0xd0, 0xdd, 0xa2, 0xca, 0x99, 0xd1, 0x96, 0x1b, 0x47, 0xcf, 0xe3, 0x66, 0x85, 0x20, 0xce, 0x4a, 0x4e, 0x1a, 0x94, 0xa6, 0x12, 0x43, 0xbc, 0x4c, 0xa3, 0xc8, 0x44, 0xf9, 0x86, 0x1c, 0x7a, 0x4a, 0xb6, 0x45, 0x87, 0x5a, 0xc7, 0x34, 0x48, 0x6d, 0x29, 0xc, 0x25, 0x12, 0xa1, 0x4, 0x88, 0xdc, 0x34, 0x17, 0xd5, 0x5, 0x8e, 0xc4, 0xb4, 0x50, 0x3e, 0xc, 0x12, 0xea, 0x1a, 0xa, 0x89, 0xbe, 0x20, 0xf, 0xe9, 0x89, 0x22, 0x42, 0x3d, 0x43, 0x34, 0x1, 0x4f, 0xa6, 0xb0, 0xee, 0x0, 0x1, + 0x4, 0x85, 0xd0, 0xc0, 0x96, 0xc5, 0x1d, 0xec, 0x8c, 0x52, 0xa2, 0x9d, 0xaf, 0xa8, 0x7a, 0x21, 0x4d, 0x26, 0xc, 0xc7, 0x34, 0xdb, 0x9b, 0xf6, 0x19, 0xbc, 0x64, 0x19, 0xa3, 0xe9, 0x34, 0x8d, 0xa4, 0x10, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x46, 0x52, 0x4e, 0x4b, 0x18, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xec, 0x94, 0x1a, 0xe4, 0xe1, 0x86, 0x62, 0x8c, 0x96, 0x9a, 0x17, 0xb9, 0x2b, 0xe7, 0xac, 0xc2, 0x45, 0x96, 0x1b, 0xbf, 0xca, 0x32, 0x6c, 0x5b, 0xdc, 0x39, 0xd6, 0x2f, 0xca, 0x3e, 0x46, 0xee, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0xec, 0x94, 0x1a, 0xe4, 0xe1, 0x86, 0x62, 0x8c, 0x96, 0x9a, 0x17, 0xb9, 0x2b, 0xe7, 0xac, 0xc2, 0x45, 0x96, 0x1b, 0xbf, 0xca, 0x32, 0x6c, 0x5b, 0xdc, 0x39, 0xd6, 0x2f, 0xca, 0x3e, 0x46, 0xee, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc9, 0xe8, 0x1a, 0x88, 0x1f, 0x4d, 0xf, 0xc2, 0x22, 0xc8, 0xf8, 0x2b, 0xdb, 0x94, 0xcf, 0x38, 0xf0, 0xab, 0xd0, 0xdd, 0xa2, 0xca, 0x99, 0xd1, 0x96, 0x1b, 0x47, 0xcf, 0xe3, 0x66, 0x85, 0x20, 0xce, 0x4a, 0x4e, 0x1a, 0x94, 0xa6, 0x12, 0x43, 0xbc, 0x4c, 0xa3, 0xc8, 0x44, 0xf9, 0x86, 0x1c, 0x7a, 0x4a, 0xb6, 0x45, 0x87, 0x5a, 0xc7, 0x34, 0x48, 0x6d, 0x29, 0xc, 0x25, 0x12, 0xa1, 0x4, 0x88, 0xdc, 0x34, 0x17, 0xd5, 0x5, 0x8e, 0xc4, 0xb4, 0x50, 0x3e, 0xc, 0x12, 0xea, 0x1a, 0xa, 0x89, 0xbe, 0x20, 0xf, 0xe9, 0x89, 0x22, 0x42, 0x3d, 0x43, 0x34, 0x1, 0x4f, 0xa6, 0xb0, 0xee, 0x0, 0x1, } assert.Equal(t, expectedProof, proof) } From 99dadc476cc02ee2608a11ba12a802dbbc0baa3e Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 14 Oct 2024 11:14:10 -0300 Subject: [PATCH 07/15] Remove unnecesary type conversion --- internal/client/consensus/grandpa/warp_sync_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/client/consensus/grandpa/warp_sync_test.go b/internal/client/consensus/grandpa/warp_sync_test.go index 4f71de5128..837fecce3b 100644 --- a/internal/client/consensus/grandpa/warp_sync_test.go +++ b/internal/client/consensus/grandpa/warp_sync_test.go @@ -96,7 +96,7 @@ func TestGenerateWarpSyncProofOk(t *testing.T) { }) digestAddArgs := make([]any, 1) - digestAddArgs[0] = types.ConsensusDigest(scheduledChange) + digestAddArgs[0] = scheduledChange digest.Add(digestAddArgs...) } @@ -154,7 +154,7 @@ func TestFindScheduledChange(t *testing.T) { digest := types.NewDigest() digestAddArgs := make([]any, 1) - digestAddArgs[0] = types.ConsensusDigest(scheduledChange) + digestAddArgs[0] = scheduledChange digest.Add(digestAddArgs...) blockHeader := &types.Header{ From c6a47c3ac59980332111e2558e5025e7db8e8068 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 14 Oct 2024 11:15:04 -0300 Subject: [PATCH 08/15] Simplify digest creation --- internal/client/consensus/grandpa/warp_sync_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/internal/client/consensus/grandpa/warp_sync_test.go b/internal/client/consensus/grandpa/warp_sync_test.go index 837fecce3b..1989c18c3f 100644 --- a/internal/client/consensus/grandpa/warp_sync_test.go +++ b/internal/client/consensus/grandpa/warp_sync_test.go @@ -95,9 +95,7 @@ func TestGenerateWarpSyncProofOk(t *testing.T) { Delay: 2, }) - digestAddArgs := make([]any, 1) - digestAddArgs[0] = scheduledChange - digest.Add(digestAddArgs...) + digest.Add(scheduledChange) } header = types.NewHeader( @@ -153,9 +151,7 @@ func TestFindScheduledChange(t *testing.T) { }) digest := types.NewDigest() - digestAddArgs := make([]any, 1) - digestAddArgs[0] = scheduledChange - digest.Add(digestAddArgs...) + digest.Add(scheduledChange) blockHeader := &types.Header{ ParentHash: common.Hash{0x00}, From e027ba32d545f7c128493d613079085a783f99dd Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 15 Oct 2024 17:44:17 -0300 Subject: [PATCH 09/15] Better testing --- dot/network/warp_sync.go | 3 +- .../client/consensus/grandpa/warp_sync.go | 40 ++-- .../consensus/grandpa/warp_sync_test.go | 200 ++++++++++++++---- .../primitives/keyring/ed25519/ed25519.go | 11 + 4 files changed, 194 insertions(+), 60 deletions(-) diff --git a/dot/network/warp_sync.go b/dot/network/warp_sync.go index 825b0919a4..752d23b575 100644 --- a/dot/network/warp_sync.go +++ b/dot/network/warp_sync.go @@ -10,6 +10,7 @@ import ( "github.com/ChainSafe/gossamer/dot/network/messages" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" + primitives "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" "github.com/ChainSafe/gossamer/lib/common" libp2pnetwork "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" @@ -19,7 +20,7 @@ const MaxAllowedSameRequestPerPeer = 5 type WarpSyncVerificationResult struct { SetId grandpa.SetID - AuthorityList types.AuthorityList + AuthorityList primitives.AuthorityList Header types.Header Completed bool } diff --git a/internal/client/consensus/grandpa/warp_sync.go b/internal/client/consensus/grandpa/warp_sync.go index 35af64dab2..a88a449489 100644 --- a/internal/client/consensus/grandpa/warp_sync.go +++ b/internal/client/consensus/grandpa/warp_sync.go @@ -11,9 +11,11 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" primitives "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" + "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa/app" "github.com/ChainSafe/gossamer/internal/primitives/core/hash" "github.com/ChainSafe/gossamer/internal/primitives/runtime" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -84,7 +86,7 @@ func (w *WarpSyncProof) lastProofBlockNumber() uint64 { func (w *WarpSyncProof) verify( setId grandpa.SetID, - authorities types.AuthorityList, + authorities primitives.AuthorityList, hardForks map[string]SetIdAuthorityList, ) (*SetIdAuthorityList, error) { currentSetId := setId @@ -99,15 +101,6 @@ func (w *WarpSyncProof) verify( currentSetId = fork.SetID currentAuthorities = fork.AuthorityList } else { - // Convert authorities to the format expected by the justification - var authorities primitives.AuthorityList - for _, auth := range currentAuthorities { - authorities = append(authorities, primitives.AuthorityIDWeight{ - AuthorityID: auth.ToRaw().Key, - AuthorityWeight: primitives.AuthorityWeight(auth.Weight), - }) - } - err := proof.Justification.Verify(uint64(currentSetId), authorities) if err != nil { return nil, err @@ -123,7 +116,7 @@ func (w *WarpSyncProof) verify( } if scheduledChange != nil { - auths, err := types.GrandpaAuthoritiesRawToAuthorities(scheduledChange.Auths) + auths, err := grandpaAuthoritiesRawToAuthorities(scheduledChange.Auths) if err != nil { return nil, fmt.Errorf("cannot parse GRANPDA raw authorities: %w", err) } @@ -154,7 +147,7 @@ func NewWarpSyncProofProvider(blockState BlockState, grandpaState GrandpaState) type SetIdAuthorityList struct { grandpa.SetID - types.AuthorityList + primitives.AuthorityList } // Generate build a warp sync encoded proof starting from the given block hash @@ -265,10 +258,10 @@ func (p *WarpSyncProofProvider) Generate(start common.Hash) ([]byte, error) { func (p *WarpSyncProofProvider) Verify( encodedProof []byte, setId grandpa.SetID, - authorities types.AuthorityList, + authorities primitives.AuthorityList, ) (*network.WarpSyncVerificationResult, error) { var proof WarpSyncProof - err := scale.Unmarshal(encodedProof, proof) + err := scale.Unmarshal(encodedProof, &proof) if err != nil { return nil, fmt.Errorf("decoding warp sync proof: %w", err) } @@ -332,3 +325,22 @@ func findScheduledChange( } return nil, nil } + +func grandpaAuthoritiesRawToAuthorities(adr []types.GrandpaAuthoritiesRaw) (primitives.AuthorityList, error) { + ad := make([]primitives.AuthorityIDWeight, len(adr)) + for i, r := range adr { + ad[i] = primitives.AuthorityIDWeight{} + + key, err := ed25519.NewPublicKey(r.Key[:]) + if err != nil { + return nil, err + } + + keyBytes := key.AsBytes() + pkey, err := app.NewPublic(keyBytes[:]) + ad[i].AuthorityID = pkey + ad[i].AuthorityWeight = primitives.AuthorityWeight(r.ID) + } + + return ad, nil +} diff --git a/internal/client/consensus/grandpa/warp_sync_test.go b/internal/client/consensus/grandpa/warp_sync_test.go index 1989c18c3f..e5deb1f071 100644 --- a/internal/client/consensus/grandpa/warp_sync_test.go +++ b/internal/client/consensus/grandpa/warp_sync_test.go @@ -5,12 +5,21 @@ package grandpa import ( "errors" + "math/rand" "testing" "github.com/ChainSafe/gossamer/dot/types" + primitives "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" + ced25519 "github.com/ChainSafe/gossamer/internal/primitives/core/ed25519" + "github.com/ChainSafe/gossamer/internal/primitives/core/hash" + "github.com/ChainSafe/gossamer/internal/primitives/keyring/ed25519" + "github.com/ChainSafe/gossamer/internal/primitives/runtime" + "github.com/ChainSafe/gossamer/internal/primitives/runtime/generic" "github.com/ChainSafe/gossamer/lib/common" + grandpa "github.com/ChainSafe/gossamer/pkg/finality-grandpa" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) @@ -66,80 +75,164 @@ func TestGenerateWarpSyncProofBlockNotFinalized(t *testing.T) { } //nolint:lll -func TestGenerateWarpSyncProofOk(t *testing.T) { +func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) defer ctrl.Finish() - encodedJustification1 := []byte{42, 0, 0, 0, 0, 0, 0, 0, 236, 148, 26, 228, 225, 134, 98, 140, 150, 154, 23, 185, 43, 231, 172, 194, 69, 150, 27, 191, 202, 50, 108, 91, 220, 57, 214, 47, 202, 62, 70, 238, 10, 0, 0, 0, 0, 0, 0, 0, 4, 236, 148, 26, 228, 225, 134, 98, 140, 150, 154, 23, 185, 43, 231, 172, 194, 69, 150, 27, 191, 202, 50, 108, 91, 220, 57, 214, 47, 202, 62, 70, 238, 10, 0, 0, 0, 0, 0, 0, 0, 201, 232, 26, 136, 31, 77, 15, 194, 34, 200, 248, 43, 219, 148, 207, 56, 240, 171, 208, 221, 162, 202, 153, 209, 150, 27, 71, 207, 227, 102, 133, 32, 206, 74, 78, 26, 148, 166, 18, 67, 188, 76, 163, 200, 68, 249, 134, 28, 122, 74, 182, 69, 135, 90, 199, 52, 72, 109, 41, 12, 37, 18, 161, 4, 136, 220, 52, 23, 213, 5, 142, 196, 180, 80, 62, 12, 18, 234, 26, 10, 137, 190, 32, 15, 233, 137, 34, 66, 61, 67, 52, 1, 79, 166, 176, 238, 0} - encodedJustification2 := []byte{50, 0, 0, 0, 0, 0, 0, 0, 236, 148, 26, 228, 225, 134, 98, 140, 150, 154, 23, 185, 43, 231, 172, 194, 69, 150, 27, 191, 202, 50, 108, 91, 220, 57, 214, 47, 202, 62, 70, 238, 10, 0, 0, 0, 0, 0, 0, 0, 4, 236, 148, 26, 228, 225, 134, 98, 140, 150, 154, 23, 185, 43, 231, 172, 194, 69, 150, 27, 191, 202, 50, 108, 91, 220, 57, 214, 47, 202, 62, 70, 238, 10, 0, 0, 0, 0, 0, 0, 0, 201, 232, 26, 136, 31, 77, 15, 194, 34, 200, 248, 43, 219, 148, 207, 56, 240, 171, 208, 221, 162, 202, 153, 209, 150, 27, 71, 207, 227, 102, 133, 32, 206, 74, 78, 26, 148, 166, 18, 67, 188, 76, 163, 200, 68, 249, 134, 28, 122, 74, 182, 69, 135, 90, 199, 52, 72, 109, 41, 12, 37, 18, 161, 4, 136, 220, 52, 23, 213, 5, 142, 196, 180, 80, 62, 12, 18, 234, 26, 10, 137, 190, 32, 15, 233, 137, 34, 66, 61, 67, 52, 1, 79, 166, 176, 238, 0} - var blockHeaders []*types.Header - blockStateMock := NewMockBlockState(ctrl) grandpaStateMock := NewMockGrandpaState(ctrl) - for blockNumber := uint(1); blockNumber <= 10; blockNumber++ { - // Create block header - var header *types.Header - parentHash := common.Hash{0x00} - if blockNumber > 1 { - parentHash = blockHeaders[blockNumber-2].Hash() - } + availableAuthorities := ed25519.AvailableAuthorities + genesisAuthorities := primitives.AuthorityList{ + primitives.AuthorityIDWeight{ + AuthorityID: ed25519.Alice.Pair().Public().(ced25519.Public), + AuthorityWeight: 1, + }, + } + + currentAuthorities := []ed25519.Keyring{ed25519.Alice} + currentSetId := uint64(0) + authoritySetChanges := []uint{} + + lastBlockHeader := &types.Header{ + ParentHash: common.MustBlake2bHash([]byte("genesis")), + Number: 1, + } + + headers := []*types.Header{ + lastBlockHeader, + } + + const maxBlocks = 5 + + for n := uint(1); n <= maxBlocks; n++ { + newAuthorities := []ed25519.Keyring{} digest := types.NewDigest() - if blockNumber == 4 { - // Last block in a set must contain a grandpa scheduled change - // So we create an empty one just to pass the nil check + // Authority set change happens every 10 blocks + if n != 0 && n%2 == 0 { + nAuthorities := rand.Intn(len(availableAuthorities)) + rand.Shuffle(len(availableAuthorities), func(i, j int) { + availableAuthorities[i], availableAuthorities[j] = availableAuthorities[j], availableAuthorities[i] + }) + + selectedAuthorities := availableAuthorities[:nAuthorities] + newAuthorities = selectedAuthorities + + nextAuthorities := []types.GrandpaAuthoritiesRaw{} + + for _, key := range selectedAuthorities { + nextAuthorities = append(nextAuthorities, + types.GrandpaAuthoritiesRaw{ + Key: [32]byte(key.Pair().Public().Bytes()), + ID: 1, + }, + ) + } + scheduledChange := createGRANDPAConsensusDigest(t, types.GrandpaScheduledChange{ - Auths: []types.GrandpaAuthoritiesRaw{}, - Delay: 2, + Auths: nextAuthorities, + Delay: 0, }) digest.Add(scheduledChange) } - header = types.NewHeader( - parentHash, - common.Hash{byte(blockNumber)}, - common.Hash{byte(blockNumber)}, - blockNumber, - digest, - ) - - t.Logf("Header %d: %s", blockNumber, header.Hash().String()) - - // authorities set changes happens only in block 4 - if blockNumber < 5 { - grandpaStateMock.EXPECT().GetAuthoritiesChangesFromBlock(blockNumber).Return([]uint{4}, nil).AnyTimes() - if blockNumber == 4 { - blockStateMock.EXPECT().GetJustification(header.Hash()).Return(encodedJustification1, nil).AnyTimes() + header := &types.Header{ + ParentHash: lastBlockHeader.Hash(), + Number: lastBlockHeader.Number + 1, + Digest: digest, + } + + headers = append(headers, header) + + lastBlockHeader = header + + if len(newAuthorities) > 0 { + targetHash := lastBlockHeader.Hash() + targetNumber := uint64(lastBlockHeader.Number) + + precommits := []grandpa.SignedPrecommit[hash.H256, uint64, primitives.AuthoritySignature, primitives.AuthorityID]{} + + for _, voter := range currentAuthorities { + precommit := grandpa.Precommit[hash.H256, uint64]{ + TargetHash: hash.H256(targetHash.String()), + TargetNumber: targetNumber, + } + + msg := grandpa.NewMessage[hash.H256, uint64, grandpa.Precommit[hash.H256, uint64]](precommit) + encoded := primitives.NewLocalizedPayload(1, primitives.SetID(currentSetId), msg) + signature := voter.Sign(encoded) + + signedPreCommit := grandpa.SignedPrecommit[hash.H256, uint64, primitives.AuthoritySignature, primitives.AuthorityID]{ + Precommit: grandpa.Precommit[hash.H256, uint64]{ + TargetHash: hash.H256(targetHash.String()), + TargetNumber: targetNumber, + }, + Signature: signature, + ID: voter.Pair().Public().(ced25519.Public), + } + + precommits = append(precommits, signedPreCommit) + } + + justification := GrandpaJustification[hash.H256, uint64]{ + Justification: primitives.GrandpaJustification[hash.H256, uint64]{ + Round: 1, + Commit: primitives.Commit[hash.H256, uint64]{ + TargetHash: hash.H256(targetHash.String()), + TargetNumber: targetNumber, + Precommits: precommits, + }, + VoteAncestries: genericHeadersList(t, headers), + }, } - } else { - grandpaStateMock.EXPECT().GetAuthoritiesChangesFromBlock(blockNumber).Return([]uint{}, nil).AnyTimes() + + encodedJustification, err := scale.Marshal(justification) + require.NoError(t, err) + + blockStateMock.EXPECT().GetJustification(header.Hash()).Return(encodedJustification, nil).AnyTimes() + blockStateMock.EXPECT().GetHighestFinalisedHeader().Return(header, nil).AnyTimes() + + authoritySetChanges = append(authoritySetChanges, n) + currentSetId++ + currentAuthorities = newAuthorities } - blockHeaders = append(blockHeaders, header) + } - // Mock block state responses - blockStateMock.EXPECT().GetHeader(header.Hash()).Return(header, nil).AnyTimes() - blockStateMock.EXPECT().GetHeaderByNumber(blockNumber).Return(header, nil).AnyTimes() + authChanges := []uint{} + for n := uint(1); n <= maxBlocks; n++ { + for _, change := range authoritySetChanges { + if n <= change { + authChanges = append(authChanges, change) + } + } + grandpaStateMock.EXPECT().GetAuthoritiesChangesFromBlock(n).Return(authChanges, nil).AnyTimes() } - blockStateMock.EXPECT().GetHighestFinalisedHeader().Return(blockHeaders[len(blockHeaders)-1], nil).AnyTimes() - blockStateMock.EXPECT().GetJustification(blockHeaders[len(blockHeaders)-1].Hash()).Return(encodedJustification2, nil).AnyTimes() + for _, header := range headers { + blockStateMock.EXPECT().GetHeaderByNumber(header.Number).Return(header, nil).AnyTimes() + blockStateMock.EXPECT().GetHeader(header.Hash()).Return(header, nil).AnyTimes() + } provider := &WarpSyncProofProvider{ blockState: blockStateMock, grandpaState: grandpaStateMock, } - proof, err := provider.Generate(blockHeaders[0].Hash()) + // Generate proof + proof, err := provider.Generate(headers[0].Hash()) assert.NoError(t, err) - expectedProof := []byte{ - 0x4, 0x85, 0xd0, 0xc0, 0x96, 0xc5, 0x1d, 0xec, 0x8c, 0x52, 0xa2, 0x9d, 0xaf, 0xa8, 0x7a, 0x21, 0x4d, 0x26, 0xc, 0xc7, 0x34, 0xdb, 0x9b, 0xf6, 0x19, 0xbc, 0x64, 0x19, 0xa3, 0xe9, 0x34, 0x8d, 0xa4, 0x10, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x46, 0x52, 0x4e, 0x4b, 0x18, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xec, 0x94, 0x1a, 0xe4, 0xe1, 0x86, 0x62, 0x8c, 0x96, 0x9a, 0x17, 0xb9, 0x2b, 0xe7, 0xac, 0xc2, 0x45, 0x96, 0x1b, 0xbf, 0xca, 0x32, 0x6c, 0x5b, 0xdc, 0x39, 0xd6, 0x2f, 0xca, 0x3e, 0x46, 0xee, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0xec, 0x94, 0x1a, 0xe4, 0xe1, 0x86, 0x62, 0x8c, 0x96, 0x9a, 0x17, 0xb9, 0x2b, 0xe7, 0xac, 0xc2, 0x45, 0x96, 0x1b, 0xbf, 0xca, 0x32, 0x6c, 0x5b, 0xdc, 0x39, 0xd6, 0x2f, 0xca, 0x3e, 0x46, 0xee, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc9, 0xe8, 0x1a, 0x88, 0x1f, 0x4d, 0xf, 0xc2, 0x22, 0xc8, 0xf8, 0x2b, 0xdb, 0x94, 0xcf, 0x38, 0xf0, 0xab, 0xd0, 0xdd, 0xa2, 0xca, 0x99, 0xd1, 0x96, 0x1b, 0x47, 0xcf, 0xe3, 0x66, 0x85, 0x20, 0xce, 0x4a, 0x4e, 0x1a, 0x94, 0xa6, 0x12, 0x43, 0xbc, 0x4c, 0xa3, 0xc8, 0x44, 0xf9, 0x86, 0x1c, 0x7a, 0x4a, 0xb6, 0x45, 0x87, 0x5a, 0xc7, 0x34, 0x48, 0x6d, 0x29, 0xc, 0x25, 0x12, 0xa1, 0x4, 0x88, 0xdc, 0x34, 0x17, 0xd5, 0x5, 0x8e, 0xc4, 0xb4, 0x50, 0x3e, 0xc, 0x12, 0xea, 0x1a, 0xa, 0x89, 0xbe, 0x20, 0xf, 0xe9, 0x89, 0x22, 0x42, 0x3d, 0x43, 0x34, 0x1, 0x4f, 0xa6, 0xb0, 0xee, 0x0, 0x1, - } - assert.Equal(t, expectedProof, proof) + // Verify proof + result, err := provider.Verify(proof, 0, genesisAuthorities) + assert.NoError(t, err) + assert.Equal(t, currentSetId, result.SetId) + assert.Equal(t, currentAuthorities, result.AuthorityList) } func TestFindScheduledChange(t *testing.T) { @@ -179,3 +272,20 @@ func createGRANDPAConsensusDigest(t *testing.T, digestData any) types.ConsensusD Data: marshaledData, } } + +func genericHeadersList(t *testing.T, headers []*types.Header) []runtime.Header[uint64, hash.H256] { + t.Helper() + + headerList := []runtime.Header[uint64, hash.H256]{} + for _, header := range headers { + if header == nil { + continue + } + newHeader := generic.Header[uint64, hash.H256, runtime.BlakeTwo256]{} + newHeader.SetParentHash(hash.H256(header.ParentHash.String())) + newHeader.SetNumber(uint64(header.Number)) + newHeader.DigestMut().Push(header.Digest) + } + + return headerList +} diff --git a/internal/primitives/keyring/ed25519/ed25519.go b/internal/primitives/keyring/ed25519/ed25519.go index 556cfe66ec..76ec3c8c03 100644 --- a/internal/primitives/keyring/ed25519/ed25519.go +++ b/internal/primitives/keyring/ed25519/ed25519.go @@ -22,6 +22,17 @@ const ( Two ) +var AvailableAuthorities = []Keyring{ + Alice, + Bob, + Charlie, + Dave, + Eve, + Ferdie, + One, + Two, +} + func (k Keyring) Sign(msg []byte) ed25519.Signature { return k.Pair().Sign(msg) } From 6c9e1801017d30b015400dbaa46be64c99071add Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 15 Oct 2024 18:20:23 -0300 Subject: [PATCH 10/15] Tests finished --- .../client/consensus/grandpa/warp_sync.go | 6 +- .../consensus/grandpa/warp_sync_test.go | 59 ++++++++++++------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/internal/client/consensus/grandpa/warp_sync.go b/internal/client/consensus/grandpa/warp_sync.go index a88a449489..3ca77d19f7 100644 --- a/internal/client/consensus/grandpa/warp_sync.go +++ b/internal/client/consensus/grandpa/warp_sync.go @@ -93,10 +93,10 @@ func (w *WarpSyncProof) verify( currentAuthorities := authorities for fragmentNumber, proof := range w.Proofs { - hash := proof.Header.Hash() + headerHash := proof.Header.Hash() number := proof.Header.Number - hardForkKey := fmt.Sprintf("%v-%v", hash, number) + hardForkKey := fmt.Sprintf("%v-%v", headerHash, number) if fork, ok := hardForks[hardForkKey]; ok { currentSetId = fork.SetID currentAuthorities = fork.AuthorityList @@ -106,7 +106,7 @@ func (w *WarpSyncProof) verify( return nil, err } - if !bytes.Equal(proof.Justification.Target().Hash.Bytes(), hash.ToBytes()) { + if !bytes.Equal(proof.Justification.Target().Hash.Bytes(), headerHash.ToBytes()) { return nil, fmt.Errorf("mismatch between header and justification") } diff --git a/internal/client/consensus/grandpa/warp_sync_test.go b/internal/client/consensus/grandpa/warp_sync_test.go index e5deb1f071..26c424d605 100644 --- a/internal/client/consensus/grandpa/warp_sync_test.go +++ b/internal/client/consensus/grandpa/warp_sync_test.go @@ -78,6 +78,9 @@ func TestGenerateWarpSyncProofBlockNotFinalized(t *testing.T) { func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { t.Parallel() + type signedPrecommit = grandpa.SignedPrecommit[hash.H256, uint64, primitives.AuthoritySignature, primitives.AuthorityID] + type preCommit = grandpa.Precommit[hash.H256, uint64] + ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -93,7 +96,7 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { } currentAuthorities := []ed25519.Keyring{ed25519.Alice} - currentSetId := uint64(0) + currentSetId := primitives.SetID(0) authoritySetChanges := []uint{} lastBlockHeader := &types.Header{ @@ -105,7 +108,7 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { lastBlockHeader, } - const maxBlocks = 5 + const maxBlocks = 2 for n := uint(1); n <= maxBlocks; n++ { newAuthorities := []ed25519.Keyring{} @@ -152,24 +155,24 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { lastBlockHeader = header if len(newAuthorities) > 0 { - targetHash := lastBlockHeader.Hash() - targetNumber := uint64(lastBlockHeader.Number) + targetHash := hash.H256(string(header.Hash().ToBytes())) + targetNumber := uint64(header.Number) - precommits := []grandpa.SignedPrecommit[hash.H256, uint64, primitives.AuthoritySignature, primitives.AuthorityID]{} + precommits := []signedPrecommit{} for _, voter := range currentAuthorities { - precommit := grandpa.Precommit[hash.H256, uint64]{ - TargetHash: hash.H256(targetHash.String()), + precommit := preCommit{ + TargetHash: targetHash, TargetNumber: targetNumber, } - msg := grandpa.NewMessage[hash.H256, uint64, grandpa.Precommit[hash.H256, uint64]](precommit) + msg := grandpa.NewMessage[hash.H256, uint64, preCommit](precommit) encoded := primitives.NewLocalizedPayload(1, primitives.SetID(currentSetId), msg) signature := voter.Sign(encoded) - signedPreCommit := grandpa.SignedPrecommit[hash.H256, uint64, primitives.AuthoritySignature, primitives.AuthorityID]{ - Precommit: grandpa.Precommit[hash.H256, uint64]{ - TargetHash: hash.H256(targetHash.String()), + signedPreCommit := signedPrecommit{ + Precommit: preCommit{ + TargetHash: targetHash, TargetNumber: targetNumber, }, Signature: signature, @@ -179,21 +182,23 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { precommits = append(precommits, signedPreCommit) } - justification := GrandpaJustification[hash.H256, uint64]{ - Justification: primitives.GrandpaJustification[hash.H256, uint64]{ - Round: 1, - Commit: primitives.Commit[hash.H256, uint64]{ - TargetHash: hash.H256(targetHash.String()), - TargetNumber: targetNumber, - Precommits: precommits, - }, - VoteAncestries: genericHeadersList(t, headers), + justification := primitives.GrandpaJustification[hash.H256, uint64]{ + Round: 1, + Commit: primitives.Commit[hash.H256, uint64]{ + TargetHash: targetHash, + TargetNumber: targetNumber, + Precommits: precommits, }, + VoteAncestries: genericHeadersList(t, headers), } encodedJustification, err := scale.Marshal(justification) require.NoError(t, err) + decodedJustification, err := decodeJustification[hash.H256, uint64, runtime.BlakeTwo256](encodedJustification) + require.NoError(t, err) + require.Equal(t, justification, decodedJustification.Justification) + blockStateMock.EXPECT().GetJustification(header.Hash()).Return(encodedJustification, nil).AnyTimes() blockStateMock.EXPECT().GetHighestFinalisedHeader().Return(header, nil).AnyTimes() @@ -232,7 +237,19 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { result, err := provider.Verify(proof, 0, genesisAuthorities) assert.NoError(t, err) assert.Equal(t, currentSetId, result.SetId) - assert.Equal(t, currentAuthorities, result.AuthorityList) + + expectedAuthorities := primitives.AuthorityList{} + + for _, key := range currentAuthorities { + expectedAuthorities = append(expectedAuthorities, + primitives.AuthorityIDWeight{ + AuthorityID: [32]byte(key.Pair().Public().Bytes()), + AuthorityWeight: 1, + }, + ) + } + + assert.Equal(t, expectedAuthorities, result.AuthorityList) } func TestFindScheduledChange(t *testing.T) { From dc9a6f849367eca54288093eba9745c2db800917 Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 15 Oct 2024 18:24:05 -0300 Subject: [PATCH 11/15] Simplify Verify return type --- .../client/consensus/grandpa/warp_sync.go | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/internal/client/consensus/grandpa/warp_sync.go b/internal/client/consensus/grandpa/warp_sync.go index 3ca77d19f7..15dd286fc6 100644 --- a/internal/client/consensus/grandpa/warp_sync.go +++ b/internal/client/consensus/grandpa/warp_sync.go @@ -278,21 +278,12 @@ func (p *WarpSyncProofProvider) Verify( return nil, fmt.Errorf("verifying warp sync proof: %w", err) } - if proof.IsFinished { - return &network.WarpSyncVerificationResult{ - SetId: nextSetAndAuthorities.SetID, - AuthorityList: nextSetAndAuthorities.AuthorityList, - Header: lastHeader, - Completed: true, - }, nil - } else { - return &network.WarpSyncVerificationResult{ - SetId: nextSetAndAuthorities.SetID, - AuthorityList: nextSetAndAuthorities.AuthorityList, - Header: lastHeader, - Completed: false, - }, nil - } + return &network.WarpSyncVerificationResult{ + SetId: nextSetAndAuthorities.SetID, + AuthorityList: nextSetAndAuthorities.AuthorityList, + Header: lastHeader, + Completed: proof.IsFinished, + }, nil } func findScheduledChange( From 8f4bf0eb09c4511fe4a6927505c0ea834c0f1d95 Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 15 Oct 2024 18:33:40 -0300 Subject: [PATCH 12/15] Fix tests --- dot/network/mock_warp_sync_provider_test.go | 3 +-- dot/network/warp_sync.go | 2 +- internal/client/consensus/grandpa/warp_sync.go | 4 ++++ internal/client/consensus/grandpa/warp_sync_test.go | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/dot/network/mock_warp_sync_provider_test.go b/dot/network/mock_warp_sync_provider_test.go index 38ef2b36b2..04280930e7 100644 --- a/dot/network/mock_warp_sync_provider_test.go +++ b/dot/network/mock_warp_sync_provider_test.go @@ -12,7 +12,6 @@ package network import ( reflect "reflect" - types "github.com/ChainSafe/gossamer/dot/types" grandpa "github.com/ChainSafe/gossamer/internal/primitives/consensus/grandpa" common "github.com/ChainSafe/gossamer/lib/common" gomock "go.uber.org/mock/gomock" @@ -57,7 +56,7 @@ func (mr *MockWarpSyncProviderMockRecorder) Generate(arg0 any) *gomock.Call { } // Verify mocks base method. -func (m *MockWarpSyncProvider) Verify(arg0 []byte, arg1 grandpa.SetID, arg2 types.AuthorityList) (*WarpSyncVerificationResult, error) { +func (m *MockWarpSyncProvider) Verify(arg0 []byte, arg1 grandpa.SetID, arg2 grandpa.AuthorityList) (*WarpSyncVerificationResult, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Verify", arg0, arg1, arg2) ret0, _ := ret[0].(*WarpSyncVerificationResult) diff --git a/dot/network/warp_sync.go b/dot/network/warp_sync.go index 752d23b575..5e5e7b85b5 100644 --- a/dot/network/warp_sync.go +++ b/dot/network/warp_sync.go @@ -33,7 +33,7 @@ type WarpSyncProvider interface { Verify( encodedProof []byte, setId grandpa.SetID, - authorities types.AuthorityList, + authorities primitives.AuthorityList, ) (*WarpSyncVerificationResult, error) } diff --git a/internal/client/consensus/grandpa/warp_sync.go b/internal/client/consensus/grandpa/warp_sync.go index 15dd286fc6..b78f4a3e35 100644 --- a/internal/client/consensus/grandpa/warp_sync.go +++ b/internal/client/consensus/grandpa/warp_sync.go @@ -329,6 +329,10 @@ func grandpaAuthoritiesRawToAuthorities(adr []types.GrandpaAuthoritiesRaw) (prim keyBytes := key.AsBytes() pkey, err := app.NewPublic(keyBytes[:]) + if err != nil { + return nil, err + } + ad[i].AuthorityID = pkey ad[i].AuthorityWeight = primitives.AuthorityWeight(r.ID) } diff --git a/internal/client/consensus/grandpa/warp_sync_test.go b/internal/client/consensus/grandpa/warp_sync_test.go index 26c424d605..3fdf691bbb 100644 --- a/internal/client/consensus/grandpa/warp_sync_test.go +++ b/internal/client/consensus/grandpa/warp_sync_test.go @@ -167,7 +167,7 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { } msg := grandpa.NewMessage[hash.H256, uint64, preCommit](precommit) - encoded := primitives.NewLocalizedPayload(1, primitives.SetID(currentSetId), msg) + encoded := primitives.NewLocalizedPayload(1, currentSetId, msg) signature := voter.Sign(encoded) signedPreCommit := signedPrecommit{ From 99d27e452481ebc47dfbb31788d9556a8f6e0ea5 Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 15 Oct 2024 19:37:25 -0300 Subject: [PATCH 13/15] Fix tests and verify method --- .../client/consensus/grandpa/warp_sync.go | 2 +- .../consensus/grandpa/warp_sync_test.go | 42 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/internal/client/consensus/grandpa/warp_sync.go b/internal/client/consensus/grandpa/warp_sync.go index b78f4a3e35..3a4eda3960 100644 --- a/internal/client/consensus/grandpa/warp_sync.go +++ b/internal/client/consensus/grandpa/warp_sync.go @@ -101,7 +101,7 @@ func (w *WarpSyncProof) verify( currentSetId = fork.SetID currentAuthorities = fork.AuthorityList } else { - err := proof.Justification.Verify(uint64(currentSetId), authorities) + err := proof.Justification.Verify(uint64(currentSetId), currentAuthorities) if err != nil { return nil, err } diff --git a/internal/client/consensus/grandpa/warp_sync_test.go b/internal/client/consensus/grandpa/warp_sync_test.go index 3fdf691bbb..95ee8852ea 100644 --- a/internal/client/consensus/grandpa/warp_sync_test.go +++ b/internal/client/consensus/grandpa/warp_sync_test.go @@ -6,6 +6,7 @@ package grandpa import ( "errors" "math/rand" + "slices" "testing" "github.com/ChainSafe/gossamer/dot/types" @@ -18,7 +19,6 @@ import ( "github.com/ChainSafe/gossamer/lib/common" grandpa "github.com/ChainSafe/gossamer/pkg/finality-grandpa" "github.com/ChainSafe/gossamer/pkg/scale" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) @@ -39,8 +39,8 @@ func TestGenerateWarpSyncProofBlockNotFound(t *testing.T) { // Check errMissingStartBlock returned by provider _, err := provider.Generate(common.EmptyHash) - assert.Error(t, err) - assert.ErrorIs(t, err, errMissingStartBlock) + require.Error(t, err) + require.ErrorIs(t, err, errMissingStartBlock) } func TestGenerateWarpSyncProofBlockNotFinalized(t *testing.T) { @@ -70,8 +70,8 @@ func TestGenerateWarpSyncProofBlockNotFinalized(t *testing.T) { // Check errMissingStartBlock returned by provider _, err := provider.Generate(notFinalizedBlockHeader.Hash()) - assert.Error(t, err) - assert.ErrorIs(t, err, errStartBlockNotFinalized) + require.Error(t, err) + require.ErrorIs(t, err, errStartBlockNotFinalized) } //nolint:lll @@ -108,7 +108,7 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { lastBlockHeader, } - const maxBlocks = 2 + const maxBlocks = 100 for n := uint(1); n <= maxBlocks; n++ { newAuthorities := []ed25519.Keyring{} @@ -116,8 +116,10 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { digest := types.NewDigest() // Authority set change happens every 10 blocks - if n != 0 && n%2 == 0 { - nAuthorities := rand.Intn(len(availableAuthorities)) + if n != 0 && n%10 == 0 { + nAuthorities := rand.Intn(2-1) + 1 + require.GreaterOrEqual(t, nAuthorities, 1) + rand.Shuffle(len(availableAuthorities), func(i, j int) { availableAuthorities[i], availableAuthorities[j] = availableAuthorities[j], availableAuthorities[i] }) @@ -127,7 +129,7 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { nextAuthorities := []types.GrandpaAuthoritiesRaw{} - for _, key := range selectedAuthorities { + for _, key := range newAuthorities { nextAuthorities = append(nextAuthorities, types.GrandpaAuthoritiesRaw{ Key: [32]byte(key.Pair().Public().Bytes()), @@ -202,9 +204,11 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { blockStateMock.EXPECT().GetJustification(header.Hash()).Return(encodedJustification, nil).AnyTimes() blockStateMock.EXPECT().GetHighestFinalisedHeader().Return(header, nil).AnyTimes() - authoritySetChanges = append(authoritySetChanges, n) + authoritySetChanges = append(authoritySetChanges, header.Number) currentSetId++ - currentAuthorities = newAuthorities + + // Update authorities for the new ones + currentAuthorities = slices.Clone(newAuthorities) } } @@ -231,12 +235,12 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { // Generate proof proof, err := provider.Generate(headers[0].Hash()) - assert.NoError(t, err) + require.NoError(t, err) // Verify proof result, err := provider.Verify(proof, 0, genesisAuthorities) - assert.NoError(t, err) - assert.Equal(t, currentSetId, result.SetId) + require.NoError(t, err) + require.Equal(t, currentSetId, result.SetId) expectedAuthorities := primitives.AuthorityList{} @@ -249,7 +253,7 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { ) } - assert.Equal(t, expectedAuthorities, result.AuthorityList) + require.Equal(t, expectedAuthorities, result.AuthorityList) } func TestFindScheduledChange(t *testing.T) { @@ -271,18 +275,18 @@ func TestFindScheduledChange(t *testing.T) { // Find scheduled change in block header scheduledChangeDigest, err := findScheduledChange(*blockHeader) - assert.NoError(t, err) - assert.NotNil(t, scheduledChangeDigest) + require.NoError(t, err) + require.NotNil(t, scheduledChangeDigest) } func createGRANDPAConsensusDigest(t *testing.T, digestData any) types.ConsensusDigest { t.Helper() grandpaConsensusDigest := types.NewGrandpaConsensusDigest() - assert.NoError(t, grandpaConsensusDigest.SetValue(digestData)) + require.NoError(t, grandpaConsensusDigest.SetValue(digestData)) marshaledData, err := scale.Marshal(grandpaConsensusDigest) - assert.NoError(t, err) + require.NoError(t, err) return types.ConsensusDigest{ ConsensusEngineID: types.GrandpaEngineID, From fe97e0a18b90e6dc5ded5f1142bbb1d8e86860be Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 15 Oct 2024 19:39:52 -0300 Subject: [PATCH 14/15] Fix authorities random picker --- internal/client/consensus/grandpa/warp_sync_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/client/consensus/grandpa/warp_sync_test.go b/internal/client/consensus/grandpa/warp_sync_test.go index 95ee8852ea..880d7343b4 100644 --- a/internal/client/consensus/grandpa/warp_sync_test.go +++ b/internal/client/consensus/grandpa/warp_sync_test.go @@ -117,7 +117,7 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { // Authority set change happens every 10 blocks if n != 0 && n%10 == 0 { - nAuthorities := rand.Intn(2-1) + 1 + nAuthorities := rand.Intn(len(availableAuthorities)-1) + 1 require.GreaterOrEqual(t, nAuthorities, 1) rand.Shuffle(len(availableAuthorities), func(i, j int) { From 46728b9b00d6e67aafa88bc2466e002c04c7d2d4 Mon Sep 17 00:00:00 2001 From: Diego Date: Tue, 15 Oct 2024 19:46:43 -0300 Subject: [PATCH 15/15] Refactor tests and add comments --- .../consensus/grandpa/warp_sync_test.go | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/internal/client/consensus/grandpa/warp_sync_test.go b/internal/client/consensus/grandpa/warp_sync_test.go index 880d7343b4..11ad44ee49 100644 --- a/internal/client/consensus/grandpa/warp_sync_test.go +++ b/internal/client/consensus/grandpa/warp_sync_test.go @@ -74,6 +74,9 @@ func TestGenerateWarpSyncProofBlockNotFinalized(t *testing.T) { require.ErrorIs(t, err, errStartBlockNotFinalized) } +// This test generates a small blockchain with authority set changes and expected +// justifications to create a warp sync proof and verify it. +// //nolint:lll func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { t.Parallel() @@ -81,12 +84,14 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { type signedPrecommit = grandpa.SignedPrecommit[hash.H256, uint64, primitives.AuthoritySignature, primitives.AuthorityID] type preCommit = grandpa.Precommit[hash.H256, uint64] + // Initialize mocks ctrl := gomock.NewController(t) defer ctrl.Finish() blockStateMock := NewMockBlockState(ctrl) grandpaStateMock := NewMockGrandpaState(ctrl) + // Set authorities availableAuthorities := ed25519.AvailableAuthorities genesisAuthorities := primitives.AuthorityList{ primitives.AuthorityIDWeight{ @@ -94,29 +99,36 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { AuthorityWeight: 1, }, } - currentAuthorities := []ed25519.Keyring{ed25519.Alice} + + // Set initial values for the scheduled changes currentSetId := primitives.SetID(0) authoritySetChanges := []uint{} - lastBlockHeader := &types.Header{ + // Genesis block + genesis := &types.Header{ ParentHash: common.MustBlake2bHash([]byte("genesis")), Number: 1, } + // All blocks headers headers := []*types.Header{ - lastBlockHeader, + genesis, } const maxBlocks = 100 + // Create blocks with their scheduled changes and justifications for n := uint(1); n <= maxBlocks; n++ { + lastBlockHeader := headers[len(headers)-1] + newAuthorities := []ed25519.Keyring{} digest := types.NewDigest() // Authority set change happens every 10 blocks if n != 0 && n%10 == 0 { + // Pick new random authorities nAuthorities := rand.Intn(len(availableAuthorities)-1) + 1 require.GreaterOrEqual(t, nAuthorities, 1) @@ -124,11 +136,10 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { availableAuthorities[i], availableAuthorities[j] = availableAuthorities[j], availableAuthorities[i] }) - selectedAuthorities := availableAuthorities[:nAuthorities] - newAuthorities = selectedAuthorities + newAuthorities = availableAuthorities[:nAuthorities] + // Map new authorities to GRANDPA raw authorities format nextAuthorities := []types.GrandpaAuthoritiesRaw{} - for _, key := range newAuthorities { nextAuthorities = append(nextAuthorities, types.GrandpaAuthoritiesRaw{ @@ -138,14 +149,15 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { ) } + // Create scheduled change scheduledChange := createGRANDPAConsensusDigest(t, types.GrandpaScheduledChange{ Auths: nextAuthorities, Delay: 0, }) - digest.Add(scheduledChange) } + // Create new block header header := &types.Header{ ParentHash: lastBlockHeader.Hash(), Number: lastBlockHeader.Number + 1, @@ -154,14 +166,13 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { headers = append(headers, header) - lastBlockHeader = header - + // If we have an authority set change, create a justification if len(newAuthorities) > 0 { targetHash := hash.H256(string(header.Hash().ToBytes())) targetNumber := uint64(header.Number) + // Create precommits for current voters precommits := []signedPrecommit{} - for _, voter := range currentAuthorities { precommit := preCommit{ TargetHash: targetHash, @@ -184,6 +195,7 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { precommits = append(precommits, signedPreCommit) } + // Create justification justification := primitives.GrandpaJustification[hash.H256, uint64]{ Round: 1, Commit: primitives.Commit[hash.H256, uint64]{ @@ -197,22 +209,18 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { encodedJustification, err := scale.Marshal(justification) require.NoError(t, err) - decodedJustification, err := decodeJustification[hash.H256, uint64, runtime.BlakeTwo256](encodedJustification) - require.NoError(t, err) - require.Equal(t, justification, decodedJustification.Justification) - blockStateMock.EXPECT().GetJustification(header.Hash()).Return(encodedJustification, nil).AnyTimes() blockStateMock.EXPECT().GetHighestFinalisedHeader().Return(header, nil).AnyTimes() + // Update authorities and set id authoritySetChanges = append(authoritySetChanges, header.Number) - currentSetId++ - - // Update authorities for the new ones currentAuthorities = slices.Clone(newAuthorities) + currentSetId++ } } + // Return expected authority changes for each block authChanges := []uint{} for n := uint(1); n <= maxBlocks; n++ { for _, change := range authoritySetChanges { @@ -223,27 +231,21 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { grandpaStateMock.EXPECT().GetAuthoritiesChangesFromBlock(n).Return(authChanges, nil).AnyTimes() } + // Mock responses for _, header := range headers { blockStateMock.EXPECT().GetHeaderByNumber(header.Number).Return(header, nil).AnyTimes() blockStateMock.EXPECT().GetHeader(header.Hash()).Return(header, nil).AnyTimes() } - provider := &WarpSyncProofProvider{ - blockState: blockStateMock, - grandpaState: grandpaStateMock, - } + // Initialize warp sync provider + provider := NewWarpSyncProofProvider(blockStateMock, grandpaStateMock) // Generate proof proof, err := provider.Generate(headers[0].Hash()) require.NoError(t, err) // Verify proof - result, err := provider.Verify(proof, 0, genesisAuthorities) - require.NoError(t, err) - require.Equal(t, currentSetId, result.SetId) - expectedAuthorities := primitives.AuthorityList{} - for _, key := range currentAuthorities { expectedAuthorities = append(expectedAuthorities, primitives.AuthorityIDWeight{ @@ -253,6 +255,9 @@ func TestGenerateAndVerifyWarpSyncProofOk(t *testing.T) { ) } + result, err := provider.Verify(proof, 0, genesisAuthorities) + require.NoError(t, err) + require.Equal(t, currentSetId, result.SetId) require.Equal(t, expectedAuthorities, result.AuthorityList) }