Skip to content

Commit

Permalink
Add Jury sig submission into the main event loop (ethereum-optimism#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
gitferry authored Jul 24, 2023
1 parent d36bc53 commit e6e82f0
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 54 deletions.
23 changes: 19 additions & 4 deletions bbnclient/bbncontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,26 @@ func (bc *BabylonController) QueryHeightWithLastPubRand(btcPubKey *types.BIP340P
return ks[0], nil
}

// QueryShouldSubmitJurySigs queries if there's a list of delegations that the Jury should submit Jury sigs to
// QueryPendingBTCDelegations queries BTC delegations that need a Jury sig
// it is only used when the program is running in Jury mode
// it returns a list of public keys used for delegations
func (bc *BabylonController) QueryShouldSubmitJurySigs(btcPubKey *types.BIP340PubKey) (bool, []*types.BIP340PubKey, error) {
panic("implement me")
func (bc *BabylonController) QueryPendingBTCDelegations() ([]*btcstakingtypes.BTCDelegation, error) {
var delegations []*btcstakingtypes.BTCDelegation

ctx, cancel := getQueryContext(bc.timeout)
defer cancel()

clientCtx := sdkclient.Context{Client: bc.rpcClient.QueryClient.RPCClient}
queryClient := btcstakingtypes.NewQueryClient(clientCtx)

// query all the unsigned delegations
queryRequest := &btcstakingtypes.QueryPendingBTCDelegationsRequest{}
res, err := queryClient.PendingBTCDelegations(ctx, queryRequest)
if err != nil {
return nil, fmt.Errorf("failed to query BTC delegations")
}
delegations = append(delegations, res.BtcDelegations...)

return delegations, nil
}

// QueryShouldValidatorVote asks Babylon if the validator should submit a finality sig for the given block height
Expand Down
5 changes: 2 additions & 3 deletions bbnclient/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ type BabylonClient interface {
// Note: the following queries are only for PoC
// QueryHeightWithLastPubRand queries the height of the last block with public randomness
QueryHeightWithLastPubRand(btcPubKey *types.BIP340PubKey) (uint64, error)
// QueryShouldSubmitJurySigs queries if there's a list of delegations that the Jury should submit Jury sigs to
// QueryShouldSubmitJurySigs queries BTC delegations that need a Jury signature
// it is only used when the program is running in Jury mode
// it returns a list of public keys used for delegations
QueryShouldSubmitJurySigs(btcPubKey *types.BIP340PubKey) (bool, []*types.BIP340PubKey, error)
QueryPendingBTCDelegations() ([]*btcstakingtypes.BTCDelegation, error)
// QueryShouldValidatorVote asks Babylon if the validator should submit a finality sig for the given block height
QueryShouldValidatorVote(btcPubKey *types.BIP340PubKey, blockHeight uint64) (bool, error)

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/stretchr/testify v1.8.4
github.com/urfave/cli v1.22.14
go.etcd.io/bbolt v1.3.7
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
google.golang.org/grpc v1.56.1
google.golang.org/protobuf v1.31.0
)
Expand Down Expand Up @@ -257,7 +258,6 @@ require (
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
Expand Down Expand Up @@ -296,7 +296,7 @@ require (
)

replace (
github.com/babylonchain/babylon => github.com/babylonchain/babylon-private v0.0.0-20230722140238-aaa0aad2ae01
github.com/babylonchain/babylon => github.com/babylonchain/babylon-private v0.0.0-20230724085949-744ada452cc6
github.com/cosmos/ibc-go/v7 => github.com/babylonchain/ibc-go/v7 v7.0.0-20230324085744-4d6a0d2c0fcf
github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@ github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX
github.com/aws/aws-sdk-go v1.44.203 h1:pcsP805b9acL3wUqa4JR2vg1k2wnItkDYNvfmcy6F+U=
github.com/aws/aws-sdk-go v1.44.203/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/babylonchain/babylon-private v0.0.0-20230722140238-aaa0aad2ae01 h1:uQtvrEjoeVtkiehFxHhgXzoEH3m4hc0to7NitVo9anQ=
github.com/babylonchain/babylon-private v0.0.0-20230722140238-aaa0aad2ae01/go.mod h1:xH1wcwOFLiu5nCZeaVDnG82ALnNEId9A/GBJeDdRckg=
github.com/babylonchain/babylon-private v0.0.0-20230724085949-744ada452cc6 h1:FioqR0PGxhpkRJmCvJePm/A/Z5MisaKcYj4Tjf2imgw=
github.com/babylonchain/babylon-private v0.0.0-20230724085949-744ada452cc6/go.mod h1:xH1wcwOFLiu5nCZeaVDnG82ALnNEId9A/GBJeDdRckg=
github.com/babylonchain/ibc-go/v7 v7.0.0-20230324085744-4d6a0d2c0fcf h1:NJU3YuruPqV8w6/y45Zsb8FudcWSkTBugdpTT7kJmjw=
github.com/babylonchain/ibc-go/v7 v7.0.0-20230324085744-4d6a0d2c0fcf/go.mod h1:BFh8nKWjr5zeR2OZfhkzdgDzj1+KjRn3aJLpwapStj8=
github.com/babylonchain/rpc-client v0.7.0 h1:koA7evcI39BjCgq+fu9MAIs+TQYCnEg56iPeL78Mihw=
Expand Down
143 changes: 134 additions & 9 deletions service/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
btcstakingtypes "github.com/babylonchain/babylon/x/btcstaking/types"
ftypes "github.com/babylonchain/babylon/x/finality/types"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -40,9 +39,11 @@ type ValidatorApp struct {
createValidatorRequestChan chan *createValidatorRequest
registerValidatorRequestChan chan *registerValidatorRequest
commitPubRandRequestChan chan *commitPubRandRequest
addJurySigRequestChan chan *addJurySigRequest

validatorRegisteredEventChan chan *validatorRegisteredEvent
pubRandCommittedEventChan chan *pubRandCommittedEvent
jurySigAddedEventChan chan *jurySigAddedEvent
}

func NewValidatorAppFromConfig(
Expand Down Expand Up @@ -83,8 +84,10 @@ func NewValidatorAppFromConfig(
createValidatorRequestChan: make(chan *createValidatorRequest),
registerValidatorRequestChan: make(chan *registerValidatorRequest),
commitPubRandRequestChan: make(chan *commitPubRandRequest),
addJurySigRequestChan: make(chan *addJurySigRequest),
pubRandCommittedEventChan: make(chan *pubRandCommittedEvent),
validatorRegisteredEventChan: make(chan *validatorRegisteredEvent),
jurySigAddedEventChan: make(chan *jurySigAddedEvent),
}, nil
}

Expand Down Expand Up @@ -143,6 +146,25 @@ type pubRandCommittedEvent struct {
successResponse chan *commitPubRandResponse
}

type addJurySigRequest struct {
bbnPubKey *secp256k1.PubKey
valBtcPk *types.BIP340PubKey
delBtcPk *types.BIP340PubKey
sig *types.BIP340Signature
errResponse chan error
successResponse chan *addJurySigResponse
}

type addJurySigResponse struct {
txHash []byte
}

type jurySigAddedEvent struct {
bbnPubKey *secp256k1.PubKey
txHash []byte
successResponse chan *addJurySigResponse
}

type CreateValidatorResult struct {
BtcValidatorPk btcec.PublicKey
BabylonValidatorPk secp256k1.PubKey
Expand Down Expand Up @@ -245,13 +267,31 @@ func (app *ValidatorApp) AddJurySignature(btcDel *bstypes.BTCDelegation) ([]byte
stakingMsgTx,
stakingTx.StakingScript,
juryPrivKey,
&chaincfg.SimNetParams,
&app.config.JuryModeConfig.ActiveNetParams,
)
if err != nil {
return nil, err
}

return app.bc.SubmitJurySig(btcDel.ValBtcPk, btcDel.BtcPk, jurySig)
request := &addJurySigRequest{
bbnPubKey: btcDel.BabylonPk,
valBtcPk: btcDel.ValBtcPk,
delBtcPk: btcDel.BtcPk,
sig: jurySig,
errResponse: make(chan error),
successResponse: make(chan *addJurySigResponse),
}

app.addJurySigRequestChan <- request

select {
case err := <-request.errResponse:
return nil, err
case successResponse := <-request.successResponse:
return successResponse.txHash, nil
case <-app.quit:
return nil, fmt.Errorf("validator app is shutting down")
}
}

func (app *ValidatorApp) getJuryPrivKey() (*btcec.PrivateKey, error) {
Expand Down Expand Up @@ -392,14 +432,14 @@ func (app *ValidatorApp) Start() error {
return
}

app.wg.Add(2)
app.wg.Add(3)
go app.handleSentToBabylonLoop()
go app.eventLoop()
if !app.config.JuryMode {
app.wg.Add(1)
if !app.IsJury() {
go app.validatorSubmissionLoop()
} else {
go app.jurySigSubmissionLoop()
}
// TODO add another loop in which the app asks Babylon whether there are any delegations need jury sig if the program is running in Jury mode
})

return startErr
Expand Down Expand Up @@ -442,6 +482,10 @@ func (app *ValidatorApp) CreateValidator(keyName string) (*CreateValidatorResult
}
}

func (app *ValidatorApp) IsJury() bool {
return app.config.JuryMode
}

func (app *ValidatorApp) ListValidators() ([]*proto.Validator, error) {
return app.vs.ListValidators()
}
Expand Down Expand Up @@ -484,7 +528,7 @@ func (app *ValidatorApp) handleCreateValidatorRequest(req *createValidatorReques
babylonPubKey := validator.GetBabylonPK()

app.logger.Info("successfully created validator")
app.logger.WithFields(logrus.Fields{ // TODO: use hex format
app.logger.WithFields(logrus.Fields{
"btc_pub_key": hex.EncodeToString(btcPubKey.SerializeCompressed()),
"babylon_pub_key": hex.EncodeToString(babylonPubKey.Key),
}).Debug("created validator")
Expand All @@ -495,13 +539,63 @@ func (app *ValidatorApp) handleCreateValidatorRequest(req *createValidatorReques
}, nil
}

func (app *ValidatorApp) getPendingDelegationsForAll() ([]*btcstakingtypes.BTCDelegation, error) {
var delegations []*btcstakingtypes.BTCDelegation

dels, err := app.bc.QueryPendingBTCDelegations()
if err != nil {
return nil, fmt.Errorf("failed to get pending BTC delegations: %w", err)
}
delegations = append(delegations, dels...)

return delegations, nil
}

// jurySigSubmissionLoop is the reactor to submit Jury signature for pending BTC delegations
func (app *ValidatorApp) jurySigSubmissionLoop() {
defer app.wg.Done()

interval := app.config.JuryModeConfig.QueryInterval
jurySigTicker := time.NewTicker(interval)

for {
select {
case <-jurySigTicker.C:
dels, err := app.getPendingDelegationsForAll()
if err != nil {
app.logger.WithFields(logrus.Fields{
"err": err,
}).Error("failed to get pending delegations")
continue
}

for _, d := range dels {
_, err := app.AddJurySignature(d)
if err != nil {
app.logger.WithFields(logrus.Fields{
"err": err,
"del_btc_pk": d.BtcPk,
}).Error("failed to submit Jury sig to the Bitcoin delegation")
}
}

case <-app.quit:
return
}
}

}

// validatorSubmissionLoop is the reactor to submit finality signature and public randomness
func (app *ValidatorApp) validatorSubmissionLoop() {
defer app.wg.Done()

commitRandTicker := time.NewTicker(app.config.RandomnessCommitInterval)

for {
select {
case <-app.poller.GetBlockInfoChan():
// TODO finality sig submission
case <-commitRandTicker.C:
lastBlock, err := app.GetCurrentBbnBlock()
if err != nil {
Expand Down Expand Up @@ -611,6 +705,15 @@ func (app *ValidatorApp) eventLoop() {
ev.successResponse <- &commitPubRandResponse{
txHash: ev.txHash,
}
case ev := <-app.jurySigAddedEventChan:
// TODO do we assume the delegator is also a BTC validator?
// if so, do we want to change its status to ACTIVE here?
// if not, maybe we can remove the handler of this event

// return to the caller
ev.successResponse <- &addJurySigResponse{
txHash: ev.txHash,
}

case <-app.quit:
return
Expand Down Expand Up @@ -665,7 +768,7 @@ func (app *ValidatorApp) handleSentToBabylonLoop() {
"err": err,
"btcPubKey": req.valBtcPk,
"startHeight": req.startingHeight,
})
}).Error("failed to commit public randomness")
req.errResponse <- err
continue
}
Expand All @@ -683,6 +786,28 @@ func (app *ValidatorApp) handleSentToBabylonLoop() {
// the commit
successResponse: req.successResponse,
}
case req := <-app.addJurySigRequestChan:
// TODO: we should add some retry mechanism or we can have a health checker to check the connection periodically
tx, err := app.bc.SubmitJurySig(req.valBtcPk, req.delBtcPk, req.sig)
if err != nil {
app.logger.WithFields(logrus.Fields{
"err": err,
"valBtcPubKey": req.valBtcPk,
"delBtcPubKey": req.delBtcPk,
}).Error("failed to submit Jury signature")
req.errResponse <- err
continue
}

app.logger.WithField("delBtcPk", req.delBtcPk).Info("successfully submit Jury sig over Bitcoin delegation to Babylon")

app.jurySigAddedEventChan <- &jurySigAddedEvent{
bbnPubKey: req.bbnPubKey,
txHash: tx,
// pass the channel to the event so that we can send the response to the user which requested
// the registration
successResponse: req.successResponse,
}

case <-app.quit:
return
Expand Down
35 changes: 16 additions & 19 deletions service/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ func FuzzAddJurySig(f *testing.F) {
app, err := service.NewValidatorAppFromConfig(&cfg, logrus.New(), mockBabylonClient)
require.NoError(t, err)

err = app.Start()
require.NoError(t, err)
defer func() {
err = app.Stop()
require.NoError(t, err)
}()

// create a validator object and save it to db
keyName := testutil.GenRandomHexStr(r, 4)
kc, err := val.NewKeyringControllerWithKeyring(app.GetKeyring(), keyName)
Expand All @@ -233,14 +240,12 @@ func FuzzAddJurySig(f *testing.F) {
require.NoError(t, err)

// create a Jury key pair in the keyring
juryKeyName := testutil.GenRandomHexStr(r, 4)
juryKc, err := val.NewKeyringControllerWithKeyring(app.GetKeyring(), juryKeyName)
juryKc, err := val.NewKeyringControllerWithKeyring(app.GetKeyring(), cfg.JuryModeConfig.JuryKeyName)
require.NoError(t, err)
jurPk, err := juryKc.CreateJuryKey()
require.NoError(t, err)
require.NotNil(t, jurPk)
cfg.JuryMode = true
cfg.JuryModeConfig.JuryKeyName = juryKeyName

// generate BTC delegation
slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams)
Expand All @@ -251,31 +256,23 @@ func FuzzAddJurySig(f *testing.F) {
stakingValue := int64(2 * 10e8)
stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, delSK, btcPk, jurPk, stakingTimeBlocks, stakingValue, slashingAddr)
require.NoError(t, err)
stakingMsgTx, err := stakingTx.ToMsgTx()
require.NoError(t, err)
// random Babylon SK
delBabylonSK, delBabylonPK, err := datagen.GenRandomSecp256k1KeyPair(r)
require.NoError(t, err)
pop, err := bstypes.NewPoP(delBabylonSK, delSK)
require.NoError(t, err)
delegatorSig, err := slashingTx.Sign(
stakingMsgTx,
stakingTx.StakingScript,
delSK,
&chaincfg.SimNetParams,
)
require.NoError(t, err)
delegation := &bstypes.BTCDelegation{
ValBtcPk: btcPkBIP340,
BtcPk: types.NewBIP340PubKeyFromBTCPK(delPK),
BabylonPk: delBabylonPK.(*secp256k1.PubKey),
Pop: pop,
StakingTx: stakingTx,
SlashingTx: slashingTx,
DelegatorSig: delegatorSig,
ValBtcPk: btcPkBIP340,
BtcPk: types.NewBIP340PubKeyFromBTCPK(delPK),
BabylonPk: delBabylonPK.(*secp256k1.PubKey),
Pop: pop,
StakingTx: stakingTx,
SlashingTx: slashingTx,
}

expectedTxHash := testutil.GenRandomByteArray(r, 32)
mockBabylonClient.EXPECT().QueryPendingBTCDelegations().
Return([]*bstypes.BTCDelegation{delegation}, nil).AnyTimes()
mockBabylonClient.EXPECT().SubmitJurySig(delegation.ValBtcPk, delegation.BtcPk, gomock.Any()).
Return(expectedTxHash, nil).AnyTimes()
txHash, err := app.AddJurySignature(delegation)
Expand Down
Loading

0 comments on commit e6e82f0

Please sign in to comment.