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

chore: release v0.11.3 with backport of #91 #93

Merged
merged 3 commits into from
Jan 13, 2025
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
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
Loading