Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(consensus/grandpa): Add warp sync verification #4240

Draft
wants to merge 8 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -8,18 +8,34 @@ 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"
)

const MaxAllowedSameRequestPerPeer = 5

type WarpSyncVerificationResult struct {
dimartiro marked this conversation as resolved.
Show resolved Hide resolved
SetId grandpa.SetID
AuthorityList grandpa.AuthorityList
Header generic.Header[uint64, hash.H256, runtime.BlakeTwo256]
dimartiro marked this conversation as resolved.
Show resolved Hide resolved
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
Loading