Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add check if the covenant is the covenant committee #91

Merged
merged 14 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 12 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

### Improvements

* [#91](https://github.com/babylonlabs-io/covenant-emulator/pull/91) Add verification
if the covenant public key was present in the params of the versioned BTC delegation
* [#83](https://github.com/babylonlabs-io/covenant-emulator/pull/83) covenant-signer: remove go.mod


## v0.11.2

### Bug fixes
Expand Down
76 changes: 76 additions & 0 deletions covenant/cache_params.go
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package covenant

import (
"sync"

"github.com/avast/retry-go/v4"
"github.com/babylonlabs-io/covenant-emulator/clientcontroller"
"github.com/babylonlabs-io/covenant-emulator/types"
"go.uber.org/zap"
)

type (
ParamsGetter interface {
Get(version uint32) (*types.StakingParams, error)
}
CacheVersionedParams struct {
sync.Mutex
paramsByVersion map[uint32]*types.StakingParams

cc clientcontroller.ClientController
logger *zap.Logger
}
)

func NewCacheVersionedParams(cc clientcontroller.ClientController, logger *zap.Logger) ParamsGetter {
return &CacheVersionedParams{
paramsByVersion: make(map[uint32]*types.StakingParams),
cc: cc,
logger: logger,
}
}

// Get returns the staking parameter from the
func (v *CacheVersionedParams) Get(version uint32) (*types.StakingParams, error) {
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
v.Lock()
defer v.Unlock()

params, ok := v.paramsByVersion[version]
if ok {
return params, nil
}

params, err := v.getParamsByVersion(version)
if err != nil {
return nil, err
}

v.paramsByVersion[version] = params
return params, nil
}

func (v *CacheVersionedParams) getParamsByVersion(version uint32) (*types.StakingParams, error) {
var (
err error
params *types.StakingParams
)

if err := retry.Do(func() error {
params, err = v.cc.QueryStakingParamsByVersion(version)
if err != nil {
return err
}
return nil
}, RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) {
v.logger.Debug(
"failed to query the consumer chain for the staking params",
zap.Uint("attempt", n+1),
zap.Uint("max_attempts", RtyAttNum),
zap.Error(err),
)
})); err != nil {
return nil, err
}

return params, nil
}
130 changes: 78 additions & 52 deletions covenant/covenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type CovenantEmulator struct {

config *covcfg.Config
logger *zap.Logger

paramCache ParamsGetter
}

func NewCovenantEmulator(
Expand All @@ -60,12 +62,13 @@ func NewCovenantEmulator(
}

return &CovenantEmulator{
cc: cc,
signer: signer,
config: config,
logger: logger,
pk: pk,
quit: make(chan struct{}),
cc: cc,
signer: signer,
config: config,
logger: logger,
pk: pk,
quit: make(chan struct{}),
paramCache: NewCacheVersionedParams(cc, logger),
}, nil
}

Expand Down Expand Up @@ -100,7 +103,7 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) (
}

// 1. get the params matched to the delegation version
params, err := ce.getParamsByVersionWithRetry(btcDel.ParamsVersion)
params, err := ce.paramCache.Get(btcDel.ParamsVersion)
if err != nil {
return nil, fmt.Errorf("failed to get staking params with version %d: %w", btcDel.ParamsVersion, err)
}
Expand Down Expand Up @@ -436,30 +439,79 @@ func (ce *CovenantEmulator) delegationsToBatches(dels []*types.Delegation) [][]*
return batches
}

