Skip to content

Commit

Permalink
feature: do not use inputs already on babyon (#67)
Browse files Browse the repository at this point in the history
* Add used inputs tracking

* Add filtering of utxos used in transaction building
  • Loading branch information
KonradStaniec authored Oct 16, 2024
1 parent 16c8dd0 commit 7dd2055
Show file tree
Hide file tree
Showing 8 changed files with 437 additions and 106 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* [#65](https://github.com/babylonlabs-io/btc-staker/pull/65) Various fixes to
pre-approval flow. Do not send signed staking transactions to Babylon.

* [#67](https://github.com/babylonlabs-io/btc-staker/pull/67) Enable concurrent
sending of multiple pre-approval staking transactions

## v0.7.2

### Bug fix
Expand Down
84 changes: 75 additions & 9 deletions itest/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ func (tm *TestManager) FinalizeUntilEpoch(t *testing.T, epoch uint64) {
},
2000,
tm.MinerAddr,
nil,
)
require.NoError(t, err)
_, err = tm.Sa.Wallet().SendRawTransaction(tx1, true)
Expand All @@ -557,6 +558,7 @@ func (tm *TestManager) FinalizeUntilEpoch(t *testing.T, epoch uint64) {
},
2000,
tm.MinerAddr,
nil,
)
require.NoError(t, err)
_, err = tm.Sa.Wallet().SendRawTransaction(tx2, true)
Expand Down Expand Up @@ -740,7 +742,7 @@ func (tm *TestManager) sendStakingTxBTC(
return hashFromString
}

func (tm *TestManager) sendMultipleStakingTx(t *testing.T, testStakingData []*testStakingData) []*chainhash.Hash {
func (tm *TestManager) sendMultipleStakingTx(t *testing.T, testStakingData []*testStakingData, sendToBabylonFirst bool) []*chainhash.Hash {
var hashes []*chainhash.Hash
for _, data := range testStakingData {
fpBTCPKs := []string{}
Expand All @@ -754,7 +756,7 @@ func (tm *TestManager) sendMultipleStakingTx(t *testing.T, testStakingData []*te
data.StakingAmount,
fpBTCPKs,
int64(data.StakingTime),
false,
sendToBabylonFirst,
)
require.NoError(t, err)
txHash, err := chainhash.NewHashFromStr(res.TxHash)
Expand All @@ -768,14 +770,21 @@ func (tm *TestManager) sendMultipleStakingTx(t *testing.T, testStakingData []*te
stakingDetails, err := tm.StakerClient.StakingDetails(context.Background(), hashStr)
require.NoError(t, err)
require.Equal(t, stakingDetails.StakingTxHash, hashStr)
require.Equal(t, stakingDetails.StakingState, proto.TransactionState_SENT_TO_BTC.String())

if sendToBabylonFirst {
require.Equal(t, stakingDetails.StakingState, proto.TransactionState_SENT_TO_BABYLON.String())
} else {
require.Equal(t, stakingDetails.StakingState, proto.TransactionState_SENT_TO_BTC.String())
}
}

mBlock := tm.mineBlock(t)
require.Equal(t, len(hashes)+1, len(mBlock.Transactions))
if !sendToBabylonFirst {
mBlock := tm.mineBlock(t)
require.Equal(t, len(hashes)+1, len(mBlock.Transactions))

_, err := tm.BabylonClient.InsertBtcBlockHeaders([]*wire.BlockHeader{&mBlock.Header})
require.NoError(t, err)
_, err := tm.BabylonClient.InsertBtcBlockHeaders([]*wire.BlockHeader{&mBlock.Header})
require.NoError(t, err)
}
return hashes
}

Expand Down Expand Up @@ -804,6 +813,7 @@ func (tm *TestManager) sendWatchedStakingTx(
[]*wire.TxOut{stakingInfo.StakingOutput},
2000,
tm.MinerAddr,
nil,
)
require.NoError(t, err)
txHash := tx.TxHash()
Expand Down Expand Up @@ -1346,7 +1356,7 @@ func TestMultipleWithdrawableStakingTransactions(t *testing.T) {
testStakingData3,
testStakingData4,
testStakingData5,
})
}, false)

go tm.mineNEmptyBlocks(t, params.ConfirmationTimeBlocks, true)

Expand Down Expand Up @@ -1380,6 +1390,61 @@ func TestMultipleWithdrawableStakingTransactions(t *testing.T) {
require.Equal(t, withdrawableTransactionsResp.Transactions[2].TransactionIdx, "4")
}

func TestMultiplePreApprovalTransactions(t *testing.T) {
t.Parallel()
// need to have at least 300 block on testnet as only then segwit is activated.
// Mature output is out which has 100 confirmations, which means 200mature outputs
// will generate 300 blocks
numMatureOutputs := uint32(200)
ctx, cancel := context.WithCancel(context.Background())
tm := StartManager(t, ctx, numMatureOutputs)
defer tm.Stop(t, cancel)
tm.insertAllMinedBlocksToBabylon(t)

cl := tm.Sa.BabylonController()
params, err := cl.Params()
require.NoError(t, err)
minStakingTime := params.MinStakingTime
stakingTime1 := minStakingTime
stakingTime2 := minStakingTime + 4
stakingTime3 := minStakingTime + 1

testStakingData1 := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime1, 10000, 1)
testStakingData2 := testStakingData1.withStakingTime(stakingTime2)
testStakingData3 := testStakingData1.withStakingTime(stakingTime3)

tm.createAndRegisterFinalityProviders(t, testStakingData1)
txHashes := tm.sendMultipleStakingTx(t, []*testStakingData{
testStakingData1,
testStakingData2,
testStakingData3,
}, true)

