From a686dfabbf827386c44f9c693e4bce491604fbf3 Mon Sep 17 00:00:00 2001 From: Robert Hambrock Date: Wed, 31 Jan 2024 06:40:17 +0100 Subject: [PATCH 01/31] Permit non-consecutive increases in validator set Changes the validator set successions to still be sequential, but non-consecutive. For bridge security against collusion by validators, it's important that these validators are still bonded, but the timing of the signature itself is secondary. As such, even if some validators have rotated out, this change permits keeping the bridge alive so long as 2/3rds of the `currentValidatorSet` are still bonded and sign the commitment. Co-authored-by: bhargavbh <2bhargav5@gmail.com> Co-authored-by: Alistair Stewart --- contracts/src/BeefyClient.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/BeefyClient.sol b/contracts/src/BeefyClient.sol index eeb27bfc42..072578af79 100644 --- a/contracts/src/BeefyClient.sol +++ b/contracts/src/BeefyClient.sol @@ -345,7 +345,7 @@ contract BeefyClient { bool is_next_session = false; ValidatorSetState storage vset; - if (commitment.validatorSetID == nextValidatorSet.id) { + if (commitment.validatorSetID > currentValidatorSet.id) { is_next_session = true; vset = nextValidatorSet; } else if (commitment.validatorSetID == currentValidatorSet.id) { @@ -359,7 +359,7 @@ contract BeefyClient { bytes32 newMMRRoot = ensureProvidesMMRRoot(commitment); if (is_next_session) { - if (leaf.nextAuthoritySetID != nextValidatorSet.id + 1) { + if (leaf.nextAuthoritySetID <= nextValidatorSet.id) { revert InvalidMMRLeaf(); } bool leafIsValid = From a223f97a2c21f14a83aa9786644f9e81f94c1fce Mon Sep 17 00:00:00 2001 From: Robert Hambrock Date: Wed, 31 Jan 2024 17:01:32 +0100 Subject: [PATCH 02/31] adjust submitInitial too Co-authored-by: bhargavbh <2bhargav5@gmail.com> --- contracts/src/BeefyClient.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/BeefyClient.sol b/contracts/src/BeefyClient.sol index 072578af79..1ed214d7f9 100644 --- a/contracts/src/BeefyClient.sol +++ b/contracts/src/BeefyClient.sol @@ -253,7 +253,7 @@ contract BeefyClient { signatureUsageCount = currentValidatorSet.usageCounters.get(proof.index); currentValidatorSet.usageCounters.set(proof.index, signatureUsageCount.saturatingAdd(1)); vset = currentValidatorSet; - } else if (commitment.validatorSetID == nextValidatorSet.id) { + } else if (commitment.validatorSetID >= nextValidatorSet.id) { signatureUsageCount = nextValidatorSet.usageCounters.get(proof.index); nextValidatorSet.usageCounters.set(proof.index, signatureUsageCount.saturatingAdd(1)); vset = nextValidatorSet; 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 03/31] 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 ebb57941b9e373b94e0e7e44a296381078699143 Mon Sep 17 00:00:00 2001 From: ron Date: Sun, 4 Feb 2024 17:49:56 +0800 Subject: [PATCH 04/31] Skip mandatory commitment --- relayer/cmd/scan_beefy.go | 6 ++- relayer/relays/beefy/main.go | 31 ++++++++++---- relayer/relays/beefy/parameters.go | 8 ++++ relayer/relays/beefy/polkadot-listener.go | 52 ++++++++++++++--------- 4 files changed, 68 insertions(+), 29 deletions(-) diff --git a/relayer/cmd/scan_beefy.go b/relayer/cmd/scan_beefy.go index 6d7cf00070..747bfbf9a6 100644 --- a/relayer/cmd/scan_beefy.go +++ b/relayer/cmd/scan_beefy.go @@ -61,7 +61,11 @@ func ScanBeefyFn(cmd *cobra.Command, _ []string) error { "validator-set-id": validatorSetID, }).Info("Connected to relaychain.") - commitments, err := polkadotListener.Start(ctx, eg, beefyBlock, validatorSetID) + var currentState beefy.BeefyState + currentState.CurrentValidatorSetId = validatorSetID + currentState.LatestBeefyBlock = beefyBlock + + commitments, err := polkadotListener.Start(ctx, eg, currentState) if err != nil { logrus.WithError(err).Fatalf("could not start") } diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index af7a6b9fac..b0c7590680 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -57,16 +57,15 @@ func (relay *Relay) Start(ctx context.Context, eg *errgroup.Group) error { return fmt.Errorf("create ethereum connection: %w", err) } - initialBeefyBlock, initialValidatorSetID, err := relay.getInitialState(ctx) + currentState, err := relay.getCurrentState(ctx) if err != nil { return fmt.Errorf("fetch BeefyClient current state: %w", err) } log.WithFields(log.Fields{ - "beefyBlock": initialBeefyBlock, - "validatorSetID": initialValidatorSetID, + "currentState": currentState, }).Info("Retrieved current BeefyClient state") - requests, err := relay.polkadotListener.Start(ctx, eg, initialBeefyBlock, initialValidatorSetID) + requests, err := relay.polkadotListener.Start(ctx, eg, currentState) if err != nil { return fmt.Errorf("initialize polkadot listener: %w", err) } @@ -79,11 +78,12 @@ func (relay *Relay) Start(ctx context.Context, eg *errgroup.Group) error { return nil } -func (relay *Relay) getInitialState(ctx context.Context) (uint64, uint64, error) { +func (relay *Relay) getCurrentState(ctx context.Context) (BeefyState, error) { + var currentState BeefyState address := common.HexToAddress(relay.config.Sink.Contracts.BeefyClient) beefyClient, err := contracts.NewBeefyClient(address, relay.ethereumConn.Client()) if err != nil { - return 0, 0, err + return currentState, err } callOpts := bind.CallOpts{ @@ -92,13 +92,26 @@ func (relay *Relay) getInitialState(ctx context.Context) (uint64, uint64, error) latestBeefyBlock, err := beefyClient.LatestBeefyBlock(&callOpts) if err != nil { - return 0, 0, err + return currentState, err } currentValidatorSet, err := beefyClient.CurrentValidatorSet(&callOpts) if err != nil { - return 0, 0, err + return currentState, err } - return latestBeefyBlock, currentValidatorSet.Id.Uint64(), nil + nextValidatorSet, err := beefyClient.NextValidatorSet(&callOpts) + if err != nil { + return currentState, err + } + + currentState = BeefyState{ + LatestBeefyBlock: latestBeefyBlock, + CurrentValidatorSetId: currentValidatorSet.Id.Uint64(), + CurrentValidatorSetRoot: currentValidatorSet.Root, + NextValidatorSetId: nextValidatorSet.Id.Uint64(), + NextValidatorSetRoot: nextValidatorSet.Root, + } + + return currentState, nil } diff --git a/relayer/relays/beefy/parameters.go b/relayer/relays/beefy/parameters.go index 54a34c2c6a..a8f3fd8503 100644 --- a/relayer/relays/beefy/parameters.go +++ b/relayer/relays/beefy/parameters.go @@ -245,3 +245,11 @@ func proofToLog(proof contracts.BeefyClientValidatorProof) logrus.Fields { "Proof": hexProof, } } + +type BeefyState struct { + LatestBeefyBlock uint64 + CurrentValidatorSetId uint64 + CurrentValidatorSetRoot [32]byte + NextValidatorSetId uint64 + NextValidatorSetRoot [32]byte +} diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index 96cc45fc49..323b694df1 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -32,8 +32,7 @@ func NewPolkadotListener( func (li *PolkadotListener) Start( ctx context.Context, eg *errgroup.Group, - currentBeefyBlock uint64, - currentValidatorSetID uint64, + currentState BeefyState, ) (<-chan Request, error) { storageKey, err := types.CreateStorageKey(li.conn.Metadata(), "Beefy", "Authorities", nil, nil) if err != nil { @@ -45,7 +44,7 @@ func (li *PolkadotListener) Start( eg.Go(func() error { defer close(requests) - err := li.scanCommitments(ctx, currentBeefyBlock, currentValidatorSetID, requests) + err := li.scanCommitments(ctx, currentState, requests) if err != nil { return err } @@ -57,16 +56,15 @@ func (li *PolkadotListener) Start( func (li *PolkadotListener) scanCommitments( ctx context.Context, - currentBeefyBlock uint64, - currentValidatorSet uint64, + currentState BeefyState, requests chan<- Request, ) error { - in, err := ScanSafeCommitments(ctx, li.conn.Metadata(), li.conn.API(), currentBeefyBlock+1) + lastSyncedBeefyBlock := currentState.LatestBeefyBlock + currentValidatorSet := currentState.CurrentValidatorSetId + in, err := ScanSafeCommitments(ctx, li.conn.Metadata(), li.conn.API(), lastSyncedBeefyBlock+1) if err != nil { return fmt.Errorf("scan commitments: %w", err) } - lastSyncedBeefyBlock := currentBeefyBlock - for { select { case <-ctx.Done(): @@ -83,14 +81,6 @@ 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, - ) - } - logEntry := log.WithFields(log.Fields{ "commitment": log.Fields{ "blockNumber": committedBeefyBlock, @@ -98,7 +88,7 @@ func (li *PolkadotListener) scanCommitments( "nextValidatorSetID": nextValidatorSetID, }, "validatorSetID": currentValidatorSet, - "IsHandover": validatorSetID == currentValidatorSet+1, + "IsHandover": validatorSetID > currentValidatorSet, "lastSyncedBeefyBlock": lastSyncedBeefyBlock, }) @@ -106,20 +96,28 @@ func (li *PolkadotListener) scanCommitments( 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 authoritie set at block %v: %w", result.BlockHash, err) + } task := Request{ Validators: validators, SignedCommitment: result.SignedCommitment, Proof: result.MMRProof, } - if validatorSetID == currentValidatorSet+1 && validatorSetID == nextValidatorSetID-1 { + if validatorSetID > currentValidatorSet { + if currentAuthoritySet.Root == currentState.NextValidatorSetRoot && committedBeefyBlock < lastSyncedBeefyBlock+li.config.UpdatePeriod { + logEntry.Info("Discarded commitment with beefy authorities not change") + continue + } task.IsHandover = true select { case <-ctx.Done(): return ctx.Err() case requests <- task: logEntry.Info("New commitment with handover added to channel") - currentValidatorSet++ + currentValidatorSet = validatorSetID lastSyncedBeefyBlock = committedBeefyBlock } } else if validatorSetID == currentValidatorSet { @@ -175,3 +173,19 @@ func (li *PolkadotListener) queryBeefyNextAuthoritySet(blockHash types.Hash) (ty return nextAuthoritySet, nil } + +type BeefyAuthoritySet = 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 +} From 5eba01b6ac0e8adf61dedcc3d57d12672760c279 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 29 May 2024 00:51:52 +0800 Subject: [PATCH 05/31] Fix updating with current beefy state --- relayer/relays/beefy/main.go | 10 ++++++---- relayer/relays/beefy/polkadot-listener.go | 16 +++++++++++----- relayer/relays/beefy/scanner.go | 4 ---- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index b0c7590680..92aaa48710 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -37,13 +37,15 @@ func NewRelay(config *Config, ethereumKeypair *secp256k1.Keypair) (*Relay, error log.Info("Beefy relay created") - return &Relay{ + relayer := Relay{ config: config, relaychainConn: relaychainConn, ethereumConn: ethereumConn, polkadotListener: polkadotListener, ethereumWriter: ethereumWriter, - }, nil + } + polkadotListener.relayer = &relayer + return &relayer, nil } func (relay *Relay) Start(ctx context.Context, eg *errgroup.Group) error { @@ -57,7 +59,7 @@ func (relay *Relay) Start(ctx context.Context, eg *errgroup.Group) error { return fmt.Errorf("create ethereum connection: %w", err) } - currentState, err := relay.getCurrentState(ctx) + currentState, err := relay.CurrentState(ctx) if err != nil { return fmt.Errorf("fetch BeefyClient current state: %w", err) } @@ -78,7 +80,7 @@ func (relay *Relay) Start(ctx context.Context, eg *errgroup.Group) error { return nil } -func (relay *Relay) getCurrentState(ctx context.Context) (BeefyState, error) { +func (relay *Relay) CurrentState(ctx context.Context) (BeefyState, error) { var currentState BeefyState address := common.HexToAddress(relay.config.Sink.Contracts.BeefyClient) beefyClient, err := contracts.NewBeefyClient(address, relay.ethereumConn.Client()) diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index 323b694df1..da2c491e28 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -17,6 +17,7 @@ type PolkadotListener struct { config *SourceConfig conn *relaychain.Connection beefyAuthoritiesKey types.StorageKey + relayer *Relay } func NewPolkadotListener( @@ -96,9 +97,9 @@ func (li *PolkadotListener) scanCommitments( if err != nil { return fmt.Errorf("fetch beefy authorities at block %v: %w", result.BlockHash, err) } - currentAuthoritySet, err := li.queryBeefyAuthoritySet(result.BlockHash) + nextAuthoritySet, err := li.queryBeefyNextAuthoritySet(result.BlockHash) if err != nil { - return fmt.Errorf("fetch beefy authoritie set at block %v: %w", result.BlockHash, err) + return fmt.Errorf("fetch beefy authorities set at block %v: %w", result.BlockHash, err) } task := Request{ Validators: validators, @@ -106,8 +107,13 @@ func (li *PolkadotListener) scanCommitments( Proof: result.MMRProof, } + currentState, err = li.relayer.CurrentState(ctx) + if err != nil { + return fmt.Errorf("get current state from beefy LC %w", err) + } + if validatorSetID > currentValidatorSet { - if currentAuthoritySet.Root == currentState.NextValidatorSetRoot && committedBeefyBlock < lastSyncedBeefyBlock+li.config.UpdatePeriod { + if nextAuthoritySet.Root == currentState.NextValidatorSetRoot && committedBeefyBlock < lastSyncedBeefyBlock+li.config.UpdatePeriod { logEntry.Info("Discarded commitment with beefy authorities not change") continue } @@ -162,7 +168,7 @@ func (li *PolkadotListener) queryBeefyAuthorities(blockHash types.Hash) ([]subst func (li *PolkadotListener) queryBeefyNextAuthoritySet(blockHash types.Hash) (types.BeefyNextAuthoritySet, error) { var nextAuthoritySet types.BeefyNextAuthoritySet - storageKey, err := types.CreateStorageKey(li.conn.Metadata(), "MmrLeaf", "BeefyNextAuthorities", nil, nil) + storageKey, err := types.CreateStorageKey(li.conn.Metadata(), "BeefyMmrLeaf", "BeefyNextAuthorities", nil, nil) ok, err := li.conn.API().RPC.State.GetStorage(storageKey, &nextAuthoritySet, blockHash) if err != nil { return nextAuthoritySet, err @@ -178,7 +184,7 @@ type BeefyAuthoritySet = 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) + storageKey, err := types.CreateStorageKey(li.conn.Metadata(), "BeefyMmrLeaf", "BeefyAuthorities", nil, nil) ok, err := li.conn.API().RPC.State.GetStorage(storageKey, &authoritySet, blockHash) if err != nil { return authoritySet, err diff --git a/relayer/relays/beefy/scanner.go b/relayer/relays/beefy/scanner.go index 16cce67a3d..12a42ebfd3 100644 --- a/relayer/relays/beefy/scanner.go +++ b/relayer/relays/beefy/scanner.go @@ -128,10 +128,6 @@ func scanCommitments(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock ui sendError(fmt.Errorf("fetch block: %w", err)) return } - log.WithFields(log.Fields{ - "blockNumber": result.BlockNumber, - "depth": result.Depth, - }).Info("fetch block") var commitment *types.SignedCommitment for j := range block.Justifications { From 482e8a20498f5ffba4481623468a7f5dc00fc57f Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 29 May 2024 02:00:04 +0800 Subject: [PATCH 06/31] Ignore submit when authorities not change --- relayer/relays/beefy/ethereum-writer.go | 13 ++++++++++++- relayer/relays/beefy/polkadot-listener.go | 1 + relayer/relays/beefy/task.go | 11 ++++++----- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/relayer/relays/beefy/ethereum-writer.go b/relayer/relays/beefy/ethereum-writer.go index f9d88c3572..4d953271bd 100644 --- a/relayer/relays/beefy/ethereum-writer.go +++ b/relayer/relays/beefy/ethereum-writer.go @@ -89,9 +89,13 @@ func (wr *EthereumWriter) submit(ctx context.Context, task Request) error { return err } if uint32(latestBeefyBlock) >= task.SignedCommitment.Commitment.BlockNumber { + log.WithFields(logrus.Fields{ + "blockNumber": task.SignedCommitment.Commitment.BlockNumber, + "IsHandover": task.IsHandover, + "ValidatorSetID": task.SignedCommitment.Commitment.ValidatorSetID, + }).Warn("Ignore outdated commitment") return nil } - currentValidatorSet, err := wr.contract.CurrentValidatorSet(&callOpts) if err != nil { return err @@ -103,6 +107,13 @@ func (wr *EthereumWriter) submit(ctx context.Context, task Request) error { task.ValidatorsRoot = currentValidatorSet.Root if task.IsHandover { task.ValidatorsRoot = nextValidatorSet.Root + if task.nextAuthoritiesRoot == task.ValidatorsRoot { + log.WithFields(logrus.Fields{ + "blockNumber": task.SignedCommitment.Commitment.BlockNumber, + "ValidatorSetID": task.SignedCommitment.Commitment.ValidatorSetID, + }).Warn("Ignore mandatory commitment authorities not change") + return nil + } } // Initial submission diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index da2c491e28..c49aaefebf 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -118,6 +118,7 @@ func (li *PolkadotListener) scanCommitments( continue } task.IsHandover = true + task.nextAuthoritiesRoot = nextAuthoritySet.Root select { case <-ctx.Done(): return ctx.Err() diff --git a/relayer/relays/beefy/task.go b/relayer/relays/beefy/task.go index 7bd8733c55..7fdbff2e4f 100644 --- a/relayer/relays/beefy/task.go +++ b/relayer/relays/beefy/task.go @@ -7,9 +7,10 @@ import ( ) type Request struct { - Validators []substrate.Authority - ValidatorsRoot [32]byte - SignedCommitment types.SignedCommitment - Proof merkle.SimplifiedMMRProof - IsHandover bool + Validators []substrate.Authority + ValidatorsRoot [32]byte + nextAuthoritiesRoot [32]byte + SignedCommitment types.SignedCommitment + Proof merkle.SimplifiedMMRProof + IsHandover bool } From 40f95923a816184dfb39c8e1e30a89b4f8d1c0f1 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 29 May 2024 08:42:31 +0800 Subject: [PATCH 07/31] Cleanup --- relayer/relays/beefy/scanner.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/relayer/relays/beefy/scanner.go b/relayer/relays/beefy/scanner.go index 12a42ebfd3..3188812f9b 100644 --- a/relayer/relays/beefy/scanner.go +++ b/relayer/relays/beefy/scanner.go @@ -206,11 +206,6 @@ func scanSafeCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.S sendError(result.Error) return } - log.WithFields(log.Fields{ - "blockNumber": result.BlockNumber, - "depth": result.Depth, - "commitment": result.SignedCommitment.Commitment, - }).Info("fetch commitment") blockNumber := result.SignedCommitment.Commitment.BlockNumber blockHash, err := api.RPC.Chain.GetBlockHash(uint64(blockNumber)) 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 08/31] 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 629a478eaf62fce4841635b2421c2d03991e7a28 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 31 May 2024 09:46:54 +0800 Subject: [PATCH 09/31] Sync beefy commitment on demand --- relayer/cmd/root.go | 1 + relayer/cmd/sync_beefy_commitment.go | 65 ++++++++++++++++ relayer/relays/beefy/ethereum-writer.go | 37 +++++---- relayer/relays/beefy/main.go | 44 +++++++++++ relayer/relays/beefy/polkadot-listener.go | 91 ++++++++++++++++++++--- 5 files changed, 214 insertions(+), 24 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..c16d763b23 --- /dev/null +++ b/relayer/cmd/sync_beefy_commitment.go @@ -0,0 +1,65 @@ +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.MarkFlagRequired("config") + cmd.Flags().String("private-key", "", "Ethereum private key") + cmd.Flags().String("privateKeyFile", "", "The file from which to read the private key") + cmd.Flags().Uint64P("relay-block", "b", 0, "Relay block number which contains a Parachain message") + cmd.MarkFlagRequired("relay-block") + 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("privateKeyFile") + 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 + } + relayBlockNumber, _ := cmd.Flags().GetUint64("relay-block") + err = relay.SyncUpdate(ctx, relayBlockNumber) + return err +} diff --git a/relayer/relays/beefy/ethereum-writer.go b/relayer/relays/beefy/ethereum-writer.go index eb3968e126..a9b445090c 100644 --- a/relayer/relays/beefy/ethereum-writer.go +++ b/relayer/relays/beefy/ethereum-writer.go @@ -40,22 +40,10 @@ 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()) + err := wr.initialize(ctx) if err != nil { - return fmt.Errorf("create beefy client: %w", err) + return fmt.Errorf("initialize EthereumWriter: %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 { @@ -301,3 +289,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..e18e884869 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -102,3 +102,47 @@ func (relay *Relay) getInitialState(ctx context.Context) (uint64, uint64, error) return latestBeefyBlock, currentValidatorSet.Id.Uint64(), nil } + +func (relay *Relay) SyncUpdate(ctx context.Context, relayBlockNumber uint64) error { + err := relay.relaychainConn.Connect(ctx) + if err != nil { + return fmt.Errorf("create relaychain connection: %w", err) + } + + err = relay.ethereumConn.Connect(ctx) + if err != nil { + return fmt.Errorf("create ethereum connection: %w", err) + } + beefyBlock, validatorSetID, err := relay.getInitialState(ctx) + if err != nil { + return fmt.Errorf("fetch BeefyClient current state: %w", err) + } + if relayBlockNumber <= beefyBlock { + log.WithFields(log.Fields{ + "validatorSetID": validatorSetID, + "beefyBlock": beefyBlock, + "relayBlock": relayBlockNumber, + }).Info("Already synced so just ignore") + return nil + } + request, err := relay.polkadotListener.generateBeefyUpdateRequest(relayBlockNumber) + if err != nil { + return fmt.Errorf("fail to generate next beefy request: %w", err) + } + err = relay.ethereumWriter.initialize(ctx) + if err != nil { + return fmt.Errorf("initialize EthereumWriter: %w", err) + } + err = relay.ethereumWriter.submit(ctx, request) + if err != nil { + return fmt.Errorf("fail to submit beefy update: %w", err) + } + updatedBeefyBlock, _, _ := relay.getInitialState(ctx) + log.WithFields(log.Fields{ + "initialValidatorSetID": validatorSetID, + "initialBeefyBlock": beefyBlock, + "blockNumber": relayBlockNumber, + "updatedBeefyBlock": updatedBeefyBlock, + }).Info("Sync beefy update success") + return nil +} diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index 5aebb9a0bf..e12481eb37 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 } @@ -150,3 +148,76 @@ func (li *PolkadotListener) queryBeefyNextAuthoritySet(blockHash types.Hash) (Be return nextAuthoritySet, nil } + +func (li *PolkadotListener) generateBeefyUpdateRequest(relayBlockNumber uint64) (Request, error) { + api := li.conn.API() + meta := li.conn.Metadata() + var request Request + var latestBeefyBlockNumber uint64 + var latestBeefyBlockHash types.Hash + for { + finalizedBeefyBlockHash, err := api.RPC.Beefy.GetFinalizedHead() + if err != nil { + return request, fmt.Errorf("fetch beefy finalized head: %w", err) + } + + finalizedBeefyBlockHeader, err := api.RPC.Chain.GetHeader(finalizedBeefyBlockHash) + if err != nil { + return request, fmt.Errorf("fetch block header: %w", err) + } + + latestBeefyBlockNumber = uint64(finalizedBeefyBlockHeader.Number) + if latestBeefyBlockNumber < relayBlockNumber { + time.Sleep(6 * time.Second) + continue + } + latestBeefyBlockHash = finalizedBeefyBlockHash + break + } + + nextFinalizedBeefyBlock, err := api.RPC.Chain.GetBlock(latestBeefyBlockHash) + if err != nil { + return request, fmt.Errorf("fetch block: %w", err) + } + + var commitment *types.SignedCommitment + for j := range nextFinalizedBeefyBlock.Justifications { + sc := types.OptionalSignedCommitment{} + if nextFinalizedBeefyBlock.Justifications[j].EngineID() == "BEEF" { + err := types.DecodeFromBytes(nextFinalizedBeefyBlock.Justifications[j].Payload(), &sc) + if err != nil { + return request, fmt.Errorf("decode BEEFY signed commitment: %w", err) + } + ok, value := sc.Unwrap() + if ok { + commitment = &value + } + } + } + if commitment == nil { + return request, fmt.Errorf("beefy block without a valid commitment") + } + + proofIsValid, proof, err := makeProof(meta, api, uint32(latestBeefyBlockNumber), latestBeefyBlockHash) + if err != nil { + return request, fmt.Errorf("proof generation for block %v at hash %v: %w", latestBeefyBlockNumber, latestBeefyBlockHash.Hex(), err) + } + if !proofIsValid { + return request, fmt.Errorf("Proof for leaf is invalid for block %v at hash %v: %w", latestBeefyBlockNumber, latestBeefyBlockHash.Hex(), 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 +} From 67c52e8940ba333fdfe62db7203a69cec988fd11 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 31 May 2024 14:14:10 +0800 Subject: [PATCH 10/31] Minor fix --- relayer/cmd/sync_beefy_commitment.go | 1 - relayer/relays/beefy/main.go | 19 +++++++++++++++++-- relayer/relays/beefy/scanner.go | 4 ---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/relayer/cmd/sync_beefy_commitment.go b/relayer/cmd/sync_beefy_commitment.go index c16d763b23..d0fd1399f0 100644 --- a/relayer/cmd/sync_beefy_commitment.go +++ b/relayer/cmd/sync_beefy_commitment.go @@ -20,7 +20,6 @@ func syncBeefyCommitmentCmd() *cobra.Command { } cmd.Flags().String("config", "/tmp/snowbridge/beefy-relay.json", "Path to configuration file") - cmd.MarkFlagRequired("config") cmd.Flags().String("private-key", "", "Ethereum private key") cmd.Flags().String("privateKeyFile", "", "The file from which to read the private key") cmd.Flags().Uint64P("relay-block", "b", 0, "Relay block number which contains a Parachain message") diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index e18e884869..2bb090782a 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -13,6 +13,7 @@ import ( "github.com/snowfork/snowbridge/relayer/contracts" "github.com/snowfork/snowbridge/relayer/crypto/secp256k1" + "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" ) @@ -125,7 +126,7 @@ func (relay *Relay) SyncUpdate(ctx context.Context, relayBlockNumber uint64) err }).Info("Already synced so just ignore") return nil } - request, err := relay.polkadotListener.generateBeefyUpdateRequest(relayBlockNumber) + task, err := relay.polkadotListener.generateBeefyUpdateRequest(relayBlockNumber) if err != nil { return fmt.Errorf("fail to generate next beefy request: %w", err) } @@ -133,7 +134,21 @@ func (relay *Relay) SyncUpdate(ctx context.Context, relayBlockNumber uint64) err if err != nil { return fmt.Errorf("initialize EthereumWriter: %w", err) } - err = relay.ethereumWriter.submit(ctx, request) + state, err := relay.ethereumWriter.queryBeefyClientState(ctx) + if err != nil { + return fmt.Errorf("query beefy client state: %w", err) + } + if task.SignedCommitment.Commitment.BlockNumber <= uint32(state.LatestBeefyBlock) { + log.WithFields(logrus.Fields{ + "beefyBlockNumber": task.SignedCommitment.Commitment.BlockNumber, + "beefyBlockSynced": state.LatestBeefyBlock, + }).Info("Commitment already synced") + return nil + } + // Mandatory commitments are always signed by the next validator set recorded in + // the beefy light client + task.ValidatorsRoot = state.NextValidatorSetRoot + err = relay.ethereumWriter.submit(ctx, task) if err != nil { return fmt.Errorf("fail to submit beefy update: %w", err) } 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 dba8cfc8cda1445e02d41f7136d5bd46a241e098 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 31 May 2024 14:36:49 +0800 Subject: [PATCH 11/31] More comments --- relayer/relays/beefy/main.go | 59 ++++++++++++++--------- relayer/relays/beefy/polkadot-listener.go | 2 +- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index 2bb090782a..18cf7e6ad4 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -105,59 +105,70 @@ func (relay *Relay) getInitialState(ctx context.Context) (uint64, uint64, error) } func (relay *Relay) SyncUpdate(ctx context.Context, relayBlockNumber uint64) error { + // Initialize relaychainConn err := relay.relaychainConn.Connect(ctx) if err != nil { return fmt.Errorf("create relaychain connection: %w", err) } + // Initialize ethereumConn err = relay.ethereumConn.Connect(ctx) if err != nil { return fmt.Errorf("create ethereum connection: %w", err) } - beefyBlock, validatorSetID, err := relay.getInitialState(ctx) + err = relay.ethereumWriter.initialize(ctx) if err != nil { - return fmt.Errorf("fetch BeefyClient current state: %w", err) + return fmt.Errorf("initialize EthereumWriter: %w", err) + } + + state, err := relay.ethereumWriter.queryBeefyClientState(ctx) + if err != nil { + return fmt.Errorf("query beefy client state: %w", err) } - if relayBlockNumber <= beefyBlock { + // Ignore relay block already synced + if relayBlockNumber <= state.LatestBeefyBlock { log.WithFields(log.Fields{ - "validatorSetID": validatorSetID, - "beefyBlock": beefyBlock, + "validatorSetID": state.CurrentValidatorSetID, + "beefyBlock": state.LatestBeefyBlock, "relayBlock": relayBlockNumber, - }).Info("Already synced so just ignore") + }).Info("Relay block already synced, just ignore") return nil } - task, err := relay.polkadotListener.generateBeefyUpdateRequest(relayBlockNumber) + + // generate beefy update for that specific relay block + task, err := relay.polkadotListener.generateBeefyUpdate(relayBlockNumber) if err != nil { return fmt.Errorf("fail to generate next beefy request: %w", err) } - err = relay.ethereumWriter.initialize(ctx) - if err != nil { - return fmt.Errorf("initialize EthereumWriter: %w", err) - } - state, err := relay.ethereumWriter.queryBeefyClientState(ctx) - if err != nil { - return fmt.Errorf("query beefy client state: %w", err) - } + + // Ignore commitment already synced if task.SignedCommitment.Commitment.BlockNumber <= uint32(state.LatestBeefyBlock) { log.WithFields(logrus.Fields{ "beefyBlockNumber": task.SignedCommitment.Commitment.BlockNumber, "beefyBlockSynced": state.LatestBeefyBlock, - }).Info("Commitment already synced") + }).Info("New commitment already synced, just ignore") return nil } - // Mandatory commitments are always signed by the next validator set recorded in - // the beefy light client - task.ValidatorsRoot = state.NextValidatorSetRoot + + // 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) } - updatedBeefyBlock, _, _ := relay.getInitialState(ctx) + + updatedState, err := relay.ethereumWriter.queryBeefyClientState(ctx) + if err != nil { + return fmt.Errorf("query beefy client state: %w", err) + } log.WithFields(log.Fields{ - "initialValidatorSetID": validatorSetID, - "initialBeefyBlock": beefyBlock, - "blockNumber": relayBlockNumber, - "updatedBeefyBlock": updatedBeefyBlock, + "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 e12481eb37..1f8db58058 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -149,7 +149,7 @@ func (li *PolkadotListener) queryBeefyNextAuthoritySet(blockHash types.Hash) (Be return nextAuthoritySet, nil } -func (li *PolkadotListener) generateBeefyUpdateRequest(relayBlockNumber uint64) (Request, error) { +func (li *PolkadotListener) generateBeefyUpdate(relayBlockNumber uint64) (Request, error) { api := li.conn.API() meta := li.conn.Metadata() var request Request From a886879bc42cf7a1dcf4f8bc97285f3ddda6b419 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 31 May 2024 20:59:47 +0800 Subject: [PATCH 12/31] More refactoring --- relayer/relays/beefy/ethereum-writer.go | 5 -- relayer/relays/beefy/main.go | 43 +++-------- relayer/relays/beefy/polkadot-listener.go | 34 ++------- relayer/relays/beefy/scanner.go | 87 +++++++++++------------ 4 files changed, 55 insertions(+), 114 deletions(-) diff --git a/relayer/relays/beefy/ethereum-writer.go b/relayer/relays/beefy/ethereum-writer.go index a9b445090c..87446d7c7e 100644 --- a/relayer/relays/beefy/ethereum-writer.go +++ b/relayer/relays/beefy/ethereum-writer.go @@ -40,11 +40,6 @@ func NewEthereumWriter( } func (wr *EthereumWriter) Start(ctx context.Context, eg *errgroup.Group, requests <-chan Request) error { - err := wr.initialize(ctx) - if err != nil { - return fmt.Errorf("initialize EthereumWriter: %w", err) - } - // launch task processor eg.Go(func() error { for { diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index 18cf7e6ad4..103baa457c 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" "github.com/sirupsen/logrus" @@ -57,53 +54,33 @@ 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()) - if err != nil { - return 0, 0, err - } - - callOpts := bind.CallOpts{ - Context: ctx, - } - - latestBeefyBlock, err := beefyClient.LatestBeefyBlock(&callOpts) - if err != nil { - return 0, 0, err - } - - currentValidatorSet, err := beefyClient.CurrentValidatorSet(&callOpts) - if err != nil { - return 0, 0, err - } - - return latestBeefyBlock, currentValidatorSet.Id.Uint64(), nil -} - func (relay *Relay) SyncUpdate(ctx context.Context, relayBlockNumber uint64) error { // Initialize relaychainConn err := relay.relaychainConn.Connect(ctx) @@ -136,7 +113,7 @@ func (relay *Relay) SyncUpdate(ctx context.Context, relayBlockNumber uint64) err } // generate beefy update for that specific relay block - task, err := relay.polkadotListener.generateBeefyUpdate(relayBlockNumber) + task, err := relay.polkadotListener.generateBeefyUpdate(ctx, relayBlockNumber) 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 1f8db58058..dfae7b5d04 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -149,7 +149,7 @@ func (li *PolkadotListener) queryBeefyNextAuthoritySet(blockHash types.Hash) (Be return nextAuthoritySet, nil } -func (li *PolkadotListener) generateBeefyUpdate(relayBlockNumber uint64) (Request, error) { +func (li *PolkadotListener) generateBeefyUpdate(ctx context.Context, relayBlockNumber uint64) (Request, error) { api := li.conn.API() meta := li.conn.Metadata() var request Request @@ -175,35 +175,9 @@ func (li *PolkadotListener) generateBeefyUpdate(relayBlockNumber uint64) (Reques break } - nextFinalizedBeefyBlock, err := api.RPC.Chain.GetBlock(latestBeefyBlockHash) + commitment, proof, err := fetchCommitmentAndProof(ctx, meta, api, latestBeefyBlockHash) if err != nil { - return request, fmt.Errorf("fetch block: %w", err) - } - - var commitment *types.SignedCommitment - for j := range nextFinalizedBeefyBlock.Justifications { - sc := types.OptionalSignedCommitment{} - if nextFinalizedBeefyBlock.Justifications[j].EngineID() == "BEEF" { - err := types.DecodeFromBytes(nextFinalizedBeefyBlock.Justifications[j].Payload(), &sc) - if err != nil { - return request, fmt.Errorf("decode BEEFY signed commitment: %w", err) - } - ok, value := sc.Unwrap() - if ok { - commitment = &value - } - } - } - if commitment == nil { - return request, fmt.Errorf("beefy block without a valid commitment") - } - - proofIsValid, proof, err := makeProof(meta, api, uint32(latestBeefyBlockNumber), latestBeefyBlockHash) - if err != nil { - return request, fmt.Errorf("proof generation for block %v at hash %v: %w", latestBeefyBlockNumber, latestBeefyBlockHash.Hex(), err) - } - if !proofIsValid { - return request, fmt.Errorf("Proof for leaf is invalid for block %v at hash %v: %w", latestBeefyBlockNumber, latestBeefyBlockHash.Hex(), err) + return request, fmt.Errorf("fetch commitment and proof: %w", err) } committedBeefyBlockNumber := uint64(commitment.Commitment.BlockNumber) @@ -216,7 +190,7 @@ func (li *PolkadotListener) generateBeefyUpdate(relayBlockNumber uint64) (Reques request = Request{ Validators: validators, SignedCommitment: *commitment, - Proof: proof, + Proof: *proof, } return request, nil diff --git a/relayer/relays/beefy/scanner.go b/relayer/relays/beefy/scanner.go index efd0f1e0ee..695cbf478a 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}: } } } @@ -290,3 +247,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 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 13/31] 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 14/31] 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 15/31] 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 c11ed7d9beefe9fc0ee924c2f3c1ed6a06aa61bc Mon Sep 17 00:00:00 2001 From: ron Date: Sun, 2 Jun 2024 01:03:36 +0800 Subject: [PATCH 16/31] Fix for boundary update --- contracts/src/BeefyClient.sol | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/contracts/src/BeefyClient.sol b/contracts/src/BeefyClient.sol index e43e1e779d..c7a1e6748d 100644 --- a/contracts/src/BeefyClient.sol +++ b/contracts/src/BeefyClient.sol @@ -256,7 +256,8 @@ contract BeefyClient { ValidatorSetState storage vset; uint16 signatureUsageCount; - if (commitment.validatorSetID == currentValidatorSet.id) { + if (commitment.validatorSetID == currentValidatorSet.id || commitment.validatorSetID == nextValidatorSet.id - 1) + { signatureUsageCount = currentValidatorSet.usageCounters.get(proof.index); currentValidatorSet.usageCounters.set(proof.index, signatureUsageCount.saturatingAdd(1)); vset = currentValidatorSet; @@ -354,11 +355,12 @@ contract BeefyClient { bool is_next_session = false; ValidatorSetState storage vset; - if (commitment.validatorSetID > currentValidatorSet.id) { + if (commitment.validatorSetID == currentValidatorSet.id || commitment.validatorSetID == nextValidatorSet.id - 1) + { + vset = currentValidatorSet; + } else if (commitment.validatorSetID >= nextValidatorSet.id) { is_next_session = true; vset = nextValidatorSet; - } else if (commitment.validatorSetID == currentValidatorSet.id) { - vset = currentValidatorSet; } else { revert InvalidCommitment(); } From 10f38d48648755585737a2e88770c1745abf1f9d Mon Sep 17 00:00:00 2001 From: ron Date: Sun, 2 Jun 2024 00:11:39 +0800 Subject: [PATCH 17/31] Fix for skip mandatory commitment --- relayer/relays/beefy/main.go | 9 ++- relayer/relays/beefy/polkadot-listener.go | 82 ++++++++++++++++++----- 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index 103baa457c..6e60d45a9e 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -126,9 +126,16 @@ func (relay *Relay) SyncUpdate(ctx context.Context, relayBlockNumber uint64) err }).Info("New commitment already synced, just ignore") return nil } + if task.SignedCommitment.Commitment.ValidatorSetID > state.NextValidatorSetID { + log.WithFields(log.Fields{ + "state": state, + "task": task, + }).Error("Task unexpected, wait for mandatory updates to catch up") + return fmt.Errorf("Task unexpected") + } // Submit the task - if task.SignedCommitment.Commitment.ValidatorSetID == state.CurrentValidatorSetID { + if task.SignedCommitment.Commitment.ValidatorSetID == state.CurrentValidatorSetID || task.SignedCommitment.Commitment.ValidatorSetID == state.NextValidatorSetID-1 { task.ValidatorsRoot = state.CurrentValidatorSetRoot } else { task.ValidatorsRoot = state.NextValidatorSetRoot diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index dfae7b5d04..ea425a013d 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -153,17 +153,45 @@ func (li *PolkadotListener) generateBeefyUpdate(ctx context.Context, relayBlockN 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) findLatestBeefyBlock(relayBlockNumber uint64) (types.Hash, error) { + api := li.conn.API() var latestBeefyBlockNumber uint64 var latestBeefyBlockHash types.Hash for { finalizedBeefyBlockHash, err := api.RPC.Beefy.GetFinalizedHead() if err != nil { - return request, fmt.Errorf("fetch beefy finalized head: %w", err) + return latestBeefyBlockHash, fmt.Errorf("fetch beefy finalized head: %w", err) } finalizedBeefyBlockHeader, err := api.RPC.Chain.GetHeader(finalizedBeefyBlockHash) if err != nil { - return request, fmt.Errorf("fetch block header: %w", err) + return latestBeefyBlockHash, fmt.Errorf("fetch block header: %w", err) } latestBeefyBlockNumber = uint64(finalizedBeefyBlockHeader.Number) @@ -174,24 +202,42 @@ func (li *PolkadotListener) generateBeefyUpdate(ctx context.Context, relayBlockN latestBeefyBlockHash = finalizedBeefyBlockHash break } + return latestBeefyBlockHash, nil +} - commitment, proof, err := fetchCommitmentAndProof(ctx, meta, api, latestBeefyBlockHash) - 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)) +func (li *PolkadotListener) findNextBeefyBlock(relayBlockNumber uint64) (types.Hash, error) { + api := li.conn.API() + nextBeefyBlockNumber := relayBlockNumber + var nextBeefyBlockHash types.Hash + var err error + for { + 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) + } - 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, + 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 request, nil } From 98e3e74a58119717134797cfecec8a8895e43124 Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 3 Jun 2024 10:36:07 +0800 Subject: [PATCH 18/31] Some refactoring --- relayer/cmd/sync_beefy_commitment.go | 12 ++++++------ relayer/relays/beefy/main.go | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/relayer/cmd/sync_beefy_commitment.go b/relayer/cmd/sync_beefy_commitment.go index d0fd1399f0..444840e33d 100644 --- a/relayer/cmd/sync_beefy_commitment.go +++ b/relayer/cmd/sync_beefy_commitment.go @@ -21,9 +21,9 @@ func syncBeefyCommitmentCmd() *cobra.Command { cmd.Flags().String("config", "/tmp/snowbridge/beefy-relay.json", "Path to configuration file") cmd.Flags().String("private-key", "", "Ethereum private key") - cmd.Flags().String("privateKeyFile", "", "The file from which to read the private key") - cmd.Flags().Uint64P("relay-block", "b", 0, "Relay block number which contains a Parachain message") - cmd.MarkFlagRequired("relay-block") + 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 } @@ -45,7 +45,7 @@ func SyncBeefyCommitmentFn(cmd *cobra.Command, _ []string) error { return err } privateKey, _ := cmd.Flags().GetString("private-key") - privateKeyFile, _ := cmd.Flags().GetString("privateKeyFile") + privateKeyFile, _ := cmd.Flags().GetString("private-key-file") if privateKey == "" && privateKeyFile == "" { return fmt.Errorf("missing private key") } @@ -58,7 +58,7 @@ func SyncBeefyCommitmentFn(cmd *cobra.Command, _ []string) error { if err != nil { return err } - relayBlockNumber, _ := cmd.Flags().GetUint64("relay-block") - err = relay.SyncUpdate(ctx, relayBlockNumber) + blockNumber, _ := cmd.Flags().GetUint64("block-number") + err = relay.OneShotSync(ctx, blockNumber) return err } diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index 6e60d45a9e..27ed0d7b2c 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -81,7 +81,7 @@ func (relay *Relay) Start(ctx context.Context, eg *errgroup.Group) error { return nil } -func (relay *Relay) SyncUpdate(ctx context.Context, relayBlockNumber uint64) error { +func (relay *Relay) OneShotSync(ctx context.Context, blockNumber uint64) error { // Initialize relaychainConn err := relay.relaychainConn.Connect(ctx) if err != nil { @@ -103,17 +103,17 @@ func (relay *Relay) SyncUpdate(ctx context.Context, relayBlockNumber uint64) err return fmt.Errorf("query beefy client state: %w", err) } // Ignore relay block already synced - if relayBlockNumber <= state.LatestBeefyBlock { + if blockNumber <= state.LatestBeefyBlock { log.WithFields(log.Fields{ "validatorSetID": state.CurrentValidatorSetID, "beefyBlock": state.LatestBeefyBlock, - "relayBlock": relayBlockNumber, + "relayBlock": blockNumber, }).Info("Relay block already synced, just ignore") return nil } // generate beefy update for that specific relay block - task, err := relay.polkadotListener.generateBeefyUpdate(ctx, relayBlockNumber) + task, err := relay.polkadotListener.generateBeefyUpdate(ctx, blockNumber) if err != nil { return fmt.Errorf("fail to generate next beefy request: %w", err) } From fc293296ce443abf616d62e5327163dc0d329d14 Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 3 Jun 2024 11:13:29 +0800 Subject: [PATCH 19/31] 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 715ac5a1c8030dac8a6ce4d3a899d48083d4eaa3 Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 3 Jun 2024 13:53:51 +0800 Subject: [PATCH 20/31] Find for next beefy block --- relayer/relays/beefy/polkadot-listener.go | 93 ++++++++++++----------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index 4a188e104c..9c55caf847 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -151,65 +151,66 @@ func (li *PolkadotListener) generateBeefyUpdate(ctx context.Context, relayBlockN return request, nil } -func (li *PolkadotListener) findLatestBeefyBlock(relayBlockNumber uint64) (types.Hash, error) { +func (li *PolkadotListener) findNextBeefyBlock(blockNumber uint64) (types.Hash, error) { api := li.conn.API() - var latestBeefyBlockNumber uint64 - var latestBeefyBlockHash types.Hash + var nextBeefyBlockHash, finalizedBeefyBlockHash types.Hash + var err error + nextBeefyBlockNumber := blockNumber for { - finalizedBeefyBlockHash, err := api.RPC.Beefy.GetFinalizedHead() + finalizedBeefyBlockHash, err = api.RPC.Beefy.GetFinalizedHead() if err != nil { - return latestBeefyBlockHash, fmt.Errorf("fetch beefy finalized head: %w", err) + return nextBeefyBlockHash, fmt.Errorf("fetch beefy finalized head: %w", err) } - finalizedBeefyBlockHeader, err := api.RPC.Chain.GetHeader(finalizedBeefyBlockHash) if err != nil { - return latestBeefyBlockHash, fmt.Errorf("fetch block header: %w", err) + return nextBeefyBlockHash, fmt.Errorf("fetch block header: %w", err) } - - latestBeefyBlockNumber = uint64(finalizedBeefyBlockHeader.Number) - if latestBeefyBlockNumber < relayBlockNumber { + latestBeefyBlockNumber := uint64(finalizedBeefyBlockHeader.Number) + if latestBeefyBlockNumber <= nextBeefyBlockNumber { + // The relay block not finalized yet, just wait and retry time.Sleep(6 * time.Second) continue - } - latestBeefyBlockHash = finalizedBeefyBlockHash - break - } - return latestBeefyBlockHash, nil -} - -func (li *PolkadotListener) findNextBeefyBlock(relayBlockNumber uint64) (types.Hash, error) { - api := li.conn.API() - nextBeefyBlockNumber := relayBlockNumber - var nextBeefyBlockHash types.Hash - var err error - for { - 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) + } 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("decode BEEFY signed commitment: %w", err) + return nextBeefyBlockHash, fmt.Errorf("fetch block hash: %w", err) } - ok, value := sc.Unwrap() - if ok { - commitment = &value + 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++ } } - if commitment != nil { - return nextBeefyBlockHash, nil - } - nextBeefyBlockNumber++ } - + return nextBeefyBlockHash, nil } From 913960e24144370f423be8fcc9fd482d87002c90 Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 3 Jun 2024 18:40:39 +0800 Subject: [PATCH 21/31] Improve log --- relayer/relays/beefy/main.go | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index 27ed0d7b2c..bc0425971e 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -10,7 +10,6 @@ import ( "github.com/snowfork/snowbridge/relayer/chain/relaychain" "github.com/snowfork/snowbridge/relayer/crypto/secp256k1" - "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" ) @@ -118,20 +117,24 @@ func (relay *Relay) OneShotSync(ctx context.Context, blockNumber uint64) error { return fmt.Errorf("fail to generate next beefy request: %w", err) } - // Ignore commitment already synced + // Ignore commitment earlier than LatestBeefyBlock which is outdated if task.SignedCommitment.Commitment.BlockNumber <= uint32(state.LatestBeefyBlock) { - log.WithFields(logrus.Fields{ - "beefyBlockNumber": task.SignedCommitment.Commitment.BlockNumber, - "beefyBlockSynced": state.LatestBeefyBlock, - }).Info("New commitment already synced, just ignore") + 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{ - "state": state, - "task": task, - }).Error("Task unexpected, wait for mandatory updates to catch up") - return fmt.Errorf("Task unexpected") + "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 From edd9f21c0b6b972928dc48a4c93e58c1867bbc0b Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 3 Jun 2024 18:47:59 +0800 Subject: [PATCH 22/31] Remove check unrelated --- relayer/relays/beefy/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index bc0425971e..1222f88a76 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -138,7 +138,7 @@ func (relay *Relay) OneShotSync(ctx context.Context, blockNumber uint64) error { } // Submit the task - if task.SignedCommitment.Commitment.ValidatorSetID == state.CurrentValidatorSetID || task.SignedCommitment.Commitment.ValidatorSetID == state.NextValidatorSetID-1 { + if task.SignedCommitment.Commitment.ValidatorSetID == state.CurrentValidatorSetID { task.ValidatorsRoot = state.CurrentValidatorSetRoot } else { task.ValidatorsRoot = state.NextValidatorSetRoot From 08c70f18a13ced98511adfd00c931bc6d7251543 Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 3 Jun 2024 18:53:45 +0800 Subject: [PATCH 23/31] Check commitment in CurrentValidatorSetID --- relayer/relays/beefy/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index 0af6396d68..1605a16ff8 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -138,7 +138,7 @@ func (relay *Relay) OneShotSync(ctx context.Context, blockNumber uint64) error { } // Submit the task - if task.SignedCommitment.Commitment.ValidatorSetID == state.CurrentValidatorSetID { + if task.SignedCommitment.Commitment.ValidatorSetID == state.CurrentValidatorSetID || task.SignedCommitment.Commitment.ValidatorSetID == state.NextValidatorSetID-1 { task.ValidatorsRoot = state.CurrentValidatorSetRoot } else { task.ValidatorsRoot = state.NextValidatorSetRoot From 95845a380269bdbe35891b2a11f7e6d214d7f251 Mon Sep 17 00:00:00 2001 From: Ron Date: Mon, 3 Jun 2024 19:31:05 +0800 Subject: [PATCH 24/31] 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 25/31] 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 3d8daa635f7dec8c692c2bb57c494b2e7f8b9fbc Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 4 Jun 2024 20:36:15 +0800 Subject: [PATCH 26/31] Test happy path --- contracts/test/BeefyClient.t.sol | 65 +++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/contracts/test/BeefyClient.t.sol b/contracts/test/BeefyClient.t.sol index 42566ca4ec..2f807afa2d 100644 --- a/contracts/test/BeefyClient.t.sol +++ b/contracts/test/BeefyClient.t.sol @@ -101,6 +101,20 @@ contract BeefyClientTest is Test { return BeefyClient.Commitment(blockNumber, setId, payload); } + function initializeNonConsecutive(uint32 _setId, uint32 _nextSetId) + public + returns (BeefyClient.Commitment memory) + { + currentSetId = _setId; + nextSetId = _nextSetId; + BeefyClient.ValidatorSet memory vset = BeefyClient.ValidatorSet(currentSetId, setSize, root); + BeefyClient.ValidatorSet memory nextvset = BeefyClient.ValidatorSet(nextSetId, setSize, root); + beefyClient.initialize_public(0, vset, nextvset); + BeefyClient.PayloadItem[] memory payload = new BeefyClient.PayloadItem[](1); + payload[0] = BeefyClient.PayloadItem(mmrRootID, abi.encodePacked(mmrRoot)); + return BeefyClient.Commitment(blockNumber, setId, payload); + } + function printBitArray(uint256[] memory bits) private view { for (uint256 i = 0; i < bits.length; i++) { console.log("bits index at %d is %d", i, bits[i]); @@ -166,7 +180,7 @@ contract BeefyClientTest is Test { ); } - function testSubmit() public returns (BeefyClient.Commitment memory) { + function testSubmitHappyPath() public returns (BeefyClient.Commitment memory) { BeefyClient.Commitment memory commitment = initialize(setId); assertEq(beefyClient.getValidatorCounter(false, finalValidatorProofs[0].index), 0); @@ -388,7 +402,7 @@ contract BeefyClientTest is Test { commitPrevRandao(); } - function testSubmitWithHandover() public { + function testSubmitWithHandoverHappyPath() public { //initialize with previous set BeefyClient.Commitment memory commitment = initialize(setId - 1); @@ -752,4 +766,51 @@ contract BeefyClientTest is Test { BeefyClient.ValidatorSet(nextId, 0, 0x0) ); } + + function testSubmitNonConsecutive() public { + BeefyClient.Commitment memory commitment = initializeNonConsecutive(setId, setId + 3); + + beefyClient.submitInitial(commitment, bitfield, finalValidatorProofs[0]); + + // mine random delay blocks + vm.roll(block.number + randaoCommitDelay); + + commitPrevRandao(); + + createFinalProofs(); + + beefyClient.submitFinal( + commitment, bitfield, finalValidatorProofs, emptyLeaf, emptyLeafProofs, emptyLeafProofOrder + ); + + assertEq(beefyClient.latestBeefyBlock(), blockNumber); + + (uint128 _currentSetId,,,) = beefyClient.currentValidatorSet(); + assertEq(_currentSetId, uint128(setId)); + + (uint128 _nextSetId,,,) = beefyClient.nextValidatorSet(); + assertEq(_nextSetId, uint128(setId + 3)); + } + + function testSubmitWithHandoverNonConsecutive() public { + //initialize with previous set + BeefyClient.Commitment memory commitment = initializeNonConsecutive(setId - 3, setId + 1); + + beefyClient.submitInitial(commitment, bitfield, finalValidatorProofs[0]); + + vm.roll(block.number + randaoCommitDelay); + + commitPrevRandao(); + + createFinalProofs(); + + beefyClient.submitFinal(commitment, bitfield, finalValidatorProofs, mmrLeaf, mmrLeafProofs, leafProofOrder); + assertEq(beefyClient.latestBeefyBlock(), blockNumber); + + (uint128 _currentSetId,,,) = beefyClient.currentValidatorSet(); + assertEq(_currentSetId, uint128(setId - 3)); + + (uint128 _nextSetId,,,) = beefyClient.nextValidatorSet(); + assertEq(_nextSetId, uint128(setId + 1)); + } } From 47cceebb6e7b1f5d66cca37a778be9cb3616b55f Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 4 Jun 2024 21:26:06 +0800 Subject: [PATCH 27/31] More tests --- contracts/test/BeefyClient.t.sol | 26 +++++++++++++++++++++--- contracts/test/mocks/BeefyClientMock.sol | 1 - 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/contracts/test/BeefyClient.t.sol b/contracts/test/BeefyClient.t.sol index 2f807afa2d..a2494d5f60 100644 --- a/contracts/test/BeefyClient.t.sol +++ b/contracts/test/BeefyClient.t.sol @@ -72,8 +72,6 @@ contract BeefyClientTest is Test { bitSetArray = beefyValidatorSetRaw.readUintArray(".participants"); absentBitSetArray = beefyValidatorSetRaw.readUintArray(".absentees"); - console.log("current validator's merkle root is: %s", Strings.toHexString(uint256(root), 32)); - beefyClient = new BeefyClientMock(randaoCommitDelay, randaoCommitExpiration, minNumRequiredSignatures); bitfield = beefyClient.createInitialBitfield(bitSetArray, setSize); @@ -792,7 +790,7 @@ contract BeefyClientTest is Test { assertEq(_nextSetId, uint128(setId + 3)); } - function testSubmitWithHandoverNonConsecutive() public { + function testSubmitNonConsecutiveCommitNotInCurrentSet() public { //initialize with previous set BeefyClient.Commitment memory commitment = initializeNonConsecutive(setId - 3, setId + 1); @@ -813,4 +811,26 @@ contract BeefyClientTest is Test { (uint128 _nextSetId,,,) = beefyClient.nextValidatorSet(); assertEq(_nextSetId, uint128(setId + 1)); } + + function testSubmitWithHandoverNonConsecutive() public { + //initialize with previous set + BeefyClient.Commitment memory commitment = initializeNonConsecutive(setId - 3, setId); + + beefyClient.submitInitial(commitment, bitfield, finalValidatorProofs[0]); + + vm.roll(block.number + randaoCommitDelay); + + commitPrevRandao(); + + createFinalProofs(); + + beefyClient.submitFinal(commitment, bitfield, finalValidatorProofs, mmrLeaf, mmrLeafProofs, leafProofOrder); + assertEq(beefyClient.latestBeefyBlock(), blockNumber); + + (uint128 _currentSetId,,,) = beefyClient.currentValidatorSet(); + assertEq(_currentSetId, uint128(setId)); + + (uint128 _nextSetId,,,) = beefyClient.nextValidatorSet(); + assertEq(_nextSetId, uint128(setId + 1)); + } } diff --git a/contracts/test/mocks/BeefyClientMock.sol b/contracts/test/mocks/BeefyClientMock.sol index 40caee279b..f28a3a454f 100644 --- a/contracts/test/mocks/BeefyClientMock.sol +++ b/contracts/test/mocks/BeefyClientMock.sol @@ -43,7 +43,6 @@ contract BeefyClientMock is BeefyClient { nextValidatorSet.length = _nextValidatorSet.length; nextValidatorSet.root = _nextValidatorSet.root; nextValidatorSet.usageCounters = createUint16Array(nextValidatorSet.length); - console.log(currentValidatorSet.usageCounters.data.length); } // Used to verify integrity of storage to storage copies From 0f221e559b24768b8f468d862fcde98bc8b614ee Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 4 Jun 2024 21:52:43 +0800 Subject: [PATCH 28/31] More tests --- contracts/test/BeefyClient.t.sol | 42 ++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/contracts/test/BeefyClient.t.sol b/contracts/test/BeefyClient.t.sol index a2494d5f60..700a324344 100644 --- a/contracts/test/BeefyClient.t.sol +++ b/contracts/test/BeefyClient.t.sol @@ -766,11 +766,10 @@ contract BeefyClientTest is Test { } function testSubmitNonConsecutive() public { - BeefyClient.Commitment memory commitment = initializeNonConsecutive(setId, setId + 3); + BeefyClient.Commitment memory commitment = initializeNonConsecutive(setId, setId + 2); beefyClient.submitInitial(commitment, bitfield, finalValidatorProofs[0]); - // mine random delay blocks vm.roll(block.number + randaoCommitDelay); commitPrevRandao(); @@ -787,12 +786,11 @@ contract BeefyClientTest is Test { assertEq(_currentSetId, uint128(setId)); (uint128 _nextSetId,,,) = beefyClient.nextValidatorSet(); - assertEq(_nextSetId, uint128(setId + 3)); + assertEq(_nextSetId, uint128(setId + 2)); } function testSubmitNonConsecutiveCommitNotInCurrentSet() public { - //initialize with previous set - BeefyClient.Commitment memory commitment = initializeNonConsecutive(setId - 3, setId + 1); + BeefyClient.Commitment memory commitment = initializeNonConsecutive(setId - 1, setId + 1); beefyClient.submitInitial(commitment, bitfield, finalValidatorProofs[0]); @@ -806,15 +804,14 @@ contract BeefyClientTest is Test { assertEq(beefyClient.latestBeefyBlock(), blockNumber); (uint128 _currentSetId,,,) = beefyClient.currentValidatorSet(); - assertEq(_currentSetId, uint128(setId - 3)); + assertEq(_currentSetId, uint128(setId - 1)); (uint128 _nextSetId,,,) = beefyClient.nextValidatorSet(); assertEq(_nextSetId, uint128(setId + 1)); } function testSubmitWithHandoverNonConsecutive() public { - //initialize with previous set - BeefyClient.Commitment memory commitment = initializeNonConsecutive(setId - 3, setId); + BeefyClient.Commitment memory commitment = initializeNonConsecutive(setId - 2, setId); beefyClient.submitInitial(commitment, bitfield, finalValidatorProofs[0]); @@ -833,4 +830,33 @@ contract BeefyClientTest is Test { (uint128 _nextSetId,,,) = beefyClient.nextValidatorSet(); assertEq(_nextSetId, uint128(setId + 1)); } + + function testSubmitWithHandoverNonConsecutiveCommitmentMoreThanNextSetID() public { + BeefyClient.Commitment memory commitment = initializeNonConsecutive(setId - 2, setId - 1); + + beefyClient.submitInitial(commitment, bitfield, finalValidatorProofs[0]); + + vm.roll(block.number + randaoCommitDelay); + + commitPrevRandao(); + + createFinalProofs(); + + beefyClient.submitFinal(commitment, bitfield, finalValidatorProofs, mmrLeaf, mmrLeafProofs, leafProofOrder); + assertEq(beefyClient.latestBeefyBlock(), blockNumber); + + (uint128 _currentSetId,,,) = beefyClient.currentValidatorSet(); + assertEq(_currentSetId, uint128(setId - 1)); + + (uint128 _nextSetId,,,) = beefyClient.nextValidatorSet(); + assertEq(_nextSetId, uint128(setId + 1)); + } + + function testSubmitNonConsecutiveCommitInvalidSetId() public { + BeefyClient.Commitment memory commitment = initializeNonConsecutive(setId - 1, setId + 2); + + vm.expectRevert(BeefyClient.InvalidCommitment.selector); + + beefyClient.submitInitial(commitment, bitfield, finalValidatorProofs[0]); + } } From 585619e79617fd71aee1ae53f2b5412325d0d782 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 4 Jun 2024 23:38:30 +0800 Subject: [PATCH 29/31] For rococo compatibility --- relayer/chain/relaychain/connection.go | 15 +++++++++++++++ relayer/relays/beefy/polkadot-listener.go | 8 +++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/relayer/chain/relaychain/connection.go b/relayer/chain/relaychain/connection.go index c2d1e38124..87407d7f94 100644 --- a/relayer/chain/relaychain/connection.go +++ b/relayer/chain/relaychain/connection.go @@ -6,6 +6,7 @@ package relaychain import ( "context" "fmt" + "strings" gsrpc "github.com/snowfork/go-substrate-rpc-client/v4" "github.com/snowfork/go-substrate-rpc-client/v4/types" @@ -18,6 +19,7 @@ type Connection struct { api *gsrpc.SubstrateAPI metadata types.Metadata genesisHash types.Hash + isRococo bool } func NewConnection(endpoint string) *Connection { @@ -34,6 +36,10 @@ func (co *Connection) Metadata() *types.Metadata { return &co.metadata } +func (co *Connection) IsRococo() bool { + return co.isRococo +} + func (co *Connection) Connect(_ context.Context) error { // Initialize API api, err := gsrpc.NewSubstrateAPI(co.endpoint) @@ -56,6 +62,15 @@ func (co *Connection) Connect(_ context.Context) error { } co.genesisHash = genesisHash + // Fetch chain name + chainName, err := api.RPC.System.Chain() + if err != nil { + return err + } + if strings.HasPrefix(string(chainName), "Rococo") { + co.isRococo = true + } + log.WithFields(log.Fields{ "endpoint": co.endpoint, "metaVersion": meta.Version, diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index e2ecc439ea..a33a9c1185 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -124,7 +124,13 @@ func (li *PolkadotListener) queryBeefyAuthorities(blockHash types.Hash) ([]subst func (li *PolkadotListener) queryBeefyNextAuthoritySet(blockHash types.Hash) (types.BeefyNextAuthoritySet, error) { var nextAuthoritySet types.BeefyNextAuthoritySet - storageKey, err := types.CreateStorageKey(li.conn.Metadata(), "BeefyMmrLeaf", "BeefyNextAuthorities", nil, nil) + var storageKey types.StorageKey + var err error + if li.conn.IsRococo() { + storageKey, err = types.CreateStorageKey(li.conn.Metadata(), "MmrLeaf", "BeefyNextAuthorities", nil, nil) + } else { + storageKey, err = types.CreateStorageKey(li.conn.Metadata(), "BeefyMmrLeaf", "BeefyNextAuthorities", nil, nil) + } ok, err := li.conn.API().RPC.State.GetStorage(storageKey, &nextAuthoritySet, blockHash) if err != nil { return nextAuthoritySet, err From 2b302029f9aac245a2f9d2797a67351bc78d0083 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 5 Jun 2024 08:59:29 +0800 Subject: [PATCH 30/31] Revert changes --- contracts/foundry.toml | 5 +---- contracts/scripts/deploy.sh | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 639d0e08be..088aa9a909 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -5,10 +5,7 @@ optimizer_runs = 20000 via_ir = false 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 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 \ From c99028d5b347e163073f3925dc04f69b8206dc90 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 5 Jun 2024 09:30:44 +0800 Subject: [PATCH 31/31] Allow commitment in future --- relayer/relays/beefy/main.go | 16 +++++++++------- relayer/relays/beefy/polkadot-listener.go | 11 ++++++++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index 553ac0f0e0..93c743d4a8 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -128,13 +128,15 @@ func (relay *Relay) OneShotSync(ctx context.Context, blockNumber uint64) error { 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 + if task.NextValidatorsRoot != state.NextValidatorSetRoot { + 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 diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index a33a9c1185..8370e14945 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -163,10 +163,15 @@ func (li *PolkadotListener) generateBeefyUpdate(relayBlockNumber uint64) (Reques if err != nil { return request, fmt.Errorf("fetch beefy authorities at block %v: %w", committedBeefyBlockHash, err) } + nextAuthoritySet, err := li.queryBeefyNextAuthoritySet(committedBeefyBlockHash) + if err != nil { + return request, fmt.Errorf("fetch beefy authorities set at block %v: %w", committedBeefyBlockHash, err) + } request = Request{ - Validators: validators, - SignedCommitment: *commitment, - Proof: *proof, + Validators: validators, + SignedCommitment: *commitment, + Proof: *proof, + NextValidatorsRoot: nextAuthoritySet.Root, } return request, nil