func RemoveAlreadySigned(localKey *btcec.PublicKey, dels []*types.Delegation) []*types.Delegation {
sanitized := make([]*types.Delegation, 0, len(dels))
localKeyBytes := schnorr.SerializePubKey(localKey)
// IsKeyInCommittee returns true if the covenant serialized public key is in the covenant committee of the
// parameter in which the BTC delegation was included.
func IsKeyInCommittee(paramCache ParamsGetter, covenantSerializedPk []byte, del *types.Delegation) (bool, error) {
stkParams, err := paramCache.Get(del.ParamsVersion)
if err != nil {
return false, err
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
}

for _, del := range dels {
delCopy := del
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
alreadySigned := false
for _, covSig := range delCopy.CovenantSigs {
remoteKey := schnorr.SerializePubKey(covSig.Pk)
if bytes.Equal(remoteKey, localKeyBytes) {
alreadySigned = true
break
}
for _, pk := range stkParams.CovenantPks {
remoteKey := schnorr.SerializePubKey(pk)
if !bytes.Equal(remoteKey, covenantSerializedPk) {
continue
}
if !alreadySigned {
sanitized = append(sanitized, delCopy)
return true, nil
}

return false, fmt.Errorf("serialized pub key is not in the list of covenants for the param version: %d", del.ParamsVersion)
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
}

// CovenantAlreadySigned returns true if the covenant already signed the BTC Delegation
func CovenantAlreadySigned(covenantSerializedPk []byte, del *types.Delegation) bool {
for _, covSig := range del.CovenantSigs {
remoteKey := schnorr.SerializePubKey(covSig.Pk)
if !bytes.Equal(remoteKey, covenantSerializedPk) {
continue
}
return true
}
return sanitized

return false
}

// sanitizeDelegations removes any delegations that have already been signed by the covenant and
// remove delegations that were not constructed with this covenant public key
func (ce *CovenantEmulator) sanitizeDelegations(dels []*types.Delegation) []*types.Delegation {
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
return SanitizeDelegations(ce.logger, ce.pk, ce.paramCache, dels)
}

// removeAlreadySigned removes any delegations that have already been signed by the covenant
func (ce *CovenantEmulator) removeAlreadySigned(dels []*types.Delegation) []*types.Delegation {
return RemoveAlreadySigned(ce.pk, dels)
// SanitizeDelegations remove the delegations in which the covenant public key already signed
// or the delegation was not constructed with that covenant public key
func SanitizeDelegations(
logger *zap.Logger,
pk *btcec.PublicKey,
paramCache ParamsGetter,
dels []*types.Delegation,
) []*types.Delegation {
covenantSerializedPk := schnorr.SerializePubKey(pk)

sanitized := make([]*types.Delegation, 0, len(dels))
for _, del := range dels {
// 1. Remove delegations that do not need the covenant's signature because
// this covenant already signed
if CovenantAlreadySigned(covenantSerializedPk, del) {
continue
}
// 2. Remove delegations that were not constructed with this covenant public key
isInCommittee, err := IsKeyInCommittee(paramCache, covenantSerializedPk, del)
if err != nil {
logger.Error(
"invalid delegation",
zap.String("staker_pk", hex.EncodeToString(covenantSerializedPk)),
zap.String("staking_tx_hex", del.StakingTxHex),
zap.String("reason", "covenant key is not in committee"),
zap.Error(err),
)
}
if !isInCommittee {
continue
}
sanitized = append(sanitized, del)
}

return sanitized
}

// covenantSigSubmissionLoop is the reactor to submit Covenant signature for BTC delegations
Expand Down Expand Up @@ -490,7 +542,7 @@ func (ce *CovenantEmulator) covenantSigSubmissionLoop() {
ce.logger.Debug("no pending delegations are found")
}
// 2. Remove delegations that do not need the covenant's signature
sanitizedDels := ce.removeAlreadySigned(dels)
sanitizedDels := ce.sanitizeDelegations(dels)

// 3. Split delegations into batches for submission
batches := ce.delegationsToBatches(sanitizedDels)
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -532,32 +584,6 @@ func (ce *CovenantEmulator) metricsUpdateLoop() {
}
}

func (ce *CovenantEmulator) getParamsByVersionWithRetry(version uint32) (*types.StakingParams, error) {
var (
params *types.StakingParams
err error
)

if err := retry.Do(func() error {
params, err = ce.cc.QueryStakingParamsByVersion(version)
if err != nil {
return err
}
return nil
}, RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) {
ce.logger.Debug(
"failed to query the consumer chain for the staking params",
zap.Uint("attempt", n+1),
zap.Uint("max_attempts", RtyAttNum),
zap.Error(err),
)
})); err != nil {
return nil, err
}

return params, nil
}

func (ce *CovenantEmulator) recordMetricsFailedSignDelegations(n int) {
failedSignDelegations.WithLabelValues(ce.PublicKeyStr()).Add(float64(n))
}
Expand Down
Loading