diff --git a/Makefile b/Makefile index dd9d0f8..66370d9 100644 --- a/Makefile +++ b/Makefile @@ -46,11 +46,11 @@ $(BUILDDIR)/: mkdir -p $(BUILDDIR)/ test: - go test ./... + go test -race ./... test-e2e: cd $(TOOLS_DIR); go install -trimpath $(BABYLON_PKG); - go test -mod=readonly -timeout=25m -v $(PACKAGES_E2E) -count=1 --tags=e2e + go test -race -mod=readonly -timeout=25m -v $(PACKAGES_E2E) -count=1 --tags=e2e build-docker: $(DOCKER) build --tag babylonlabs-io/vigilante -f Dockerfile \ diff --git a/btcstaking-tracker/btcslasher/slasher.go b/btcstaking-tracker/btcslasher/slasher.go index a68b13e..8c284bf 100644 --- a/btcstaking-tracker/btcslasher/slasher.go +++ b/btcstaking-tracker/btcslasher/slasher.go @@ -3,6 +3,8 @@ package btcslasher import ( "context" "fmt" + "github.com/babylonlabs-io/vigilante/types" + "github.com/decred/dcrd/dcrec/secp256k1/v4" "sync" "time" @@ -233,8 +235,8 @@ func (bs *BTCSlasher) equivocationTracker() { // SlashFinalityProvider slashes all BTC delegations under a given finality provider // the checkBTC option indicates whether to check the slashing tx's input is still spendable // on Bitcoin (including mempool txs). -func (bs *BTCSlasher) SlashFinalityProvider(extractedfpBTCSK *btcec.PrivateKey) error { - fpBTCPK := bbn.NewBIP340PubKeyFromBTCPK(extractedfpBTCSK.PubKey()) +func (bs *BTCSlasher) SlashFinalityProvider(extractedFpBTCSK *btcec.PrivateKey) error { + fpBTCPK := bbn.NewBIP340PubKeyFromBTCPK(extractedFpBTCSK.PubKey()) bs.logger.Infof("start slashing finality provider %s", fpBTCPK.MarshalHex()) // get all active and unbonded BTC delegations at the current BTC height @@ -246,6 +248,9 @@ func (bs *BTCSlasher) SlashFinalityProvider(extractedfpBTCSK *btcec.PrivateKey) return fmt.Errorf("failed to get BTC delegations under finality provider %s: %w", fpBTCPK.MarshalHex(), err) } + // Initialize a mutex protected *btcec.PrivateKey + safeExtractedFpBTCSK := types.NewPrivateKeyWithMutex(extractedFpBTCSK) + // try to slash both staking and unbonding txs for each BTC delegation // sign and submit slashing tx for each active delegation // TODO: use semaphore to prevent spamming BTC node @@ -253,7 +258,9 @@ func (bs *BTCSlasher) SlashFinalityProvider(extractedfpBTCSK *btcec.PrivateKey) bs.wg.Add(1) go func(d *bstypes.BTCDelegationResponse) { defer bs.wg.Done() - bs.slashBTCDelegation(fpBTCPK, extractedfpBTCSK, d) + safeExtractedFpBTCSK.UseKey(func(key *secp256k1.PrivateKey) { + bs.slashBTCDelegation(fpBTCPK, key, d) + }) }(del) } // sign and submit slashing tx for each unbonded delegation @@ -262,7 +269,9 @@ func (bs *BTCSlasher) SlashFinalityProvider(extractedfpBTCSK *btcec.PrivateKey) bs.wg.Add(1) go func(d *bstypes.BTCDelegationResponse) { defer bs.wg.Done() - bs.slashBTCDelegation(fpBTCPK, extractedfpBTCSK, d) + safeExtractedFpBTCSK.UseKey(func(key *secp256k1.PrivateKey) { + bs.slashBTCDelegation(fpBTCPK, key, d) + }) }(del) } diff --git a/btcstaking-tracker/btcslasher/slasher_utils.go b/btcstaking-tracker/btcslasher/slasher_utils.go index e8a3882..a192bef 100644 --- a/btcstaking-tracker/btcslasher/slasher_utils.go +++ b/btcstaking-tracker/btcslasher/slasher_utils.go @@ -288,6 +288,12 @@ func findFPIdxInWitness(fpBTCPK *bbn.BIP340PubKey, fpBtcPkList []bbn.BIP340PubKe return 0, fmt.Errorf("the given finality provider's PK is not found in the BTC delegation") } +// BuildSlashingTxWithWitness constructs a Bitcoin slashing transaction with the required witness data +// using the provided finality provider's private key. It handles the conversion and validation of +// various parameters needed for slashing a Bitcoin delegation, including the staking transaction, +// finality provider public keys, and covenant public keys. +// Note: this function is UNSAFE for concurrent accesses as slashTx.BuildSlashingTxWithWitness is not safe for +// concurrent access inside it's calling asig.NewDecyptionKeyFromBTCSK func BuildSlashingTxWithWitness( d *bstypes.BTCDelegationResponse, bsParams *bstypes.Params, diff --git a/monitor/btcscanner/btc_scanner.go b/monitor/btcscanner/btc_scanner.go index 651d1d3..7145989 100644 --- a/monitor/btcscanner/btc_scanner.go +++ b/monitor/btcscanner/btc_scanner.go @@ -28,6 +28,7 @@ type Scanner interface { } type BtcScanner struct { + mu sync.Mutex logger *zap.SugaredLogger // connect to BTC node @@ -94,7 +95,7 @@ func (bs *BtcScanner) Start(startHeight uint64) { return } - bs.BaseHeight = startHeight + bs.SetBaseHeight(startHeight) bs.Started.Store(true) bs.logger.Info("the BTC scanner is started") @@ -143,7 +144,7 @@ func (bs *BtcScanner) Bootstrap() { if bs.confirmedTipBlock != nil { firstUnconfirmedHeight = uint64(bs.confirmedTipBlock.Height + 1) } else { - firstUnconfirmedHeight = bs.BaseHeight + firstUnconfirmedHeight = bs.GetBaseHeight() } bs.logger.Infof("the bootstrapping starts at %d", firstUnconfirmedHeight) @@ -283,5 +284,13 @@ func (bs *BtcScanner) Stop() { } func (bs *BtcScanner) GetBaseHeight() uint64 { + bs.mu.Lock() + defer bs.mu.Unlock() return bs.BaseHeight } + +func (bs *BtcScanner) SetBaseHeight(h uint64) { + bs.mu.Lock() + defer bs.mu.Unlock() + bs.BaseHeight = h +} diff --git a/types/safeprivatekey.go b/types/safeprivatekey.go new file mode 100644 index 0000000..aae4222 --- /dev/null +++ b/types/safeprivatekey.go @@ -0,0 +1,33 @@ +package types + +import ( + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "sync" +) + +// PrivateKeyWithMutex wraps a btcec.PrivateKey with a mutex to ensure thread-safe access. +type PrivateKeyWithMutex struct { + mu sync.Mutex + key *secp256k1.PrivateKey +} + +// NewPrivateKeyWithMutex creates a new PrivateKeyWithMutex. +func NewPrivateKeyWithMutex(key *secp256k1.PrivateKey) *PrivateKeyWithMutex { + return &PrivateKeyWithMutex{ + key: key, + } +} + +// GetKey safely retrieves the private key. +func (p *PrivateKeyWithMutex) GetKey() *secp256k1.PrivateKey { + p.mu.Lock() + defer p.mu.Unlock() + return p.key +} + +// UseKey performs an operation with the private key in a thread-safe manner. +func (p *PrivateKeyWithMutex) UseKey(operation func(key *secp256k1.PrivateKey)) { + p.mu.Lock() + defer p.mu.Unlock() + operation(p.key) +}