这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* [#580](https://github.com/babylonlabs-io/finality-provider/pull/580) chore: bump bbn for v2 compatibility
* [#572](https://github.com/babylonlabs-io/finality-provider/pull/572)
chore(rollup): ensure pub randomness is timestamped
* [#583](https://github.com/babylonlabs-io/finality-provider/pull/583)
feat(rollup): sparse pub rand generation
* [#584](https://github.com/babylonlabs-io/finality-provider/pull/584) chore: bump go 1.24
* [#586](https://github.com/babylonlabs-io/finality-provider/pull/586) chore: init fp metrics

Expand Down
14 changes: 10 additions & 4 deletions bsn/rollup/clientcontroller/consumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ func (cc *RollupBSNController) ReliablySendMsg(ctx context.Context, msg sdk.Msg,
return cc.reliablySendMsgs(ctx, []sdk.Msg{msg}, expectedErrs, unrecoverableErrs)
}

// queryContractConfig queries the finality contract for its config
func (cc *RollupBSNController) queryContractConfig(ctx context.Context) (*ContractConfig, error) {
// QueryContractConfig queries the finality contract for its config
func (cc *RollupBSNController) QueryContractConfig(ctx context.Context) (*ContractConfig, error) {
query := QueryMsg{
Config: &ContractConfig{},
}
Expand Down Expand Up @@ -589,14 +589,20 @@ func (cc *RollupBSNController) QueryPubRandCommitForHeight(ctx context.Context,
if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
if resp == nil {
return nil, nil
}
if err := resp.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate response: %w", err)
}

return resp, nil
}

// isEligibleForFinalitySignature checks if finality signatures are allowed for the given height
// based on the contract's BSN activation and interval requirements
func (cc *RollupBSNController) isEligibleForFinalitySignature(ctx context.Context, height uint64) (bool, error) {
config, err := cc.queryContractConfig(ctx)
config, err := cc.QueryContractConfig(ctx)
if err != nil {
return false, fmt.Errorf("failed to query contract config: %w", err)
}
Expand Down Expand Up @@ -628,7 +634,7 @@ func (cc *RollupBSNController) isEligibleForFinalitySignature(ctx context.Contex
}

func (cc *RollupBSNController) QueryFinalityActivationBlockHeight(ctx context.Context) (uint64, error) {
config, err := cc.queryContractConfig(ctx)
config, err := cc.QueryContractConfig(ctx)
if err != nil {
return 0, fmt.Errorf("failed to query contract config: %w", err)
}
Expand Down
29 changes: 24 additions & 5 deletions bsn/rollup/clientcontroller/msg.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package clientcontroller

import "fmt"
import (
"fmt"

"github.com/babylonlabs-io/finality-provider/types"
)

type CommitPublicRandomnessMsg struct {
CommitPublicRandomness CommitPublicRandomnessMsgParams `json:"commit_public_randomness"`
Expand Down Expand Up @@ -73,20 +77,35 @@ type HighestVotedHeightQuery struct {
BtcPkHex string `json:"btc_pk_hex"`
}

var _ types.PubRandCommit = &RollupPubRandCommit{}

type RollupPubRandCommit struct {
StartHeight uint64 `json:"start_height"`
NumPubRand uint64 `json:"num_pub_rand"`
Commitment []byte `json:"commitment"`
Interval uint64 `json:"interval"`
BabylonEpoch uint64 `json:"babylon_epoch"`
Commitment []byte `json:"commitment"`
}

// EndHeight returns the last height for which randomness actually exists in this commitment.
// For sparse commitments, randomness is generated only at specific intervals, not consecutively.
//
// Example with StartHeight=60, NumPubRand=5, Interval=5:
// - Randomness exists for heights: 60, 65, 70, 75, 80
// - EndHeight() returns 80 (the last height with actual randomness)
// - Heights 61-64, 66-69, 71-74, 76-79, 81+ have NO randomness
//
// The ShouldCommit function is responsible for computing the next eligible start height
// and ensuring no gaps or overlaps by using proper alignment logic.
func (r *RollupPubRandCommit) EndHeight() uint64 {
return r.StartHeight + (r.NumPubRand-1)*r.Interval
}

// Interface implementation
func (r *RollupPubRandCommit) EndHeight() uint64 { return r.StartHeight + r.NumPubRand - 1 }
func (r *RollupPubRandCommit) Validate() error {
if r.NumPubRand < 1 {
return fmt.Errorf("NumPubRand must be >= 1, got %d", r.NumPubRand)
}

return nil
}

Expand Down
Binary file modified bsn/rollup/e2e/bytecode/finality.wasm
Binary file not shown.
15 changes: 11 additions & 4 deletions bsn/rollup/e2e/rollup_test_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,16 +502,23 @@ func (ctm *OpL2ConsumerTestManager) getConsumerFpInstance(
fpMetrics := metrics.NewFpMetrics()
poller := service.NewChainPoller(ctm.logger, fpCfg.PollerConfig, ctm.RollupBSNController, fpMetrics)

rndCommitter := service.NewDefaultRandomnessCommitter(
// For E2E tests, use RollupRandomnessCommitter to test our sparse generation
contractConfig, err := ctm.RollupBSNController.QueryContractConfig(context.Background())
require.NoError(t, err)

rndCommitter := service.NewRollupRandomnessCommitter(
service.NewRandomnessCommitterConfig(fpCfg.NumPubRand, int64(fpCfg.TimestampingDelayBlocks), fpCfg.ContextSigningHeight),
service.NewPubRandState(pubRandStore), ctm.RollupBSNController, ctm.ConsumerEOTSClient, ctm.logger, fpMetrics)
service.NewPubRandState(pubRandStore), ctm.RollupBSNController, ctm.ConsumerEOTSClient, ctm.logger, fpMetrics,
contractConfig.FinalitySignatureInterval)

heightDeterminer := service.NewStartHeightDeterminer(ctm.RollupBSNController, fpCfg.PollerConfig, ctm.logger)
finalitySubmitter := service.NewDefaultFinalitySubmitter(ctm.RollupBSNController, ctm.ConsumerEOTSClient, rndCommitter.GetPubRandProofList,

// For E2E tests, use RollupFinalitySubmitter to test sparse randomness generation
finalitySubmitter := service.NewRollupFinalitySubmitter(ctm.RollupBSNController, ctm.ConsumerEOTSClient, rndCommitter.GetPubRandProofList,
service.NewDefaultFinalitySubmitterConfig(fpCfg.MaxSubmissionRetries,
fpCfg.ContextSigningHeight,
fpCfg.SubmissionRetryInterval),
ctm.logger, fpMetrics)
ctm.logger, fpMetrics, contractConfig.FinalitySignatureInterval)

fpInstance, err := service.NewFinalityProviderInstance(
consumerFpPk,
Expand Down
21 changes: 19 additions & 2 deletions bsn/rollup/service/app.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package service

import (
"context"
"fmt"

rollupfpcc "github.com/babylonlabs-io/finality-provider/bsn/rollup/clientcontroller"
Expand Down Expand Up @@ -50,24 +51,40 @@ func NewRollupBSNFinalityProviderAppFromConfig(
return nil, fmt.Errorf("failed to initiate public randomness store: %w", err)
}

rndCommitter := service.NewDefaultRandomnessCommitter(
// For rollup environments, always use RollupRandomnessCommitter
contractConfig, err := consumerCon.QueryContractConfig(context.Background())
if err != nil {
return nil, fmt.Errorf("failed to query contract config: %w", err)
}

logger.Info("using RollupRandomnessCommitter for rollup environment",
zap.Uint64("finality_signature_interval", contractConfig.FinalitySignatureInterval))

rndCommitter := service.NewRollupRandomnessCommitter(
service.NewRandomnessCommitterConfig(cfg.Common.NumPubRand, int64(cfg.Common.TimestampingDelayBlocks), cfg.Common.ContextSigningHeight),
service.NewPubRandState(pubRandStore),
consumerCon,
em,
logger,
fpMetrics,
contractConfig.FinalitySignatureInterval,
)

heightDeterminer := service.NewStartHeightDeterminer(consumerCon, cfg.Common.PollerConfig, logger)
finalitySubmitter := service.NewDefaultFinalitySubmitter(consumerCon,

logger.Info("using RollupFinalitySubmitter for rollup environment",
zap.Uint64("finality_signature_interval", contractConfig.FinalitySignatureInterval))

// For rollup environments, use RollupFinalitySubmitter for sparse randomness generation
finalitySubmitter := service.NewRollupFinalitySubmitter(consumerCon,
em,
rndCommitter.GetPubRandProofList,
service.NewDefaultFinalitySubmitterConfig(cfg.Common.MaxSubmissionRetries,
cfg.Common.ContextSigningHeight,
cfg.Common.SubmissionRetryInterval),
logger,
fpMetrics,
contractConfig.FinalitySignatureInterval,
)

fpApp, err := service.NewFinalityProviderApp(cfg.Common, cc, consumerCon, em, poller, rndCommitter, heightDeterminer, finalitySubmitter, fpMetrics, db, logger)
Expand Down
23 changes: 23 additions & 0 deletions eotsmanager/client/rpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,29 @@ func (c *EOTSManagerGRpcClient) CreateRandomnessPairList(uid, chainID []byte, st
return pubRandFieldValList, nil
}

func (c *EOTSManagerGRpcClient) CreateRandomnessPairListWithInterval(uid, chainID []byte, startHeight uint64, num uint32, interval uint64) ([]*btcec.FieldVal, error) {
// For now, implement using existing RPC by calling individual heights
// TODO: Later can add dedicated GRPC method for efficiency
// https://github.com/babylonlabs-io/finality-provider/issues/590
pubRandList := make([]*btcec.FieldVal, 0, num)

for i := uint32(0); i < num; i++ {
height := startHeight + uint64(i)*interval
// We request exactly 1 randomness value per height since we're generating sparse randomness
// for specific heights (startHeight, startHeight+interval, startHeight+2*interval, etc.)
singleList, err := c.CreateRandomnessPairList(uid, chainID, height, 1)
if err != nil {
return nil, fmt.Errorf("failed to create randomness for height %d: %w", height, err)
}
if len(singleList) != 1 {
return nil, fmt.Errorf("expected 1 randomness value, got %d", len(singleList))
}
pubRandList = append(pubRandList, singleList[0])
}

return pubRandList, nil
}

func (c *EOTSManagerGRpcClient) SaveEOTSKeyName(pk *btcec.PublicKey, keyName string) error {
req := &proto.SaveEOTSKeyNameRequest{
KeyName: keyName,
Expand Down
8 changes: 8 additions & 0 deletions eotsmanager/eotsmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ type EOTSManager interface {
// block height
CreateRandomnessPairList(uid []byte, chainID []byte, startHeight uint64, num uint32) ([]*btcec.FieldVal, error)

// CreateRandomnessPairListWithInterval generates a list of Schnorr randomness pairs with intervals
// from startHeight, startHeight+interval, startHeight+2*interval, etc. where num means the number of public randomness
// It fails if the finality provider does not exist or a randomness pair has been created before
// or passPhrase is incorrect
// NOTE: the randomness is deterministically generated based on the EOTS key, chainID and
// block height. This method is used for rollup FPs that only vote on specific intervals.
CreateRandomnessPairListWithInterval(uid []byte, chainID []byte, startHeight uint64, num uint32, interval uint64) ([]*btcec.FieldVal, error)

// SignEOTS signs an EOTS using the private key of the finality provider and the corresponding
// secret randomness of the given chain at the given height
// It fails if the finality provider does not exist or there's no randomness committed to the given height.
Expand Down
22 changes: 22 additions & 0 deletions eotsmanager/localmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,28 @@ func (lm *LocalEOTSManager) CreateRandomnessPairList(fpPk []byte, chainID []byte
return prList, nil
}

// CreateRandomnessPairListWithInterval generates a list of public randomness pairs with a given interval.
// It creates keys for heights starting from startHeight and incrementing by interval for num entries.
// For example, with startHeight=100, num=3, interval=5, it generates keys for heights [100, 105, 110].
func (lm *LocalEOTSManager) CreateRandomnessPairListWithInterval(fpPk []byte, chainID []byte, startHeight uint64, num uint32, interval uint64) ([]*btcec.FieldVal, error) {
prList := make([]*btcec.FieldVal, 0, num)

for i := uint32(0); i < num; i++ {
// KEY DIFFERENCE: height increments by interval, not 1
height := startHeight + uint64(i)*interval // 100, 105, 110, 115...
_, pubRand, err := lm.getRandomnessPair(fpPk, chainID, height)
if err != nil {
return nil, err
}

prList = append(prList, pubRand)
}
lm.metrics.IncrementEotsFpTotalGeneratedRandomnessCounter(hex.EncodeToString(fpPk))
lm.metrics.SetEotsFpLastGeneratedRandomnessHeight(hex.EncodeToString(fpPk), float64(startHeight))

return prList, nil
}

func (lm *LocalEOTSManager) SignEOTS(eotsPk []byte, chainID []byte, msg []byte, height uint64) (*btcec.ModNScalar, error) {
record, found, err := lm.es.GetSignRecord(eotsPk, chainID, height)
if err != nil {
Expand Down
7 changes: 3 additions & 4 deletions finality-provider/service/fp_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,21 @@ import (
"context"
"errors"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"sync"
"time"

"github.com/avast/retry-go/v4"
bbntypes "github.com/babylonlabs-io/babylon/v3/types"
"go.uber.org/atomic"
"go.uber.org/zap"

ccapi "github.com/babylonlabs-io/finality-provider/clientcontroller/api"
"github.com/babylonlabs-io/finality-provider/eotsmanager"
fpcfg "github.com/babylonlabs-io/finality-provider/finality-provider/config"
"github.com/babylonlabs-io/finality-provider/finality-provider/proto"
"github.com/babylonlabs-io/finality-provider/finality-provider/store"
"github.com/babylonlabs-io/finality-provider/metrics"
"github.com/babylonlabs-io/finality-provider/types"
"github.com/btcsuite/btcd/btcec/v2"
"go.uber.org/atomic"
"go.uber.org/zap"
)

type FinalityProviderInstance struct {
Expand Down
11 changes: 11 additions & 0 deletions finality-provider/service/pub_rand_store_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ func (st *PubRandState) addPubRandProofList(
return nil
}

func (st *PubRandState) addPubRandProofListWithInterval(
pk, chainID []byte, startHeight uint64, numPubRand uint64,
proofList []*merkle.Proof, interval uint64,
) error {
if err := st.s.AddPubRandProofListWithInterval(chainID, pk, startHeight, numPubRand, proofList, interval); err != nil {
return fmt.Errorf("failed to add pub rand proof list with interval: %w", err)
}

return nil
}

func (st *PubRandState) getPubRandProof(pk, chainID []byte, height uint64) ([]byte, error) {
proof, err := st.s.GetPubRandProof(chainID, pk, height)
if err != nil {
Expand Down
Loading