From ea2f146c818d6c9e51dc66624fc3b841f1cd8e0a Mon Sep 17 00:00:00 2001 From: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Date: Mon, 27 May 2024 17:21:35 +0200 Subject: [PATCH 1/9] Improve filtering --- relayer/relays/beefy/ethereum-writer.go | 94 ++++++++++++++--- relayer/relays/beefy/fixture-data-logger.go | 1 - relayer/relays/beefy/parameters.go | 2 +- relayer/relays/beefy/polkadot-listener.go | 111 +++++++++----------- relayer/relays/beefy/scanner.go | 27 +++-- relayer/relays/beefy/task.go | 19 +++- 6 files changed, 162 insertions(+), 92 deletions(-) diff --git a/relayer/relays/beefy/ethereum-writer.go b/relayer/relays/beefy/ethereum-writer.go index f9d88c3572..3fcd7c513a 100644 --- a/relayer/relays/beefy/ethereum-writer.go +++ b/relayer/relays/beefy/ethereum-writer.go @@ -68,7 +68,19 @@ func (wr *EthereumWriter) Start(ctx context.Context, eg *errgroup.Group, request return nil } - err := wr.submit(ctx, task) + filterMode := FilterMode{ + MandatoryCommitmentsOnly: true, + } + + accept, err := wr.filter(ctx, &task, filterMode) + if err != nil { + return fmt.Errorf("filter commitment: %w", err) + } + if !accept { + return nil + } + + err = wr.submit(ctx, task) if err != nil { return fmt.Errorf("submit request: %w", err) } @@ -79,32 +91,89 @@ func (wr *EthereumWriter) Start(ctx context.Context, eg *errgroup.Group, request return nil } -func (wr *EthereumWriter) submit(ctx context.Context, task Request) error { +type FilterMode struct { + MandatoryCommitmentsOnly bool + All bool + DiscardDepth uint64 +} + +type BeefyClientState struct { + LatestBeefyBlock uint64 + CurrentValidatorSetID uint64 + CurrentValidatorSetRoot [32]byte + NextValidatorSetID uint64 + NextValidatorSetRoot [32]byte +} + +func (wr *EthereumWriter) queryBeefyClientState(ctx context.Context) (*BeefyClientState, error) { callOpts := bind.CallOpts{ Context: ctx, } latestBeefyBlock, err := wr.contract.LatestBeefyBlock(&callOpts) if err != nil { - return err - } - if uint32(latestBeefyBlock) >= task.SignedCommitment.Commitment.BlockNumber { - return nil + return nil, err } currentValidatorSet, err := wr.contract.CurrentValidatorSet(&callOpts) if err != nil { - return err + return nil, err } nextValidatorSet, err := wr.contract.NextValidatorSet(&callOpts) if err != nil { - return err + return nil, err } - task.ValidatorsRoot = currentValidatorSet.Root - if task.IsHandover { - task.ValidatorsRoot = nextValidatorSet.Root + + return &BeefyClientState{ + LatestBeefyBlock: latestBeefyBlock, + CurrentValidatorSetID: currentValidatorSet.Id.Uint64(), + CurrentValidatorSetRoot: currentValidatorSet.Root, + NextValidatorSetID: nextValidatorSet.Id.Uint64(), + NextValidatorSetRoot: nextValidatorSet.Root, + }, nil +} + +// filter out commitments that we don't want to commit +func (wr *EthereumWriter) filter(ctx context.Context, task *Request, filterMode FilterMode) (bool, error) { + + state, err := wr.queryBeefyClientState(ctx) + if err != nil { + return false, fmt.Errorf("query beefy client state: %w", err) + } + + commitmentBlockNumber := task.SignedCommitment.Commitment.BlockNumber + commitmentValidatorSetID := task.SignedCommitment.Commitment.ValidatorSetID + + // Filter out commitments which are stale, regardless of filter mode + if commitmentBlockNumber < uint32(state.LatestBeefyBlock) { + return false, nil + } + + // Mark commitment as mandatory if its signed by the next authority set + if commitmentValidatorSetID == state.NextValidatorSetID { + task.IsMandatory = true + task.ValidatorsRoot = state.NextValidatorSetRoot + } else { + task.ValidatorsRoot = state.CurrentValidatorSetRoot + } + + switch { + // Only include mandatory commitments + case filterMode.MandatoryCommitmentsOnly: + if !task.IsMandatory { + return false, nil + } + // Only include mandatory commitments and non-mandatory commitments that are not too old + case filterMode.All: + if !task.IsMandatory && task.Depth > filterMode.DiscardDepth { + return false, nil + } } + return true, nil +} + +func (wr *EthereumWriter) submit(ctx context.Context, task Request) error { // Initial submission tx, initialBitfield, err := wr.doSubmitInitial(ctx, &task) if err != nil { @@ -131,6 +200,8 @@ func (wr *EthereumWriter) submit(ctx context.Context, task Request) error { wr.conn.MakeTxOpts(ctx), *commitmentHash, ) + log.Info("") + _, err = wr.conn.WatchTransaction(ctx, tx, 1) if err != nil { log.WithError(err).Error("Failed to CommitPrevRandao") @@ -153,7 +224,6 @@ func (wr *EthereumWriter) submit(ctx context.Context, task Request) error { log.WithFields(logrus.Fields{ "tx": tx.Hash().Hex(), "blockNumber": task.SignedCommitment.Commitment.BlockNumber, - "IsHandover": task.IsHandover, }).Debug("Transaction SubmitFinal succeeded") return nil diff --git a/relayer/relays/beefy/fixture-data-logger.go b/relayer/relays/beefy/fixture-data-logger.go index 566868fe8c..c713814097 100644 --- a/relayer/relays/beefy/fixture-data-logger.go +++ b/relayer/relays/beefy/fixture-data-logger.go @@ -48,7 +48,6 @@ func (wr *EthereumWriter) makeSubmitFinalLogFields( "leafProofOrder": params.LeafProofOrder, }, "commitmentHash": commitmentHash, - "handover": task.IsHandover, } return fields, nil diff --git a/relayer/relays/beefy/parameters.go b/relayer/relays/beefy/parameters.go index 54a34c2c6a..4a1e6566e0 100644 --- a/relayer/relays/beefy/parameters.go +++ b/relayer/relays/beefy/parameters.go @@ -165,7 +165,7 @@ func (r *Request) MakeSubmitFinalParams(validatorIndices []uint64, initialBitfie proofOrder := new(big.Int) - if r.IsHandover { + if r.IsMandatory { inputLeaf = contracts.BeefyClientMMRLeaf{ Version: uint8(r.Proof.Leaf.Version), ParentNumber: uint32(r.Proof.Leaf.ParentNumberAndHash.ParentNumber), diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index 96cc45fc49..a270d9cdef 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -4,13 +4,12 @@ import ( "context" "fmt" + log "github.com/sirupsen/logrus" "github.com/snowfork/go-substrate-rpc-client/v4/types" "golang.org/x/sync/errgroup" "github.com/snowfork/snowbridge/relayer/chain/relaychain" "github.com/snowfork/snowbridge/relayer/substrate" - - log "github.com/sirupsen/logrus" ) type PolkadotListener struct { @@ -41,7 +40,7 @@ func (li *PolkadotListener) Start( } li.beefyAuthoritiesKey = storageKey - requests := make(chan Request) + requests := make(chan Request, 1) eg.Go(func() error { defer close(requests) @@ -61,12 +60,10 @@ func (li *PolkadotListener) scanCommitments( currentValidatorSet uint64, requests chan<- Request, ) error { - in, err := ScanSafeCommitments(ctx, li.conn.Metadata(), li.conn.API(), currentBeefyBlock+1) + in, err := ScanProvableCommitments(ctx, li.conn.Metadata(), li.conn.API(), currentBeefyBlock+1) if err != nil { - return fmt.Errorf("scan commitments: %w", err) + return fmt.Errorf("scan provable commitments: %w", err) } - lastSyncedBeefyBlock := currentBeefyBlock - for { select { case <-ctx.Done(): @@ -83,67 +80,43 @@ func (li *PolkadotListener) scanCommitments( validatorSetID := result.SignedCommitment.Commitment.ValidatorSetID nextValidatorSetID := uint64(result.MMRProof.Leaf.BeefyNextAuthoritySet.ID) - if validatorSetID != currentValidatorSet && validatorSetID != currentValidatorSet+1 { - return fmt.Errorf("commitment has unexpected validatorSetID: blockNumber=%v validatorSetID=%v expectedValidatorSetID=%v", - committedBeefyBlock, - validatorSetID, - currentValidatorSet, - ) + validators, err := li.queryBeefyAuthorities(result.BlockHash) + if err != nil { + return fmt.Errorf("fetch beefy authorities at block %v: %w", result.BlockHash, err) } - logEntry := log.WithFields(log.Fields{ - "commitment": log.Fields{ - "blockNumber": committedBeefyBlock, - "validatorSetID": validatorSetID, - "nextValidatorSetID": nextValidatorSetID, - }, - "validatorSetID": currentValidatorSet, - "IsHandover": validatorSetID == currentValidatorSet+1, - "lastSyncedBeefyBlock": lastSyncedBeefyBlock, - }) + currentAuthoritySet, err := li.queryBeefyAuthoritySet(result.BlockHash) + if err != nil { + return fmt.Errorf("fetch beefy authority set at block %v: %w", result.BlockHash, err) + } - validators, err := li.queryBeefyAuthorities(result.BlockHash) + nextAuthoritySet, err := li.queryBeefyNextAuthoritySet(result.BlockHash) if err != nil { - return fmt.Errorf("fetch beefy authorities at block %v: %w", result.BlockHash, err) + return fmt.Errorf("fetch beefy next authority set at block %v: %w", result.BlockHash, err) } + task := Request{ - Validators: validators, - SignedCommitment: result.SignedCommitment, - Proof: result.MMRProof, + Validators: validators, + SignedCommitment: result.SignedCommitment, + Proof: result.MMRProof, + CurrentAuthoritySet: currentAuthoritySet, + NextAuthoritySet: nextAuthoritySet, + Depth: result.Depth, } - if validatorSetID == currentValidatorSet+1 && validatorSetID == nextValidatorSetID-1 { - task.IsHandover = true - select { - case <-ctx.Done(): - return ctx.Err() - case requests <- task: - logEntry.Info("New commitment with handover added to channel") - currentValidatorSet++ - lastSyncedBeefyBlock = committedBeefyBlock - } - } else if validatorSetID == currentValidatorSet { - if result.Depth > li.config.FastForwardDepth { - logEntry.Warn("Discarded commitment with depth not fast forward") - continue - } - if committedBeefyBlock < lastSyncedBeefyBlock+li.config.UpdatePeriod { - logEntry.Info("Discarded commitment with sampling") - continue - } - - // drop task if it can't be processed immediately - select { - case <-ctx.Done(): - return ctx.Err() - case requests <- task: - lastSyncedBeefyBlock = committedBeefyBlock - logEntry.Info("New commitment added to channel") - default: - logEntry.Warn("Discarded commitment fail adding to channel") - } - } else { - logEntry.Warn("Discarded invalid commitment") + log.WithFields(log.Fields{ + "commitment": log.Fields{ + "blockNumber": committedBeefyBlock, + "validatorSetID": validatorSetID, + "nextValidatorSetID": nextValidatorSetID, + }, + "validatorSetID": currentValidatorSet, + }).Info("Sending BEEFY commitment to ethereum writer") + + select { + case <-ctx.Done(): + return ctx.Err() + case requests <- task: } } } @@ -162,8 +135,22 @@ func (li *PolkadotListener) queryBeefyAuthorities(blockHash types.Hash) ([]subst return authorities, nil } -func (li *PolkadotListener) queryBeefyNextAuthoritySet(blockHash types.Hash) (types.BeefyNextAuthoritySet, error) { - var nextAuthoritySet types.BeefyNextAuthoritySet +func (li *PolkadotListener) queryBeefyAuthoritySet(blockHash types.Hash) (BeefyAuthoritySet, error) { + var authoritySet BeefyAuthoritySet + storageKey, err := types.CreateStorageKey(li.conn.Metadata(), "MmrLeaf", "BeefyAuthorities", nil, nil) + ok, err := li.conn.API().RPC.State.GetStorage(storageKey, &authoritySet, blockHash) + if err != nil { + return authoritySet, err + } + if !ok { + return authoritySet, fmt.Errorf("beefy authoritySet not found") + } + + return authoritySet, nil +} + +func (li *PolkadotListener) queryBeefyNextAuthoritySet(blockHash types.Hash) (BeefyAuthoritySet, error) { + var nextAuthoritySet BeefyAuthoritySet storageKey, err := types.CreateStorageKey(li.conn.Metadata(), "MmrLeaf", "BeefyNextAuthorities", nil, nil) ok, err := li.conn.API().RPC.State.GetStorage(storageKey, &nextAuthoritySet, blockHash) if err != nil { diff --git a/relayer/relays/beefy/scanner.go b/relayer/relays/beefy/scanner.go index 16cce67a3d..49cfabd5fa 100644 --- a/relayer/relays/beefy/scanner.go +++ b/relayer/relays/beefy/scanner.go @@ -131,7 +131,7 @@ func scanCommitments(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock ui log.WithFields(log.Fields{ "blockNumber": result.BlockNumber, "depth": result.Depth, - }).Info("fetch block") + }).Trace("fetching block") var commitment *types.SignedCommitment for j := range block.Justifications { @@ -166,7 +166,7 @@ func scanCommitments(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock ui } } -type ScanSafeCommitmentsResult struct { +type ScanProvableCommitmentsResult struct { SignedCommitment types.SignedCommitment MMRProof merkle.SimplifiedMMRProof BlockHash types.Hash @@ -174,20 +174,20 @@ type ScanSafeCommitmentsResult struct { Error error } -func ScanSafeCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateAPI, startBlock uint64) (<-chan ScanSafeCommitmentsResult, error) { - out := make(chan ScanSafeCommitmentsResult) - go scanSafeCommitments(ctx, meta, api, startBlock, out) +func ScanProvableCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateAPI, startBlock uint64) (<-chan ScanProvableCommitmentsResult, error) { + out := make(chan ScanProvableCommitmentsResult) + go scanProvableCommitments(ctx, meta, api, startBlock, out) return out, nil } -func scanSafeCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateAPI, startBlock uint64, out chan<- ScanSafeCommitmentsResult) { +func scanProvableCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateAPI, startBlock uint64, out chan<- ScanProvableCommitmentsResult) { defer close(out) sendError := func(err error) { select { case <-ctx.Done(): return - case out <- ScanSafeCommitmentsResult{Error: err}: + case out <- ScanProvableCommitmentsResult{Error: err}: } } @@ -214,13 +214,14 @@ func scanSafeCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.S "blockNumber": result.BlockNumber, "depth": result.Depth, "commitment": result.SignedCommitment.Commitment, - }).Info("fetch commitment") + }).Info("Detected BEEFY commitment in block") blockNumber := result.SignedCommitment.Commitment.BlockNumber blockHash, err := api.RPC.Chain.GetBlockHash(uint64(blockNumber)) if err != nil { sendError(fmt.Errorf("fetch block hash: %w", err)) return + } proofIsValid, proof, err := makeProof(meta, api, blockNumber, blockHash) if err != nil { @@ -229,18 +230,14 @@ func scanSafeCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.S } if !proofIsValid { - log.WithFields(log.Fields{ - "parentNumber": blockNumber, - "beefyBlockHash": blockHash, - "validatorSetID": result.SignedCommitment.Commitment.ValidatorSetID, - }).Info("Proof for leaf is invalid") - continue + sendError(fmt.Errorf("Leaf for parent block %v at hash %v is unprovable", blockNumber, blockHash.Hex())) + return } select { case <-ctx.Done(): return - case out <- ScanSafeCommitmentsResult{result.SignedCommitment, proof, blockHash, result.Depth, nil}: + case out <- ScanProvableCommitmentsResult{result.SignedCommitment, proof, blockHash, result.Depth, nil}: } } diff --git a/relayer/relays/beefy/task.go b/relayer/relays/beefy/task.go index 7bd8733c55..b98f1bab66 100644 --- a/relayer/relays/beefy/task.go +++ b/relayer/relays/beefy/task.go @@ -6,10 +6,27 @@ import ( "github.com/snowfork/snowbridge/relayer/substrate" ) +type BeefyAuthoritySet struct { + // ID + ID types.U64 + // Number of validators in the set. + Len types.U32 + // Merkle Root Hash build from BEEFY uncompressed AuthorityIds. + Root types.H256 +} + type Request struct { + // Validators that signed this commitment Validators []substrate.Authority ValidatorsRoot [32]byte SignedCommitment types.SignedCommitment Proof merkle.SimplifiedMMRProof - IsHandover bool + // Current authority set for the parent BEEFY block + CurrentAuthoritySet BeefyAuthoritySet + // Next authority set for the parent BEEFY block + NextAuthoritySet BeefyAuthoritySet + // Depth of commitment, relative to the chain head + Depth uint64 + // Whether this commitment is mandatory + IsMandatory bool } From 885300541d1444e204a75e7ca94cdca32d3fae7d Mon Sep 17 00:00:00 2001 From: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Date: Thu, 30 May 2024 15:03:41 +0200 Subject: [PATCH 2/9] Improve BEEFY relayer --- relayer/cmd/scan_beefy.go | 7 +- relayer/relays/beefy/ethereum-writer.go | 62 ++------ relayer/relays/beefy/parameters.go | 29 ++-- relayer/relays/beefy/polkadot-listener.go | 24 +-- relayer/relays/beefy/scanner.go | 185 +++++++++++----------- relayer/relays/beefy/task.go | 8 - 6 files changed, 125 insertions(+), 190 deletions(-) diff --git a/relayer/cmd/scan_beefy.go b/relayer/cmd/scan_beefy.go index 6d7cf00070..5c54550739 100644 --- a/relayer/cmd/scan_beefy.go +++ b/relayer/cmd/scan_beefy.go @@ -55,10 +55,9 @@ func ScanBeefyFn(cmd *cobra.Command, _ []string) error { beefyBlock, _ := cmd.Flags().GetUint64("beefy-block") validatorSetID, _ := cmd.Flags().GetUint64("validator-set-id") logrus.WithFields(logrus.Fields{ - "polkadot-url": polkadotUrl, - "fast-forward-depth": fastForwardDepth, - "beefy-block": beefyBlock, - "validator-set-id": validatorSetID, + "polkadot-url": polkadotUrl, + "beefy-block": beefyBlock, + "validator-set-id": validatorSetID, }).Info("Connected to relaychain.") commitments, err := polkadotListener.Start(ctx, eg, beefyBlock, validatorSetID) diff --git a/relayer/relays/beefy/ethereum-writer.go b/relayer/relays/beefy/ethereum-writer.go index 3fcd7c513a..eb3968e126 100644 --- a/relayer/relays/beefy/ethereum-writer.go +++ b/relayer/relays/beefy/ethereum-writer.go @@ -68,18 +68,23 @@ func (wr *EthereumWriter) Start(ctx context.Context, eg *errgroup.Group, request return nil } - filterMode := FilterMode{ - MandatoryCommitmentsOnly: true, - } - - accept, err := wr.filter(ctx, &task, filterMode) + state, err := wr.queryBeefyClientState(ctx) if err != nil { - return fmt.Errorf("filter commitment: %w", err) + return fmt.Errorf("query beefy client state: %w", err) } - if !accept { - return nil + + if task.SignedCommitment.Commitment.BlockNumber < uint32(state.LatestBeefyBlock) { + log.WithFields(logrus.Fields{ + "beefyBlockNumber": task.SignedCommitment.Commitment.BlockNumber, + "latestBeefyBlock": state.LatestBeefyBlock, + }).Info("Commitment already synced") + continue } + // Mandatory commitments are always signed by the next validator set recorded in + // the beefy light client + task.ValidatorsRoot = state.NextValidatorSetRoot + err = wr.submit(ctx, task) if err != nil { return fmt.Errorf("submit request: %w", err) @@ -133,46 +138,6 @@ func (wr *EthereumWriter) queryBeefyClientState(ctx context.Context) (*BeefyClie }, nil } -// filter out commitments that we don't want to commit -func (wr *EthereumWriter) filter(ctx context.Context, task *Request, filterMode FilterMode) (bool, error) { - - state, err := wr.queryBeefyClientState(ctx) - if err != nil { - return false, fmt.Errorf("query beefy client state: %w", err) - } - - commitmentBlockNumber := task.SignedCommitment.Commitment.BlockNumber - commitmentValidatorSetID := task.SignedCommitment.Commitment.ValidatorSetID - - // Filter out commitments which are stale, regardless of filter mode - if commitmentBlockNumber < uint32(state.LatestBeefyBlock) { - return false, nil - } - - // Mark commitment as mandatory if its signed by the next authority set - if commitmentValidatorSetID == state.NextValidatorSetID { - task.IsMandatory = true - task.ValidatorsRoot = state.NextValidatorSetRoot - } else { - task.ValidatorsRoot = state.CurrentValidatorSetRoot - } - - switch { - // Only include mandatory commitments - case filterMode.MandatoryCommitmentsOnly: - if !task.IsMandatory { - return false, nil - } - // Only include mandatory commitments and non-mandatory commitments that are not too old - case filterMode.All: - if !task.IsMandatory && task.Depth > filterMode.DiscardDepth { - return false, nil - } - } - - return true, nil -} - func (wr *EthereumWriter) submit(ctx context.Context, task Request) error { // Initial submission tx, initialBitfield, err := wr.doSubmitInitial(ctx, &task) @@ -200,7 +165,6 @@ func (wr *EthereumWriter) submit(ctx context.Context, task Request) error { wr.conn.MakeTxOpts(ctx), *commitmentHash, ) - log.Info("") _, err = wr.conn.WatchTransaction(ctx, tx, 1) if err != nil { diff --git a/relayer/relays/beefy/parameters.go b/relayer/relays/beefy/parameters.go index 4a1e6566e0..03af798f69 100644 --- a/relayer/relays/beefy/parameters.go +++ b/relayer/relays/beefy/parameters.go @@ -164,22 +164,19 @@ func (r *Request) MakeSubmitFinalParams(validatorIndices []uint64, initialBitfie var merkleProofItems [][32]byte proofOrder := new(big.Int) - - if r.IsMandatory { - inputLeaf = contracts.BeefyClientMMRLeaf{ - Version: uint8(r.Proof.Leaf.Version), - ParentNumber: uint32(r.Proof.Leaf.ParentNumberAndHash.ParentNumber), - ParentHash: r.Proof.Leaf.ParentNumberAndHash.Hash, - ParachainHeadsRoot: r.Proof.Leaf.ParachainHeads, - NextAuthoritySetID: uint64(r.Proof.Leaf.BeefyNextAuthoritySet.ID), - NextAuthoritySetLen: uint32(r.Proof.Leaf.BeefyNextAuthoritySet.Len), - NextAuthoritySetRoot: r.Proof.Leaf.BeefyNextAuthoritySet.Root, - } - for _, mmrProofItem := range r.Proof.MerkleProofItems { - merkleProofItems = append(merkleProofItems, mmrProofItem) - } - proofOrder = proofOrder.SetUint64(r.Proof.MerkleProofOrder) - } + inputLeaf = contracts.BeefyClientMMRLeaf{ + Version: uint8(r.Proof.Leaf.Version), + ParentNumber: uint32(r.Proof.Leaf.ParentNumberAndHash.ParentNumber), + ParentHash: r.Proof.Leaf.ParentNumberAndHash.Hash, + ParachainHeadsRoot: r.Proof.Leaf.ParachainHeads, + NextAuthoritySetID: uint64(r.Proof.Leaf.BeefyNextAuthoritySet.ID), + NextAuthoritySetLen: uint32(r.Proof.Leaf.BeefyNextAuthoritySet.Len), + NextAuthoritySetRoot: r.Proof.Leaf.BeefyNextAuthoritySet.Root, + } + for _, mmrProofItem := range r.Proof.MerkleProofItems { + merkleProofItems = append(merkleProofItems, mmrProofItem) + } + proofOrder = proofOrder.SetUint64(r.Proof.MerkleProofOrder) msg := FinalRequestParams{ Commitment: commitment, diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index a270d9cdef..5aebb9a0bf 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -60,10 +60,11 @@ func (li *PolkadotListener) scanCommitments( currentValidatorSet uint64, requests chan<- Request, ) error { - in, err := ScanProvableCommitments(ctx, li.conn.Metadata(), li.conn.API(), currentBeefyBlock+1) + in, err := ScanCommitments(ctx, li.conn.Metadata(), li.conn.API(), currentBeefyBlock+1) if err != nil { return fmt.Errorf("scan provable commitments: %w", err) } + for { select { case <-ctx.Done(): @@ -78,30 +79,17 @@ func (li *PolkadotListener) scanCommitments( committedBeefyBlock := uint64(result.SignedCommitment.Commitment.BlockNumber) validatorSetID := result.SignedCommitment.Commitment.ValidatorSetID - nextValidatorSetID := uint64(result.MMRProof.Leaf.BeefyNextAuthoritySet.ID) + nextValidatorSetID := uint64(result.Proof.Leaf.BeefyNextAuthoritySet.ID) validators, err := li.queryBeefyAuthorities(result.BlockHash) if err != nil { return fmt.Errorf("fetch beefy authorities at block %v: %w", result.BlockHash, err) } - currentAuthoritySet, err := li.queryBeefyAuthoritySet(result.BlockHash) - if err != nil { - return fmt.Errorf("fetch beefy authority set at block %v: %w", result.BlockHash, err) - } - - nextAuthoritySet, err := li.queryBeefyNextAuthoritySet(result.BlockHash) - if err != nil { - return fmt.Errorf("fetch beefy next authority set at block %v: %w", result.BlockHash, err) - } - task := Request{ - Validators: validators, - SignedCommitment: result.SignedCommitment, - Proof: result.MMRProof, - CurrentAuthoritySet: currentAuthoritySet, - NextAuthoritySet: nextAuthoritySet, - Depth: result.Depth, + Validators: validators, + SignedCommitment: result.SignedCommitment, + Proof: result.Proof, } log.WithFields(log.Fields{ diff --git a/relayer/relays/beefy/scanner.go b/relayer/relays/beefy/scanner.go index 49cfabd5fa..ebaef22c94 100644 --- a/relayer/relays/beefy/scanner.go +++ b/relayer/relays/beefy/scanner.go @@ -15,20 +15,19 @@ import ( type ScanBlocksResult struct { BlockNumber uint64 BlockHash types.Hash - Depth uint64 Error error } -func ScanBlocks(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock uint64) (chan ScanBlocksResult, error) { +func ScanBlocks(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateAPI, startBlock uint64) (chan ScanBlocksResult, error) { results := make(chan ScanBlocksResult) - go scanBlocks(ctx, api, startBlock, results) + go scanBlocks(ctx, meta, api, startBlock, results) return results, nil } -func scanBlocks(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock uint64, out chan<- ScanBlocksResult) { +func scanBlocks(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateAPI, startBlock uint64, out chan<- ScanBlocksResult) { defer close(out) - sendError := func(err error) { + emitError := func(err error) { select { case <-ctx.Done(): return @@ -36,20 +35,49 @@ func scanBlocks(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock uint64, } } - current := startBlock - for { + fetchFinalizedBeefyHeader := func() (*types.Header, error) { finalizedHash, err := api.RPC.Beefy.GetFinalizedHead() if err != nil { - sendError(fmt.Errorf("fetch finalized head: %w", err)) - return + return nil, fmt.Errorf("fetch finalized head: %w", err) } finalizedHeader, err := api.RPC.Chain.GetHeader(finalizedHash) if err != nil { - sendError(fmt.Errorf("fetch header for finalised head %v: %w", finalizedHash.Hex(), err)) - return + return nil, fmt.Errorf("fetch header for finalised head %v: %w", finalizedHash.Hex(), err) } + return finalizedHeader, nil + } + + sessionCurrentIndexKey, err := types.CreateStorageKey(meta, "Session", "CurrentIndex", nil, nil) + if err != nil { + emitError(fmt.Errorf("create storage key: %w", err)) + return + } + + blockHash, err := api.RPC.Chain.GetBlockHash(max(startBlock-1, 0)) + if err != nil { + emitError(fmt.Errorf("fetch block hash: %w", err)) + return + } + + // Get session index of block before start block + var currentSessionIndex uint32 + _, err = api.RPC.State.GetStorage(sessionCurrentIndexKey, ¤tSessionIndex, blockHash) + if err != nil { + emitError(fmt.Errorf("fetch session index: %w", err)) + return + } + + finalizedHeader, err := fetchFinalizedBeefyHeader() + if err != nil { + emitError(err) + return + } + current := startBlock + for { + log.Info("foo") + finalizedBlockNumber := uint64(finalizedHeader.Number) if current > finalizedBlockNumber { select { @@ -57,19 +85,43 @@ func scanBlocks(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock uint64, return case <-time.After(3 * time.Second): } + finalizedHeader, err = fetchFinalizedBeefyHeader() + if err != nil { + emitError(err) + return + } continue } + if current > uint64(finalizedHeader.Number) { + return + } + blockHash, err := api.RPC.Chain.GetBlockHash(current) if err != nil { - sendError(fmt.Errorf("fetch block hash: %w", err)) + emitError(fmt.Errorf("fetch block hash: %w", err)) + return + } + + var sessionIndex uint32 + _, err = api.RPC.State.GetStorage(sessionCurrentIndexKey, &sessionIndex, blockHash) + if err != nil { + emitError(fmt.Errorf("fetch session index: %w", err)) return } + if sessionIndex > currentSessionIndex { + log.Info("BOO") + currentSessionIndex = sessionIndex + } else { + current++ + continue + } + select { case <-ctx.Done(): return - case out <- ScanBlocksResult{BlockNumber: current, BlockHash: blockHash, Depth: finalizedBlockNumber - current}: + case out <- ScanBlocksResult{BlockNumber: current, BlockHash: blockHash}: } current++ @@ -78,22 +130,21 @@ func scanBlocks(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock uint64, type ScanCommitmentsResult struct { SignedCommitment types.SignedCommitment - BlockNumber uint64 + Proof merkle.SimplifiedMMRProof BlockHash types.Hash - Depth uint64 Error error } -func ScanCommitments(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock uint64) (<-chan ScanCommitmentsResult, error) { +func ScanCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateAPI, startBlock uint64) (<-chan ScanCommitmentsResult, error) { out := make(chan ScanCommitmentsResult) - go scanCommitments(ctx, api, startBlock, out) + go scanCommitments(ctx, meta, api, startBlock, out) return out, nil } -func scanCommitments(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock uint64, out chan<- ScanCommitmentsResult) { +func scanCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateAPI, startBlock uint64, out chan<- ScanCommitmentsResult) { defer close(out) - sendError := func(err error) { + emitError := func(err error) { select { case <-ctx.Done(): return @@ -101,17 +152,16 @@ func scanCommitments(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock ui } } - in, err := ScanBlocks(ctx, api, startBlock) + in, err := ScanBlocks(ctx, meta, api, startBlock) if err != nil { - sendError(err) + emitError(err) return } for { select { case <-ctx.Done(): - out <- ScanCommitmentsResult{Error: ctx.Err()} - close(out) + emitError(err) return case result, ok := <-in: if !ok { @@ -119,19 +169,15 @@ func scanCommitments(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock ui } if result.Error != nil { - sendError(result.Error) + emitError(result.Error) return } block, err := api.RPC.Chain.GetBlock(result.BlockHash) if err != nil { - sendError(fmt.Errorf("fetch block: %w", err)) + emitError(fmt.Errorf("fetch block: %w", err)) return } - log.WithFields(log.Fields{ - "blockNumber": result.BlockNumber, - "depth": result.Depth, - }).Trace("fetching block") var commitment *types.SignedCommitment for j := range block.Justifications { @@ -143,7 +189,7 @@ func scanCommitments(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock ui // https://github.com/paritytech/substrate/blob/bcee526a9b73d2df9d5dea0f1a17677618d70b8e/primitives/beefy/src/commitment.rs#L89 err := types.DecodeFromBytes(block.Justifications[j].Payload(), &sc) if err != nil { - sendError(fmt.Errorf("decode BEEFY signed commitment: %w", err)) + emitError(fmt.Errorf("decode signed beefy commitment: %w", err)) return } ok, value := sc.Unwrap() @@ -154,96 +200,45 @@ func scanCommitments(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock ui } if commitment == nil { - continue - } - - select { - case <-ctx.Done(): - return - case out <- ScanCommitmentsResult{BlockNumber: result.BlockNumber, BlockHash: result.BlockHash, SignedCommitment: *commitment, Depth: result.Depth}: - } - } - } -} - -type ScanProvableCommitmentsResult struct { - SignedCommitment types.SignedCommitment - MMRProof merkle.SimplifiedMMRProof - BlockHash types.Hash - Depth uint64 - Error error -} - -func ScanProvableCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateAPI, startBlock uint64) (<-chan ScanProvableCommitmentsResult, error) { - out := make(chan ScanProvableCommitmentsResult) - go scanProvableCommitments(ctx, meta, api, startBlock, out) - return out, nil -} - -func scanProvableCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateAPI, startBlock uint64, out chan<- ScanProvableCommitmentsResult) { - defer close(out) - - sendError := func(err error) { - select { - case <-ctx.Done(): - return - case out <- ScanProvableCommitmentsResult{Error: err}: - } - } - - in, err := ScanCommitments(ctx, api, startBlock) - if err != nil { - sendError(err) - return - } - - for { - select { - case <-ctx.Done(): - return - case result, ok := <-in: - if !ok { - return - } - - if result.Error != nil { - sendError(result.Error) + emitError(fmt.Errorf("expected mandatory beefy justification in block")) return } - log.WithFields(log.Fields{ - "blockNumber": result.BlockNumber, - "depth": result.Depth, - "commitment": result.SignedCommitment.Commitment, - }).Info("Detected BEEFY commitment in block") - blockNumber := result.SignedCommitment.Commitment.BlockNumber + blockNumber := commitment.Commitment.BlockNumber blockHash, err := api.RPC.Chain.GetBlockHash(uint64(blockNumber)) if err != nil { - sendError(fmt.Errorf("fetch block hash: %w", err)) + emitError(fmt.Errorf("fetch block hash: %w", err)) return } proofIsValid, proof, err := makeProof(meta, api, blockNumber, blockHash) if err != nil { - sendError(fmt.Errorf("proof generation for block %v at hash %v: %w", blockNumber, blockHash.Hex(), err)) + emitError(fmt.Errorf("proof generation for block %v at hash %v: %w", blockNumber, blockHash.Hex(), err)) return } if !proofIsValid { - sendError(fmt.Errorf("Leaf for parent block %v at hash %v is unprovable", blockNumber, blockHash.Hex())) + emitError(fmt.Errorf("Leaf for parent block %v at hash %v is unprovable", blockNumber, blockHash.Hex())) return } select { case <-ctx.Done(): return - case out <- ScanProvableCommitmentsResult{result.SignedCommitment, proof, blockHash, result.Depth, nil}: + case out <- ScanCommitmentsResult{BlockHash: blockHash, SignedCommitment: *commitment, Proof: proof}: } - } } } +type ScanProvableCommitmentsResult struct { + SignedCommitment types.SignedCommitment + MMRProof merkle.SimplifiedMMRProof + BlockHash types.Hash + Depth uint64 + Error error +} + func makeProof(meta *types.Metadata, api *gsrpc.SubstrateAPI, blockNumber uint32, blockHash types.Hash) (bool, merkle.SimplifiedMMRProof, error) { proof1, err := api.RPC.MMR.GenerateProof(blockNumber, blockHash) if err != nil { diff --git a/relayer/relays/beefy/task.go b/relayer/relays/beefy/task.go index b98f1bab66..e35862f40f 100644 --- a/relayer/relays/beefy/task.go +++ b/relayer/relays/beefy/task.go @@ -21,12 +21,4 @@ type Request struct { ValidatorsRoot [32]byte SignedCommitment types.SignedCommitment Proof merkle.SimplifiedMMRProof - // Current authority set for the parent BEEFY block - CurrentAuthoritySet BeefyAuthoritySet - // Next authority set for the parent BEEFY block - NextAuthoritySet BeefyAuthoritySet - // Depth of commitment, relative to the chain head - Depth uint64 - // Whether this commitment is mandatory - IsMandatory bool } From fd7db5b31a053137b28cd27435e1037f79f06283 Mon Sep 17 00:00:00 2001 From: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Date: Fri, 31 May 2024 15:36:50 +0200 Subject: [PATCH 3/9] review feedback --- contracts/foundry.toml | 11 +++++++---- contracts/scripts/deploy.sh | 4 ++-- relayer/relays/beefy/ethereum-writer.go | 6 ------ relayer/relays/beefy/scanner.go | 4 ---- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 3483fcd311..863a0dd273 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -5,12 +5,15 @@ optimizer_runs = 20_000 via_ir = true test = 'test' script = 'scripts' -fs_permissions = [{ access = "read-write", path = "test/data"}, { access = "read", path = "./"}] +fs_permissions = [ + { access = "read-write", path = "test/data" }, + { access = "read", path = "./" }, +] ignored_error_codes = [ # DeployLocal.sol is never deployed - 5574 + 5574, ] -# [etherscan] -# mainnet = { key = "${ETHERSCAN_API_KEY}" } +[etherscan] +mainnet = { key = "${ETHERSCAN_API_KEY}" } diff --git a/contracts/scripts/deploy.sh b/contracts/scripts/deploy.sh index 88c75701aa..f6bcb42956 100755 --- a/contracts/scripts/deploy.sh +++ b/contracts/scripts/deploy.sh @@ -2,11 +2,11 @@ set -eux -forge script "scripts/Deploy.sol:${1}" \ +forge script "scripts/DeployBeefyClient.sol:DeployBeefyClient" \ --chain-id 1 \ --rpc-url "${MAINNET_RPC_URL}" \ --ledger \ - --mnemonic-derivation-paths "${MNEMONIC_DERIVATION_PATH}" \ + --mnemonic-derivation-paths "m/44'/60'/1'/0/0" \ --broadcast \ --verify \ --optimize \ diff --git a/relayer/relays/beefy/ethereum-writer.go b/relayer/relays/beefy/ethereum-writer.go index eb3968e126..2efeb9b3b3 100644 --- a/relayer/relays/beefy/ethereum-writer.go +++ b/relayer/relays/beefy/ethereum-writer.go @@ -96,12 +96,6 @@ func (wr *EthereumWriter) Start(ctx context.Context, eg *errgroup.Group, request return nil } -type FilterMode struct { - MandatoryCommitmentsOnly bool - All bool - DiscardDepth uint64 -} - type BeefyClientState struct { LatestBeefyBlock uint64 CurrentValidatorSetID uint64 diff --git a/relayer/relays/beefy/scanner.go b/relayer/relays/beefy/scanner.go index ebaef22c94..efd0f1e0ee 100644 --- a/relayer/relays/beefy/scanner.go +++ b/relayer/relays/beefy/scanner.go @@ -5,7 +5,6 @@ import ( "fmt" "time" - log "github.com/sirupsen/logrus" gsrpc "github.com/snowfork/go-substrate-rpc-client/v4" "github.com/snowfork/go-substrate-rpc-client/v4/types" "github.com/snowfork/snowbridge/relayer/crypto/keccak" @@ -76,8 +75,6 @@ func scanBlocks(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateA } current := startBlock for { - log.Info("foo") - finalizedBlockNumber := uint64(finalizedHeader.Number) if current > finalizedBlockNumber { select { @@ -111,7 +108,6 @@ func scanBlocks(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateA } if sessionIndex > currentSessionIndex { - log.Info("BOO") currentSessionIndex = sessionIndex } else { current++ From 58d83bcf844a23d2391d16eb1888b595b429ae9a Mon Sep 17 00:00:00 2001 From: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Date: Fri, 31 May 2024 15:38:27 +0200 Subject: [PATCH 4/9] review feedback #2 --- relayer/relays/beefy/scanner.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/relayer/relays/beefy/scanner.go b/relayer/relays/beefy/scanner.go index efd0f1e0ee..fa316fd558 100644 --- a/relayer/relays/beefy/scanner.go +++ b/relayer/relays/beefy/scanner.go @@ -227,14 +227,6 @@ func scanCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.Subst } } -type ScanProvableCommitmentsResult struct { - SignedCommitment types.SignedCommitment - MMRProof merkle.SimplifiedMMRProof - BlockHash types.Hash - Depth uint64 - Error error -} - func makeProof(meta *types.Metadata, api *gsrpc.SubstrateAPI, blockNumber uint32, blockHash types.Hash) (bool, merkle.SimplifiedMMRProof, error) { proof1, err := api.RPC.MMR.GenerateProof(blockNumber, blockHash) if err != nil { From 80b4dbd0629c59ddd005c37ccb56518c27be21fa Mon Sep 17 00:00:00 2001 From: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Date: Fri, 31 May 2024 16:06:34 +0200 Subject: [PATCH 5/9] unused code --- relayer/relays/beefy/polkadot-listener.go | 42 ----------------------- 1 file changed, 42 deletions(-) diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index 5aebb9a0bf..88883b7184 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -9,7 +9,6 @@ import ( "golang.org/x/sync/errgroup" "github.com/snowfork/snowbridge/relayer/chain/relaychain" - "github.com/snowfork/snowbridge/relayer/substrate" ) type PolkadotListener struct { @@ -109,44 +108,3 @@ func (li *PolkadotListener) scanCommitments( } } } - -func (li *PolkadotListener) queryBeefyAuthorities(blockHash types.Hash) ([]substrate.Authority, error) { - var authorities []substrate.Authority - ok, err := li.conn.API().RPC.State.GetStorage(li.beefyAuthoritiesKey, &authorities, blockHash) - if err != nil { - return nil, err - } - if !ok { - return nil, fmt.Errorf("beefy authorities not found") - } - - return authorities, nil -} - -func (li *PolkadotListener) queryBeefyAuthoritySet(blockHash types.Hash) (BeefyAuthoritySet, error) { - var authoritySet BeefyAuthoritySet - storageKey, err := types.CreateStorageKey(li.conn.Metadata(), "MmrLeaf", "BeefyAuthorities", nil, nil) - ok, err := li.conn.API().RPC.State.GetStorage(storageKey, &authoritySet, blockHash) - if err != nil { - return authoritySet, err - } - if !ok { - return authoritySet, fmt.Errorf("beefy authoritySet not found") - } - - return authoritySet, nil -} - -func (li *PolkadotListener) queryBeefyNextAuthoritySet(blockHash types.Hash) (BeefyAuthoritySet, error) { - var nextAuthoritySet BeefyAuthoritySet - storageKey, err := types.CreateStorageKey(li.conn.Metadata(), "MmrLeaf", "BeefyNextAuthorities", nil, nil) - ok, err := li.conn.API().RPC.State.GetStorage(storageKey, &nextAuthoritySet, blockHash) - if err != nil { - return nextAuthoritySet, err - } - if !ok { - return nextAuthoritySet, fmt.Errorf("beefy nextAuthoritySet not found") - } - - return nextAuthoritySet, nil -} From fc293296ce443abf616d62e5327163dc0d329d14 Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 3 Jun 2024 11:13:29 +0800 Subject: [PATCH 6/9] Fix ci breaking --- relayer/relays/beefy/polkadot-listener.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index 88883b7184..3ef4675bac 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -9,6 +9,7 @@ import ( "golang.org/x/sync/errgroup" "github.com/snowfork/snowbridge/relayer/chain/relaychain" + "github.com/snowfork/snowbridge/relayer/substrate" ) type PolkadotListener struct { @@ -108,3 +109,16 @@ func (li *PolkadotListener) scanCommitments( } } } + +func (li *PolkadotListener) queryBeefyAuthorities(blockHash types.Hash) ([]substrate.Authority, error) { + var authorities []substrate.Authority + ok, err := li.conn.API().RPC.State.GetStorage(li.beefyAuthoritiesKey, &authorities, blockHash) + if err != nil { + return nil, err + } + if !ok { + return nil, fmt.Errorf("beefy authorities not found") + } + + return authorities, nil +} From 95845a380269bdbe35891b2a11f7e6d214d7f251 Mon Sep 17 00:00:00 2001 From: Ron Date: Mon, 3 Jun 2024 19:31:05 +0800 Subject: [PATCH 7/9] Sync beefy commitment on demand (#1217) * Sync beefy commitment on demand * Minor fix * More comments * More refactoring * Fix for skip mandatory commitment * Some refactoring * Find for next beefy block * Improve log * Remove check unrelated --- relayer/cmd/root.go | 1 + relayer/cmd/sync_beefy_commitment.go | 64 +++++++++++++ relayer/relays/beefy/ethereum-writer.go | 38 ++++---- relayer/relays/beefy/main.go | 95 ++++++++++++++---- relayer/relays/beefy/polkadot-listener.go | 112 ++++++++++++++++++++-- relayer/relays/beefy/scanner.go | 87 ++++++++--------- 6 files changed, 305 insertions(+), 92 deletions(-) create mode 100644 relayer/cmd/sync_beefy_commitment.go diff --git a/relayer/cmd/root.go b/relayer/cmd/root.go index 11f8b794e2..85b18eb658 100644 --- a/relayer/cmd/root.go +++ b/relayer/cmd/root.go @@ -36,6 +36,7 @@ func init() { rootCmd.AddCommand(storeBeaconStateCmd()) rootCmd.AddCommand(importBeaconStateCmd()) rootCmd.AddCommand(listBeaconStateCmd()) + rootCmd.AddCommand(syncBeefyCommitmentCmd()) } func Execute() { diff --git a/relayer/cmd/sync_beefy_commitment.go b/relayer/cmd/sync_beefy_commitment.go new file mode 100644 index 0000000000..444840e33d --- /dev/null +++ b/relayer/cmd/sync_beefy_commitment.go @@ -0,0 +1,64 @@ +package cmd + +import ( + "fmt" + "log" + + "github.com/sirupsen/logrus" + "github.com/snowfork/snowbridge/relayer/chain/ethereum" + "github.com/snowfork/snowbridge/relayer/relays/beefy" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func syncBeefyCommitmentCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "sync-latest-beefy-commitment", + Short: "Sync beefy commitment on demand", + Args: cobra.ExactArgs(0), + RunE: SyncBeefyCommitmentFn, + } + + cmd.Flags().String("config", "/tmp/snowbridge/beefy-relay.json", "Path to configuration file") + cmd.Flags().String("private-key", "", "Ethereum private key") + cmd.Flags().String("private-key-file", "", "The file from which to read the private key") + cmd.Flags().Uint64P("block-number", "b", 0, "Relay block number which contains a Parachain message") + cmd.MarkFlagRequired("block-number") + return cmd +} + +func SyncBeefyCommitmentFn(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + + log.SetOutput(logrus.WithFields(logrus.Fields{"logger": "stdlib"}).WriterLevel(logrus.InfoLevel)) + logrus.SetLevel(logrus.DebugLevel) + + configFile, err := cmd.Flags().GetString("config") + viper.SetConfigFile(configFile) + if err := viper.ReadInConfig(); err != nil { + return err + } + + var config beefy.Config + err = viper.Unmarshal(&config) + if err != nil { + return err + } + privateKey, _ := cmd.Flags().GetString("private-key") + privateKeyFile, _ := cmd.Flags().GetString("private-key-file") + if privateKey == "" && privateKeyFile == "" { + return fmt.Errorf("missing private key") + } + keypair, err := ethereum.ResolvePrivateKey(privateKey, privateKeyFile) + if err != nil { + return err + } + + relay, err := beefy.NewRelay(&config, keypair) + if err != nil { + return err + } + blockNumber, _ := cmd.Flags().GetUint64("block-number") + err = relay.OneShotSync(ctx, blockNumber) + return err +} diff --git a/relayer/relays/beefy/ethereum-writer.go b/relayer/relays/beefy/ethereum-writer.go index 2efeb9b3b3..187c190f97 100644 --- a/relayer/relays/beefy/ethereum-writer.go +++ b/relayer/relays/beefy/ethereum-writer.go @@ -40,23 +40,6 @@ func NewEthereumWriter( } func (wr *EthereumWriter) Start(ctx context.Context, eg *errgroup.Group, requests <-chan Request) error { - address := common.HexToAddress(wr.config.Contracts.BeefyClient) - contract, err := contracts.NewBeefyClient(address, wr.conn.Client()) - if err != nil { - return fmt.Errorf("create beefy client: %w", err) - } - wr.contract = contract - - callOpts := bind.CallOpts{ - Context: ctx, - } - blockWaitPeriod, err := wr.contract.RandaoCommitDelay(&callOpts) - if err != nil { - return fmt.Errorf("create randao commit delay: %w", err) - } - wr.blockWaitPeriod = blockWaitPeriod.Uint64() - log.WithField("randaoCommitDelay", wr.blockWaitPeriod).Trace("Fetched randaoCommitDelay") - // launch task processor eg.Go(func() error { for { @@ -295,3 +278,24 @@ func (wr *EthereumWriter) doSubmitFinal(ctx context.Context, commitmentHash [32] return tx, nil } + +func (wr *EthereumWriter) initialize(ctx context.Context) error { + address := common.HexToAddress(wr.config.Contracts.BeefyClient) + contract, err := contracts.NewBeefyClient(address, wr.conn.Client()) + if err != nil { + return fmt.Errorf("create beefy client: %w", err) + } + wr.contract = contract + + callOpts := bind.CallOpts{ + Context: ctx, + } + blockWaitPeriod, err := wr.contract.RandaoCommitDelay(&callOpts) + if err != nil { + return fmt.Errorf("create randao commit delay: %w", err) + } + wr.blockWaitPeriod = blockWaitPeriod.Uint64() + log.WithField("randaoCommitDelay", wr.blockWaitPeriod).Trace("Fetched randaoCommitDelay") + + return nil +} diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index af7a6b9fac..1222f88a76 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -6,11 +6,8 @@ import ( "golang.org/x/sync/errgroup" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" "github.com/snowfork/snowbridge/relayer/chain/ethereum" "github.com/snowfork/snowbridge/relayer/chain/relaychain" - "github.com/snowfork/snowbridge/relayer/contracts" "github.com/snowfork/snowbridge/relayer/crypto/secp256k1" log "github.com/sirupsen/logrus" @@ -56,49 +53,109 @@ func (relay *Relay) Start(ctx context.Context, eg *errgroup.Group) error { if err != nil { return fmt.Errorf("create ethereum connection: %w", err) } + err = relay.ethereumWriter.initialize(ctx) + if err != nil { + return fmt.Errorf("initialize ethereum writer: %w", err) + } - initialBeefyBlock, initialValidatorSetID, err := relay.getInitialState(ctx) + initialState, err := relay.ethereumWriter.queryBeefyClientState(ctx) if err != nil { return fmt.Errorf("fetch BeefyClient current state: %w", err) } log.WithFields(log.Fields{ - "beefyBlock": initialBeefyBlock, - "validatorSetID": initialValidatorSetID, + "beefyBlock": initialState.LatestBeefyBlock, + "validatorSetID": initialState.CurrentValidatorSetID, }).Info("Retrieved current BeefyClient state") - requests, err := relay.polkadotListener.Start(ctx, eg, initialBeefyBlock, initialValidatorSetID) + requests, err := relay.polkadotListener.Start(ctx, eg, initialState.LatestBeefyBlock, initialState.CurrentValidatorSetID) if err != nil { return fmt.Errorf("initialize polkadot listener: %w", err) } err = relay.ethereumWriter.Start(ctx, eg, requests) if err != nil { - return fmt.Errorf("initialize ethereum writer: %w", err) + return fmt.Errorf("start ethereum writer: %w", err) } return nil } -func (relay *Relay) getInitialState(ctx context.Context) (uint64, uint64, error) { - address := common.HexToAddress(relay.config.Sink.Contracts.BeefyClient) - beefyClient, err := contracts.NewBeefyClient(address, relay.ethereumConn.Client()) +func (relay *Relay) OneShotSync(ctx context.Context, blockNumber uint64) error { + // Initialize relaychainConn + err := relay.relaychainConn.Connect(ctx) if err != nil { - return 0, 0, err + return fmt.Errorf("create relaychain connection: %w", err) } - callOpts := bind.CallOpts{ - Context: ctx, + // Initialize ethereumConn + err = relay.ethereumConn.Connect(ctx) + if err != nil { + return fmt.Errorf("create ethereum connection: %w", err) + } + err = relay.ethereumWriter.initialize(ctx) + if err != nil { + return fmt.Errorf("initialize EthereumWriter: %w", err) } - latestBeefyBlock, err := beefyClient.LatestBeefyBlock(&callOpts) + state, err := relay.ethereumWriter.queryBeefyClientState(ctx) if err != nil { - return 0, 0, err + return fmt.Errorf("query beefy client state: %w", err) + } + // Ignore relay block already synced + if blockNumber <= state.LatestBeefyBlock { + log.WithFields(log.Fields{ + "validatorSetID": state.CurrentValidatorSetID, + "beefyBlock": state.LatestBeefyBlock, + "relayBlock": blockNumber, + }).Info("Relay block already synced, just ignore") + return nil } - currentValidatorSet, err := beefyClient.CurrentValidatorSet(&callOpts) + // generate beefy update for that specific relay block + task, err := relay.polkadotListener.generateBeefyUpdate(ctx, blockNumber) if err != nil { - return 0, 0, err + return fmt.Errorf("fail to generate next beefy request: %w", err) } - return latestBeefyBlock, currentValidatorSet.Id.Uint64(), nil + // Ignore commitment earlier than LatestBeefyBlock which is outdated + if task.SignedCommitment.Commitment.BlockNumber <= uint32(state.LatestBeefyBlock) { + log.WithFields(log.Fields{ + "latestBeefyBlock": state.LatestBeefyBlock, + "currentValidatorSetID": state.CurrentValidatorSetID, + "nextValidatorSetID": state.NextValidatorSetID, + "blockNumberToSync": task.SignedCommitment.Commitment.BlockNumber, + }).Info("Commitment outdated, just ignore") + return nil + } + if task.SignedCommitment.Commitment.ValidatorSetID > state.NextValidatorSetID { + log.WithFields(log.Fields{ + "latestBeefyBlock": state.LatestBeefyBlock, + "currentValidatorSetID": state.CurrentValidatorSetID, + "nextValidatorSetID": state.NextValidatorSetID, + "validatorSetIDToSync": task.SignedCommitment.Commitment.ValidatorSetID, + }).Warn("Task unexpected, wait for mandatory updates to catch up first") + return nil + } + + // Submit the task + if task.SignedCommitment.Commitment.ValidatorSetID == state.CurrentValidatorSetID { + task.ValidatorsRoot = state.CurrentValidatorSetRoot + } else { + task.ValidatorsRoot = state.NextValidatorSetRoot + } + err = relay.ethereumWriter.submit(ctx, task) + if err != nil { + return fmt.Errorf("fail to submit beefy update: %w", err) + } + + updatedState, err := relay.ethereumWriter.queryBeefyClientState(ctx) + if err != nil { + return fmt.Errorf("query beefy client state: %w", err) + } + log.WithFields(log.Fields{ + "latestBeefyBlock": updatedState.LatestBeefyBlock, + "currentValidatorSetID": updatedState.CurrentValidatorSetID, + "nextValidatorSetID": updatedState.NextValidatorSetID, + }).Info("Sync beefy update success") + return nil } diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index 3ef4675bac..9c55caf847 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -3,6 +3,7 @@ package beefy import ( "context" "fmt" + "time" log "github.com/sirupsen/logrus" "github.com/snowfork/go-substrate-rpc-client/v4/types" @@ -13,9 +14,8 @@ import ( ) type PolkadotListener struct { - config *SourceConfig - conn *relaychain.Connection - beefyAuthoritiesKey types.StorageKey + config *SourceConfig + conn *relaychain.Connection } func NewPolkadotListener( @@ -34,12 +34,6 @@ func (li *PolkadotListener) Start( currentBeefyBlock uint64, currentValidatorSetID uint64, ) (<-chan Request, error) { - storageKey, err := types.CreateStorageKey(li.conn.Metadata(), "Beefy", "Authorities", nil, nil) - if err != nil { - return nil, fmt.Errorf("create storage key: %w", err) - } - li.beefyAuthoritiesKey = storageKey - requests := make(chan Request, 1) eg.Go(func() error { @@ -111,8 +105,12 @@ func (li *PolkadotListener) scanCommitments( } func (li *PolkadotListener) queryBeefyAuthorities(blockHash types.Hash) ([]substrate.Authority, error) { + storageKey, err := types.CreateStorageKey(li.conn.Metadata(), "Beefy", "Authorities", nil, nil) + if err != nil { + return nil, fmt.Errorf("create storage key: %w", err) + } var authorities []substrate.Authority - ok, err := li.conn.API().RPC.State.GetStorage(li.beefyAuthoritiesKey, &authorities, blockHash) + ok, err := li.conn.API().RPC.State.GetStorage(storageKey, &authorities, blockHash) if err != nil { return nil, err } @@ -122,3 +120,97 @@ func (li *PolkadotListener) queryBeefyAuthorities(blockHash types.Hash) ([]subst return authorities, nil } + +func (li *PolkadotListener) generateBeefyUpdate(ctx context.Context, relayBlockNumber uint64) (Request, error) { + api := li.conn.API() + meta := li.conn.Metadata() + var request Request + beefyBlockHash, err := li.findNextBeefyBlock(relayBlockNumber) + if err != nil { + return request, fmt.Errorf("find match beefy block: %w", err) + } + + commitment, proof, err := fetchCommitmentAndProof(ctx, meta, api, beefyBlockHash) + if err != nil { + return request, fmt.Errorf("fetch commitment and proof: %w", err) + } + + committedBeefyBlockNumber := uint64(commitment.Commitment.BlockNumber) + committedBeefyBlockHash, err := api.RPC.Chain.GetBlockHash(uint64(committedBeefyBlockNumber)) + + validators, err := li.queryBeefyAuthorities(committedBeefyBlockHash) + if err != nil { + return request, fmt.Errorf("fetch beefy authorities at block %v: %w", committedBeefyBlockHash, err) + } + request = Request{ + Validators: validators, + SignedCommitment: *commitment, + Proof: *proof, + } + + return request, nil +} + +func (li *PolkadotListener) findNextBeefyBlock(blockNumber uint64) (types.Hash, error) { + api := li.conn.API() + var nextBeefyBlockHash, finalizedBeefyBlockHash types.Hash + var err error + nextBeefyBlockNumber := blockNumber + for { + finalizedBeefyBlockHash, err = api.RPC.Beefy.GetFinalizedHead() + if err != nil { + return nextBeefyBlockHash, fmt.Errorf("fetch beefy finalized head: %w", err) + } + finalizedBeefyBlockHeader, err := api.RPC.Chain.GetHeader(finalizedBeefyBlockHash) + if err != nil { + return nextBeefyBlockHash, fmt.Errorf("fetch block header: %w", err) + } + latestBeefyBlockNumber := uint64(finalizedBeefyBlockHeader.Number) + if latestBeefyBlockNumber <= nextBeefyBlockNumber { + // The relay block not finalized yet, just wait and retry + time.Sleep(6 * time.Second) + continue + } else if latestBeefyBlockNumber <= nextBeefyBlockNumber+600 { + // The relay block has been finalized not long ago(1 hour), just return the finalized block + nextBeefyBlockHash = finalizedBeefyBlockHash + break + } else { + // The relay block has been finalized for a long time, in this case return the next block + // which contains a beefy justification + for { + if nextBeefyBlockNumber == latestBeefyBlockNumber { + nextBeefyBlockHash = finalizedBeefyBlockHash + break + } + nextBeefyBlockHash, err = api.RPC.Chain.GetBlockHash(nextBeefyBlockNumber) + if err != nil { + return nextBeefyBlockHash, fmt.Errorf("fetch block hash: %w", err) + } + block, err := api.RPC.Chain.GetBlock(nextBeefyBlockHash) + if err != nil { + return nextBeefyBlockHash, fmt.Errorf("fetch block: %w", err) + } + + var commitment *types.SignedCommitment + for j := range block.Justifications { + sc := types.OptionalSignedCommitment{} + if block.Justifications[j].EngineID() == "BEEF" { + err := types.DecodeFromBytes(block.Justifications[j].Payload(), &sc) + if err != nil { + return nextBeefyBlockHash, fmt.Errorf("decode BEEFY signed commitment: %w", err) + } + ok, value := sc.Unwrap() + if ok { + commitment = &value + } + } + } + if commitment != nil { + return nextBeefyBlockHash, nil + } + nextBeefyBlockNumber++ + } + } + } + return nextBeefyBlockHash, nil +} diff --git a/relayer/relays/beefy/scanner.go b/relayer/relays/beefy/scanner.go index fa316fd558..6c84cc06bb 100644 --- a/relayer/relays/beefy/scanner.go +++ b/relayer/relays/beefy/scanner.go @@ -169,59 +169,16 @@ func scanCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.Subst return } - block, err := api.RPC.Chain.GetBlock(result.BlockHash) + commitment, proof, err := fetchCommitmentAndProof(ctx, meta, api, result.BlockHash) if err != nil { - emitError(fmt.Errorf("fetch block: %w", err)) - return - } - - var commitment *types.SignedCommitment - for j := range block.Justifications { - sc := types.OptionalSignedCommitment{} - // Filter justification by EngineID - // https://github.com/paritytech/substrate/blob/55c64bcc2af5a6e5fc3eb245e638379ebe18a58d/primitives/beefy/src/lib.rs#L114 - if block.Justifications[j].EngineID() == "BEEF" { - // Decode as SignedCommitment - // https://github.com/paritytech/substrate/blob/bcee526a9b73d2df9d5dea0f1a17677618d70b8e/primitives/beefy/src/commitment.rs#L89 - err := types.DecodeFromBytes(block.Justifications[j].Payload(), &sc) - if err != nil { - emitError(fmt.Errorf("decode signed beefy commitment: %w", err)) - return - } - ok, value := sc.Unwrap() - if ok { - commitment = &value - } - } - } - - if commitment == nil { - emitError(fmt.Errorf("expected mandatory beefy justification in block")) - return - } - - blockNumber := commitment.Commitment.BlockNumber - blockHash, err := api.RPC.Chain.GetBlockHash(uint64(blockNumber)) - if err != nil { - emitError(fmt.Errorf("fetch block hash: %w", err)) - return - - } - proofIsValid, proof, err := makeProof(meta, api, blockNumber, blockHash) - if err != nil { - emitError(fmt.Errorf("proof generation for block %v at hash %v: %w", blockNumber, blockHash.Hex(), err)) - return - } - - if !proofIsValid { - emitError(fmt.Errorf("Leaf for parent block %v at hash %v is unprovable", blockNumber, blockHash.Hex())) + emitError(fmt.Errorf("fetch commitment and proof: %w", err)) return } select { case <-ctx.Done(): return - case out <- ScanCommitmentsResult{BlockHash: blockHash, SignedCommitment: *commitment, Proof: proof}: + case out <- ScanCommitmentsResult{BlockHash: result.BlockHash, SignedCommitment: *commitment, Proof: *proof}: } } } @@ -282,3 +239,41 @@ func verifyProof(meta *types.Metadata, api *gsrpc.SubstrateAPI, proof merkle.Sim return actualRoot == expectedRoot, nil } + +func fetchCommitmentAndProof(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateAPI, beefyBlockHash types.Hash) (*types.SignedCommitment, *merkle.SimplifiedMMRProof, error) { + beefyHeader, err := api.RPC.Chain.GetHeader(beefyBlockHash) + if err != nil { + return nil, nil, fmt.Errorf("fetch header: %w", err) + } + beefyBlock, err := api.RPC.Chain.GetBlock(beefyBlockHash) + if err != nil { + return nil, nil, fmt.Errorf("fetch block: %w", err) + } + + var commitment *types.SignedCommitment + for j := range beefyBlock.Justifications { + sc := types.OptionalSignedCommitment{} + if beefyBlock.Justifications[j].EngineID() == "BEEF" { + err := types.DecodeFromBytes(beefyBlock.Justifications[j].Payload(), &sc) + if err != nil { + return nil, nil, fmt.Errorf("decode BEEFY signed commitment: %w", err) + } + ok, value := sc.Unwrap() + if ok { + commitment = &value + } + } + } + if commitment == nil { + return nil, nil, fmt.Errorf("beefy block without a valid commitment") + } + + proofIsValid, proof, err := makeProof(meta, api, uint32(beefyHeader.Number), beefyBlockHash) + if err != nil { + return nil, nil, fmt.Errorf("proof generation for block %v at hash %v: %w", beefyHeader.Number, beefyBlockHash.Hex(), err) + } + if !proofIsValid { + return nil, nil, fmt.Errorf("Proof for leaf is invalid for block %v at hash %v: %w", beefyHeader.Number, beefyBlockHash.Hex(), err) + } + return commitment, &proof, nil +} From 6d26d047d7964b1b9570bcf1f329b7d96b63e82a Mon Sep 17 00:00:00 2001 From: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:12:39 +0200 Subject: [PATCH 8/9] unused context parameter --- relayer/relays/beefy/main.go | 2 +- relayer/relays/beefy/polkadot-listener.go | 4 ++-- relayer/relays/beefy/scanner.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index 1222f88a76..209e33e5d8 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -112,7 +112,7 @@ func (relay *Relay) OneShotSync(ctx context.Context, blockNumber uint64) error { } // generate beefy update for that specific relay block - task, err := relay.polkadotListener.generateBeefyUpdate(ctx, blockNumber) + task, err := relay.polkadotListener.generateBeefyUpdate(blockNumber) if err != nil { return fmt.Errorf("fail to generate next beefy request: %w", err) } diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index 9c55caf847..24894af1e8 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -121,7 +121,7 @@ func (li *PolkadotListener) queryBeefyAuthorities(blockHash types.Hash) ([]subst return authorities, nil } -func (li *PolkadotListener) generateBeefyUpdate(ctx context.Context, relayBlockNumber uint64) (Request, error) { +func (li *PolkadotListener) generateBeefyUpdate(relayBlockNumber uint64) (Request, error) { api := li.conn.API() meta := li.conn.Metadata() var request Request @@ -130,7 +130,7 @@ func (li *PolkadotListener) generateBeefyUpdate(ctx context.Context, relayBlockN return request, fmt.Errorf("find match beefy block: %w", err) } - commitment, proof, err := fetchCommitmentAndProof(ctx, meta, api, beefyBlockHash) + commitment, proof, err := fetchCommitmentAndProof(meta, api, beefyBlockHash) if err != nil { return request, fmt.Errorf("fetch commitment and proof: %w", err) } diff --git a/relayer/relays/beefy/scanner.go b/relayer/relays/beefy/scanner.go index 6c84cc06bb..d6f08352f5 100644 --- a/relayer/relays/beefy/scanner.go +++ b/relayer/relays/beefy/scanner.go @@ -169,7 +169,7 @@ func scanCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.Subst return } - commitment, proof, err := fetchCommitmentAndProof(ctx, meta, api, result.BlockHash) + commitment, proof, err := fetchCommitmentAndProof(meta, api, result.BlockHash) if err != nil { emitError(fmt.Errorf("fetch commitment and proof: %w", err)) return @@ -240,7 +240,7 @@ func verifyProof(meta *types.Metadata, api *gsrpc.SubstrateAPI, proof merkle.Sim return actualRoot == expectedRoot, nil } -func fetchCommitmentAndProof(ctx context.Context, meta *types.Metadata, api *gsrpc.SubstrateAPI, beefyBlockHash types.Hash) (*types.SignedCommitment, *merkle.SimplifiedMMRProof, error) { +func fetchCommitmentAndProof(meta *types.Metadata, api *gsrpc.SubstrateAPI, beefyBlockHash types.Hash) (*types.SignedCommitment, *merkle.SimplifiedMMRProof, error) { beefyHeader, err := api.RPC.Chain.GetHeader(beefyBlockHash) if err != nil { return nil, nil, fmt.Errorf("fetch header: %w", err) From 03a63f74e52da0ca8929a3c0e3db69489a315758 Mon Sep 17 00:00:00 2001 From: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:00:27 +0200 Subject: [PATCH 9/9] Update deploy.sh --- contracts/scripts/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/scripts/deploy.sh b/contracts/scripts/deploy.sh index f6bcb42956..8f35b74644 100755 --- a/contracts/scripts/deploy.sh +++ b/contracts/scripts/deploy.sh @@ -6,7 +6,7 @@ forge script "scripts/DeployBeefyClient.sol:DeployBeefyClient" \ --chain-id 1 \ --rpc-url "${MAINNET_RPC_URL}" \ --ledger \ - --mnemonic-derivation-paths "m/44'/60'/1'/0/0" \ + --mnemonic-derivation-paths "${MNEMONIC_DERIVATION_PATH}" \ --broadcast \ --verify \ --optimize \