Skip to content

Commit

Permalink
chore: release v0.11.3 with backport of #91 (#93)
Browse files Browse the repository at this point in the history
* feat: add check if the covenant is the covenant committee (#91)

* fix: add check if the covenant is the covenant committee of that btc delegation based on the params version

* chore: refactory sanitize delegations

* chore: add #91 to changelog

* chore: add godoc to funcs

* chore: add client controller and logger directly to param cache

* chore: refactory to remove delegations in a single loop over dels

* chore: add check for del copy

* chore: removed delCopy

* fix: test check endheight modified

* chore: add test that verifies if all the delegations pointers wasn't pointing to the last one

* chore: return error in func IsKeyInCommittee and log if there is a error

* chore: stop the loop if there is an error in sanitize delegations

* chore: address pr comments, transform err from %s to %w and add check if sanitized dels returns some delegation

* chore: changelog release v0.11.3 with #91 PR
  • Loading branch information
RafilxTenfen authored Jan 13, 2025
1 parent 16098ba commit 5d5ad1f
Show file tree
Hide file tree
Showing 4 changed files with 311 additions and 54 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## Unreleased

## v0.11.3

### Bug fixes

* [#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

## v0.11.2

### Bug fixes
Expand Down
76 changes: 76 additions & 0 deletions covenant/cache_params.go
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) {
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
}
143 changes: 90 additions & 53 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,72 @@ 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, fmt.Errorf("unable to get the param version: %d, reason: %w", del.ParamsVersion, err)
}

for _, del := range dels {
delCopy := del
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, nil
}

// 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
}

// 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 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, error) {
return SanitizeDelegations(ce.pk, ce.paramCache, 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(
pk *btcec.PublicKey,
paramCache ParamsGetter,
dels []*types.Delegation,
) ([]*types.Delegation, error) {
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 {
return nil, fmt.Errorf("unable to verify if covenant key is in committee: %w", err)
}
if !isInCommittee {
continue
}
sanitized = append(sanitized, del)
}

return sanitized, nil
}

// covenantSigSubmissionLoop is the reactor to submit Covenant signature for BTC delegations
Expand All @@ -483,14 +528,32 @@ func (ce *CovenantEmulator) covenantSigSubmissionLoop() {
continue
}

pendingDels := len(dels)
// record delegation metrics
ce.recordMetricsCurrentPendingDelegations(len(dels))
ce.recordMetricsCurrentPendingDelegations(pendingDels)

if len(dels) == 0 {
ce.logger.Debug("no pending delegations are found")
continue
}

// 2. Remove delegations that do not need the covenant's signature
sanitizedDels := ce.removeAlreadySigned(dels)
sanitizedDels, err := ce.sanitizeDelegations(dels)
if err != nil {
ce.logger.Error(
"error sanitizing delegations",
zap.Error(err),
)
continue
}

if len(sanitizedDels) == 0 {
ce.logger.Debug(
"no new delegations to sign",
zap.Int("pending_dels_len", pendingDels),
)
continue
}

// 3. Split delegations into batches for submission
batches := ce.delegationsToBatches(sanitizedDels)
Expand Down Expand Up @@ -532,32 +595,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

0 comments on commit 5d5ad1f

Please sign in to comment.