Skip to content

Commit

Permalink
feat(consensus/grandpa): Add warp sync verification
Browse files Browse the repository at this point in the history
  • Loading branch information
dimartiro committed Oct 9, 2024
1 parent 6e7a351 commit a905fca
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 12 deletions.
16 changes: 16 additions & 0 deletions dot/network/mock_warp_sync_provider_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions dot/network/warp_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,32 @@ import (
"errors"

"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"
)

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) {
Expand Down
133 changes: 122 additions & 11 deletions internal/client/consensus/grandpa/warp_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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]
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
Expand All @@ -107,20 +155,20 @@ 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
}

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
}
Expand All @@ -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)
Expand All @@ -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
}
Expand All @@ -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
Expand All @@ -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,
)
}
2 changes: 1 addition & 1 deletion internal/client/consensus/grandpa/warp_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
5 changes: 5 additions & 0 deletions internal/primitives/consensus/grandpa/grandpa.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit a905fca

Please sign in to comment.