for _, txHash := range txHashes {
txHash := txHash
tm.waitForStakingTxState(t, txHash, proto.TransactionState_SENT_TO_BABYLON)
}

pend, err := tm.BabylonClient.QueryPendingBTCDelegations()
require.NoError(t, err)
require.Len(t, pend, 3)
tm.insertCovenantSigForDelegation(t, pend[0])
tm.insertCovenantSigForDelegation(t, pend[1])
tm.insertCovenantSigForDelegation(t, pend[2])

for _, txHash := range txHashes {
txHash := txHash
tm.waitForStakingTxState(t, txHash, proto.TransactionState_VERIFIED)
}

// Ultimately we will get 3 tx in the mempool meaning all staking transactions
// use valid inputs
require.Eventually(t, func() bool {
txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcClient, txHashes)
return len(txFromMempool) == 3
}, eventuallyWaitTimeOut, eventuallyPollTime)
}

func TestSendingWatchedStakingTransaction(t *testing.T) {
t.Parallel()
// need to have at least 300 block on testnet as only then segwit is activated.
Expand Down Expand Up @@ -1456,7 +1521,7 @@ func TestRestartingTxNotOnBabylon(t *testing.T) {
txHashes := tm.sendMultipleStakingTx(t, []*testStakingData{
testStakingData1,
testStakingData2,
})
}, false)

// Confirm tx on btc
minedBlocks := tm.mineNEmptyBlocks(t, params.ConfirmationTimeBlocks, false)
Expand Down Expand Up @@ -1677,6 +1742,7 @@ func TestBitcoindWalletRpcApi(t *testing.T) {
[]*wire.TxOut{newOutput},
btcutil.Amount(2000),
walletAddress,
nil,
)
require.NoError(t, err)

Expand Down
51 changes: 27 additions & 24 deletions staker/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)

// we can make command to implement StakingEvent interface
var _ StakingEvent = (*stakingRequestCmd)(nil)

type stakingRequestCmd struct {
stakerAddress btcutil.Address
stakingTxHash chainhash.Hash
stakingTx *wire.MsgTx
stakingOutputIdx uint32
stakingOutputPkScript []byte
stakingOutput *wire.TxOut
feeRate chainfee.SatPerKVByte
stakingTime uint16
stakingValue btcutil.Amount
fpBtcPks []*btcec.PublicKey
Expand All @@ -36,9 +35,8 @@ func (req *stakingRequestCmd) isWatched() bool {

func newOwnedStakingCommand(
stakerAddress btcutil.Address,
stakingTx *wire.MsgTx,
stakingOutputIdx uint32,
stakingOutputPkScript []byte,
stakingOutput *wire.TxOut,
feeRate chainfee.SatPerKVByte,
stakingTime uint16,
stakingValue btcutil.Amount,
fpBtcPks []*btcec.PublicKey,
Expand All @@ -48,10 +46,8 @@ func newOwnedStakingCommand(
) *stakingRequestCmd {
return &stakingRequestCmd{
stakerAddress: stakerAddress,
stakingTxHash: stakingTx.TxHash(),
stakingTx: stakingTx,
stakingOutputIdx: stakingOutputIdx,
stakingOutputPkScript: stakingOutputPkScript,
stakingOutput: stakingOutput,
feeRate: feeRate,
stakingTime: stakingTime,
stakingValue: stakingValue,
fpBtcPks: fpBtcPks,
Expand All @@ -65,6 +61,12 @@ func newOwnedStakingCommand(
}

type watchTxDataCmd struct {
// watched tx data
stakingTxHash chainhash.Hash
stakingTx *wire.MsgTx
stakingOutputIdx uint32
stakingOutputPkScript []byte

slashingTx *wire.MsgTx
slashingTxSig *schnorr.Signature
stakerBabylonAddr sdk.AccAddress
Expand Down Expand Up @@ -97,32 +99,33 @@ func newWatchedStakingCmd(
) *stakingRequestCmd {
return &stakingRequestCmd{
stakerAddress: stakerAddress,
stakingTxHash: stakingTx.TxHash(),
stakingTx: stakingTx,
stakingOutputIdx: stakingOutputIdx,
stakingOutputPkScript: stakingOutputPkScript,
stakingTime: stakingTime,
stakingValue: stakingValue,
fpBtcPks: fpBtcPks,
requiredDepthOnBtcChain: confirmationTimeBlocks,
pop: pop,
watchTxData: &watchTxDataCmd{
slashingTx: slashingTx,
slashingTxSig: slashingTxSignature,
stakerBabylonAddr: stakerBabylonAddr,
stakerBtcPk: stakerBtcPk,
unbondingTx: unbondingTx,
slashUnbondingTx: slashUnbondingTx,
slashUnbondingTxSig: slashUnbondingTxSig,
unbondingTime: unbondingTime,
stakingTxHash: stakingTx.TxHash(),
stakingTx: stakingTx,
stakingOutputIdx: stakingOutputIdx,
stakingOutputPkScript: stakingOutputPkScript,
slashingTx: slashingTx,
slashingTxSig: slashingTxSignature,
stakerBabylonAddr: stakerBabylonAddr,
stakerBtcPk: stakerBtcPk,
unbondingTx: unbondingTx,
slashUnbondingTx: slashUnbondingTx,
slashUnbondingTxSig: slashUnbondingTxSig,
unbondingTime: unbondingTime,
},
errChan: make(chan error, 1),
successChan: make(chan *chainhash.Hash, 1),
}
}

func (event *stakingRequestCmd) EventId() chainhash.Hash {
return event.stakingTxHash
// we do not have has for this event
return chainhash.Hash{}
}

func (event *stakingRequestCmd) EventDesc() string {
Expand Down
Loading

0 comments on commit 7dd2055

Please sign in to comment.