diff --git a/relayer/cmd/scan_beefy.go b/relayer/cmd/scan_beefy.go index 32c9ad4562..7ee6f4efd5 100644 --- a/relayer/cmd/scan_beefy.go +++ b/relayer/cmd/scan_beefy.go @@ -62,7 +62,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 94e9ba5aad..ab8330ab4b 100644 --- a/relayer/relays/beefy/parameters.go +++ b/relayer/relays/beefy/parameters.go @@ -236,3 +236,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..1a16283745 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,8 @@ func (li *PolkadotListener) scanCommitments( "nextValidatorSetID": nextValidatorSetID, }, "validatorSetID": currentValidatorSet, - "IsHandover": validatorSetID == currentValidatorSet+1, + "IsHandover": validatorSetID > currentValidatorSet, + "IsMandatory": result.IsMandatory, "lastSyncedBeefyBlock": lastSyncedBeefyBlock, }) @@ -106,20 +97,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 +174,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 +} diff --git a/relayer/relays/beefy/scanner.go b/relayer/relays/beefy/scanner.go index 3188812f9b..d53e10e51d 100644 --- a/relayer/relays/beefy/scanner.go +++ b/relayer/relays/beefy/scanner.go @@ -2,6 +2,7 @@ package beefy import ( "context" + "encoding/binary" "fmt" "time" @@ -81,6 +82,7 @@ type ScanCommitmentsResult struct { BlockNumber uint64 BlockHash types.Hash Depth uint64 + IsMandatory bool Error error } @@ -129,6 +131,21 @@ func scanCommitments(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock ui return } + isMandatory := false + for _, digest := range block.Block.Header.Digest { + if !digest.IsConsensus { + continue + } + consensus := digest.AsConsensus + // Mandatory commitments contain a BEEF digest of type ConsensusLog::AuthoritiesChange (0x01) + // which signifies the the change of session authorities. + // https://github.com/paritytech/polkadot-sdk/blob/6a168ad57ad13ea0896f7684120f4fa15bfef474/substrate/primitives/consensus/beefy/src/lib.rs#L254C2-L254C19 + if decodeEngineId(uint32(consensus.ConsensusEngineID)) == "BEEF" && consensus.Bytes[0] == 0x01 { + isMandatory = true + break + } + } + var commitment *types.SignedCommitment for j := range block.Justifications { sc := types.OptionalSignedCommitment{} @@ -156,7 +173,7 @@ func scanCommitments(ctx context.Context, api *gsrpc.SubstrateAPI, startBlock ui select { case <-ctx.Done(): return - case out <- ScanCommitmentsResult{BlockNumber: result.BlockNumber, BlockHash: result.BlockHash, SignedCommitment: *commitment, Depth: result.Depth}: + case out <- ScanCommitmentsResult{BlockNumber: result.BlockNumber, BlockHash: result.BlockHash, SignedCommitment: *commitment, Depth: result.Depth, IsMandatory: isMandatory}: } } } @@ -167,6 +184,7 @@ type ScanSafeCommitmentsResult struct { MMRProof merkle.SimplifiedMMRProof BlockHash types.Hash Depth uint64 + IsMandatory bool Error error } @@ -231,7 +249,7 @@ func scanSafeCommitments(ctx context.Context, meta *types.Metadata, api *gsrpc.S select { case <-ctx.Done(): return - case out <- ScanSafeCommitmentsResult{result.SignedCommitment, proof, blockHash, result.Depth, nil}: + case out <- ScanSafeCommitmentsResult{result.SignedCommitment, proof, blockHash, result.Depth, result.IsMandatory, nil}: } } @@ -293,3 +311,9 @@ func verifyProof(meta *types.Metadata, api *gsrpc.SubstrateAPI, proof merkle.Sim return actualRoot == expectedRoot, nil } + +func decodeEngineId(engineId uint32) string { + idAsBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(idAsBytes, engineId) + return string(idAsBytes) +} diff --git a/web/packages/test/config/beefy-relay.json b/web/packages/test/config/beefy-relay.json index 119df296e9..c2f4929508 100644 --- a/web/packages/test/config/beefy-relay.json +++ b/web/packages/test/config/beefy-relay.json @@ -5,7 +5,7 @@ }, "beefy-activation-block": 0, "fast-forward-depth": 20, - "update-period": 0 + "update-period": 20 }, "sink": { "ethereum": { diff --git a/web/packages/test/scripts/build-binary.sh b/web/packages/test/scripts/build-binary.sh index 7bd383fc8c..ef28b2256e 100755 --- a/web/packages/test/scripts/build-binary.sh +++ b/web/packages/test/scripts/build-binary.sh @@ -17,8 +17,7 @@ build_binaries() { # Check that all 3 binaries are available and no changes made in the polkadot and substrate dirs if [[ ! -e "target/release/polkadot" || ! -e "target/release/polkadot-execute-worker" || ! -e "target/release/polkadot-prepare-worker" || "$changes_detected" -eq 1 ]]; then echo "Building polkadot binary, due to changes detected in polkadot or substrate, or binaries not found" - # Increase session length to 2 mins - ROCOCO_EPOCH_DURATION=20 cargo build --release --locked --bin polkadot --bin polkadot-execute-worker --bin polkadot-prepare-worker + cargo build --release --locked --bin polkadot --bin polkadot-execute-worker --bin polkadot-prepare-worker else echo "No changes detected in polkadot or substrate and binaries are available, not rebuilding relaychain binaries." fi