From d19d19c284355d4282abcd0a721d1e732da6f50e Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Mon, 3 Mar 2025 13:15:55 +0100 Subject: [PATCH] add sanity check for transactions standards (#146) * add sanity check for transactions standards --- CHANGELOG.md | 1 + staker/babylontypes.go | 12 ++++++++++++ staker/types.go | 6 ++++++ utils/btc_utils.go | 38 ++++++++++++++++++++++++++++++++++++++ walletcontroller/client.go | 9 +++++++++ 5 files changed, 66 insertions(+) create mode 100644 utils/btc_utils.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 700d34e..9bbf12f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ configuration to enabled it (disabled by default). * [#114](https://github.com/babylonlabs-io/btc-staker/pull/114) **Multi-staking support**. This PR contains a series of PRs on multi-staking support and BTC staking integration. * [#134](https://github.com/babylonlabs-io/btc-staker/pull/134) Removal of both the watch-staking endpoint and the post-approval flow, and reduction of state in the database. +* [#146](https://github.com/babylonlabs-io/btc-staker/pull/146) Sanity check that all transactions are standard ## v0.15.2 diff --git a/staker/babylontypes.go b/staker/babylontypes.go index ed194ef..a179e87 100644 --- a/staker/babylontypes.go +++ b/staker/babylontypes.go @@ -133,6 +133,18 @@ func (app *App) buildDelegation( return nil, fmt.Errorf("failed to receive unbondingSlashingSig.Signature ") } + // sanity check that all our transactions are standard + // if they are not this can mean bug either in Babylon parameters or in Staker code + if err := utils.CheckTransaction(stakingSlashingTx); err != nil { + return nil, fmt.Errorf("failed to build delegation data: failed to build staking slashing tx: %w", err) + } + if err := utils.CheckTransaction(undelegationDesc.UnbondingTransaction); err != nil { + return nil, fmt.Errorf("failed to build delegation data: failed to build unbonding tx: %w", err) + } + if err := utils.CheckTransaction(undelegationDesc.SlashUnbondingTransaction); err != nil { + return nil, fmt.Errorf("failed to build delegation data: failed to build unbondingslashing tx: %w", err) + } + dg := createDelegationData( req, externalData.stakerPublicKey, diff --git a/staker/types.go b/staker/types.go index 686c938..a8f174d 100644 --- a/staker/types.go +++ b/staker/types.go @@ -12,6 +12,7 @@ import ( cl "github.com/babylonlabs-io/btc-staker/babylonclient" "github.com/babylonlabs-io/btc-staker/stakerdb" + "github.com/babylonlabs-io/btc-staker/utils" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" @@ -211,6 +212,11 @@ func createSpendStakeTx( return nil, nil, fmt.Errorf("too big fee rate for spend stake tx. calculated fee: %d. funding output value: %d", fee, fundingOutput.Value) } + // sanity check that transaction is standard + if err := utils.CheckTransaction(spendTx); err != nil { + return nil, nil, fmt.Errorf("failed to build spend stake tx: %w", err) + } + return spendTx, &fee, nil } diff --git a/utils/btc_utils.go b/utils/btc_utils.go new file mode 100644 index 0000000..3949dcf --- /dev/null +++ b/utils/btc_utils.go @@ -0,0 +1,38 @@ +package utils + +import ( + "fmt" + + "github.com/btcsuite/btcd/mempool" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +const ( + minTransactionSize = 65 +) + +// Perform subset of transactions standard tests: +// - whether transaction is not considered dust +// - whether transactions is not too small +func CheckTransaction(tx *wire.MsgTx) error { + if tx.SerializeSizeStripped() < minTransactionSize { + return fmt.Errorf("transaction is too small. Tx size: %d, min size: %d", tx.SerializeSizeStripped(), minTransactionSize) + } + + numOpReturns := 0 + for _, txOut := range tx.TxOut { + scriptClass := txscript.GetScriptClass(txOut.PkScript) + if scriptClass == txscript.NullDataTy { + numOpReturns++ + } else if mempool.IsDust(txOut, mempool.DefaultMinRelayTxFee) { + return fmt.Errorf("transaction output is dust. Value: %d", txOut.Value) + } + } + + if numOpReturns > 1 { + return fmt.Errorf("transaction has more than one op_return output") + } + + return nil +} diff --git a/walletcontroller/client.go b/walletcontroller/client.go index ae333d6..d47aea6 100644 --- a/walletcontroller/client.go +++ b/walletcontroller/client.go @@ -11,6 +11,7 @@ import ( "github.com/babylonlabs-io/babylon/crypto/bip322" "github.com/babylonlabs-io/btc-staker/stakercfg" "github.com/babylonlabs-io/btc-staker/types" + "github.com/babylonlabs-io/btc-staker/utils" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcjson" @@ -256,6 +257,14 @@ func (w *RPCWalletController) CreateTransaction( return nil, err } + err = utils.CheckTransaction(tx) + + if err != nil { + // returning error here means our tx building code is buggy, but it will save + // user from submitting invalid transaction to the network + return nil, fmt.Errorf("transaction is not standard: %w", err) + } + return tx, err }