diff --git a/btcclient/client_wallet.go b/btcclient/client_wallet.go index 1504eacc..fe52e740 100644 --- a/btcclient/client_wallet.go +++ b/btcclient/client_wallet.go @@ -137,3 +137,11 @@ func (c *Client) GetHighUTXOAndSum() (*btcjson.ListUnspentResult, float64, error func CalculateTxFee(feeRateAmount btcutil.Amount, size uint64) (uint64, error) { return uint64(feeRateAmount.MulF64(float64(size) / 1024)), nil } + +func (c *Client) FundRawTransaction(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) (*btcjson.FundRawTransactionResult, error) { + return c.Client.FundRawTransaction(tx, opts, isWitness) +} + +func (c *Client) SignRawTransactionWithWallet(tx *wire.MsgTx) (*wire.MsgTx, bool, error) { + return c.Client.SignRawTransactionWithWallet(tx) +} diff --git a/btcclient/interface.go b/btcclient/interface.go index 12adb6a5..d3021412 100644 --- a/btcclient/interface.go +++ b/btcclient/interface.go @@ -39,4 +39,6 @@ type BTCWallet interface { WalletPassphrase(passphrase string, timeoutSecs int64) error DumpPrivKey(address btcutil.Address) (*btcutil.WIF, error) GetHighUTXOAndSum() (*btcjson.ListUnspentResult, float64, error) + FundRawTransaction(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) (*btcjson.FundRawTransactionResult, error) + SignRawTransactionWithWallet(tx *wire.MsgTx) (*wire.MsgTx, bool, error) } diff --git a/e2etest/bitcoind_node_setup.go b/e2etest/bitcoind_node_setup.go index 52ffb4d4..e6686eaf 100644 --- a/e2etest/bitcoind_node_setup.go +++ b/e2etest/bitcoind_node_setup.go @@ -67,6 +67,7 @@ func (h *BitcoindTestHandler) Start() { }, startTimeout, 500*time.Millisecond, "bitcoind did not start") } +// GetBlockCount retrieves the current number of blocks in the blockchain from the Bitcoind. func (h *BitcoindTestHandler) GetBlockCount() (int, error) { buff, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"getblockcount"}) if err != nil { @@ -78,6 +79,7 @@ func (h *BitcoindTestHandler) GetBlockCount() (int, error) { return strconv.Atoi(parsedBuffStr) } +// GenerateBlocks mines a specified number of blocks in the Bitcoind. func (h *BitcoindTestHandler) GenerateBlocks(count int) *GenerateBlockResponse { buff, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"-generate", fmt.Sprintf("%d", count)}) require.NoError(h.t, err) @@ -89,11 +91,10 @@ func (h *BitcoindTestHandler) GenerateBlocks(count int) *GenerateBlockResponse { return &response } +// CreateWallet creates a new wallet with the specified name and passphrase in the Bitcoind func (h *BitcoindTestHandler) CreateWallet(walletName string, passphrase string) *CreateWalletResponse { - // last false on the list will create legacy wallet. This is needed, as currently - // we are signing all taproot transactions by dumping the private key and signing it - // on app level. Descriptor wallets do not allow dumping private keys. - buff, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"createwallet", walletName, "false", "false", passphrase, "false", "false"}) + // last arg is true which indicates we are using descriptor wallets they do not allow dumping private keys. + buff, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"createwallet", walletName, "false", "false", passphrase, "false", "true"}) require.NoError(h.t, err) var response CreateWalletResponse @@ -108,3 +109,9 @@ func (h *BitcoindTestHandler) InvalidateBlock(blockHash string) { _, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"invalidateblock", blockHash}) require.NoError(h.t, err) } + +// ImportDescriptors imports a given Bitcoin address descriptor into the Bitcoind +func (h *BitcoindTestHandler) ImportDescriptors(descriptor string) { + _, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"importdescriptors", descriptor}) + require.NoError(h.t, err) +} diff --git a/e2etest/container/config.go b/e2etest/container/config.go index b1d14313..01b75746 100644 --- a/e2etest/container/config.go +++ b/e2etest/container/config.go @@ -10,7 +10,7 @@ type ImageConfig struct { //nolint:deadcode const ( dockerBitcoindRepository = "lncm/bitcoind" - dockerBitcoindVersionTag = "v24.0.1" + dockerBitcoindVersionTag = "v27.0" ) // NewImageConfig returns ImageConfig needed for running e2e test. diff --git a/e2etest/container/container.go b/e2etest/container/container.go index c95bd19b..a2bab703 100644 --- a/e2etest/container/container.go +++ b/e2etest/container/container.go @@ -158,6 +158,7 @@ func (m *Manager) RunBitcoindResource( "-rpcallowip=0.0.0.0/0", "-rpcbind=0.0.0.0", "-zmqpubsequence=tcp://0.0.0.0:28333", + "-fallbackfee=0.0002", }, }, noRestart, diff --git a/e2etest/test_manager.go b/e2etest/test_manager.go index c3f21be2..7a82624e 100644 --- a/e2etest/test_manager.go +++ b/e2etest/test_manager.go @@ -4,6 +4,8 @@ import ( "bytes" "context" "encoding/hex" + "encoding/json" + "fmt" "go.uber.org/zap" "testing" "time" @@ -87,7 +89,6 @@ func StartManager(t *testing.T, numMatureOutputsInWallet uint32) *TestManager { btcHandler.Start() passphrase := "pass" _ = btcHandler.CreateWallet("default", passphrase) - blocksResponse := btcHandler.GenerateBlocks(int(numMatureOutputsInWallet)) cfg := defaultVigilanteConfig() @@ -102,6 +103,13 @@ func StartManager(t *testing.T, numMatureOutputsInWallet uint32) *TestManager { }, nil) require.NoError(t, err) + err = testRpcClient.WalletPassphrase(passphrase, 600) + require.NoError(t, err) + + walletPrivKey, err := importPrivateKey(btcHandler) + require.NoError(t, err) + blocksResponse := btcHandler.GenerateBlocks(int(numMatureOutputsInWallet)) + btcClient := initBTCClientWithSubscriber(t, cfg) var buff bytes.Buffer @@ -136,12 +144,6 @@ func StartManager(t *testing.T, numMatureOutputsInWallet uint32) *TestManager { return true }, eventuallyWaitTimeOut, eventuallyPollTime) - err = testRpcClient.WalletPassphrase(passphrase, 600) - require.NoError(t, err) - - walletPrivKey, err := testRpcClient.DumpPrivKey(minerAddressDecoded) - require.NoError(t, err) - return &TestManager{ TestRpcClient: testRpcClient, BabylonHandler: bh, @@ -149,7 +151,7 @@ func StartManager(t *testing.T, numMatureOutputsInWallet uint32) *TestManager { BitcoindHandler: btcHandler, BTCClient: btcClient, Config: cfg, - WalletPrivKey: walletPrivKey.PrivKey, + WalletPrivKey: walletPrivKey, } } @@ -227,3 +229,36 @@ func (tm *TestManager) CatchUpBTCLightClient(t *testing.T) { _, err = tm.InsertBTCHeadersToBabylon(headers) require.NoError(t, err) } + +func importPrivateKey(btcHandler *BitcoindTestHandler) (*btcec.PrivateKey, error) { + privKey, err := btcec.NewPrivateKey() + if err != nil { + return nil, err + } + + wif, err := btcutil.NewWIF(privKey, regtestParams, true) + if err != nil { + return nil, err + } + + // "combo" allows us to import a key and handle multiple types of btc scripts with a single descriptor command. + descriptor := fmt.Sprintf("combo(%s)", wif.String()) + + // Create the JSON descriptor object. + descJSON, err := json.Marshal([]map[string]interface{}{ + { + "desc": descriptor, + "active": true, + "timestamp": "now", // tells Bitcoind to start scanning from the current blockchain height + "label": "test key", + }, + }) + + if err != nil { + return nil, err + } + + btcHandler.ImportDescriptors(string(descJSON)) + + return privKey, nil +} diff --git a/e2etest/test_manager_btcstaking.go b/e2etest/test_manager_btcstaking.go index 579a5eea..c8b1bcdc 100644 --- a/e2etest/test_manager_btcstaking.go +++ b/e2etest/test_manager_btcstaking.go @@ -160,7 +160,6 @@ func (tm *TestManager) CreateBTCDelegation( require.NoError(t, err) btccParams := btccParamsResp.Params for i := 0; i < int(btccParams.BtcConfirmationDepth); i++ { - //tm.MineBlockWithTxs(t, tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{})) tm.mineBlock(t) } diff --git a/submitter/relayer/relayer.go b/submitter/relayer/relayer.go index 615921d8..1e23ebb0 100644 --- a/submitter/relayer/relayer.go +++ b/submitter/relayer/relayer.go @@ -3,7 +3,9 @@ package relayer import ( "bytes" "encoding/hex" + "errors" "fmt" + "github.com/btcsuite/btcd/btcjson" "math" "strconv" "time" @@ -15,7 +17,6 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/jinzhu/copier" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "go.uber.org/zap" @@ -25,6 +26,11 @@ import ( "github.com/babylonlabs-io/vigilante/types" ) +const ( + changePosition = 1 + dustThreshold btcutil.Amount = 546 +) + type Relayer struct { chainfee.Estimator btcclient.BTCWallet @@ -154,13 +160,7 @@ func (rl *Relayer) shouldResendCheckpoint(ckptInfo *types.CheckpointInfo, bumped // based on the current BTC load, considering both tx sizes // the result is multiplied by ResubmitFeeMultiplier set in config func (rl *Relayer) calculateBumpedFee(ckptInfo *types.CheckpointInfo) btcutil.Amount { - feeRate := rl.getFeeRate() - newTx1Fee := feeRate.FeeForVSize(ckptInfo.Tx1.Size) - newTx2Fee := feeRate.FeeForVSize(ckptInfo.Tx2.Size) - // minus the old fee of the first transaction because we do not want to pay again for the first transaction - bumpedFee := newTx1Fee + newTx2Fee - ckptInfo.Tx1.Fee - - return bumpedFee.MulF64(float64(rl.config.ResubmitFeeMultiplier)) + return ckptInfo.Tx2.Fee.MulF64(rl.config.ResubmitFeeMultiplier) } // resendSecondTxOfCheckpointToBTC resends the second tx of the checkpoint with bumpedFee @@ -168,28 +168,31 @@ func (rl *Relayer) resendSecondTxOfCheckpointToBTC(tx2 *types.BtcTxInfo, bumpedF // set output value of the second tx to be the balance minus the bumped fee // if the bumped fee is higher than the balance, then set the bumped fee to // be equal to the balance to ensure the output value is not negative - balance := tx2.Utxo.Amount + balance := btcutil.Amount(tx2.Tx.TxOut[changePosition].Value) + + // todo: revise this as this means we will end up with output with value 0 that will be rejected by bitcoind as dust output. if bumpedFee > balance { rl.logger.Debugf("the bumped fee %v Satoshis for the second tx is more than UTXO amount %v Satoshis", bumpedFee, balance) bumpedFee = balance } - tx2.Tx.TxOut[1].Value = int64(balance - bumpedFee) + + tx2.Tx.TxOut[changePosition].Value = int64(balance - bumpedFee) // resign the tx as the output is changed - tx, err := rl.dumpPrivKeyAndSignTx(tx2.Tx, tx2.Utxo) + tx, err := rl.signTx(tx2.Tx) if err != nil { return nil, err } - txid, err := rl.sendTxToBTC(tx) + txID, err := rl.sendTxToBTC(tx) if err != nil { return nil, err } // update tx info tx2.Fee = bumpedFee - tx2.TxId = txid + tx2.TxId = txID return tx2, nil } @@ -217,29 +220,22 @@ func (rl *Relayer) calcMinRelayFee(txVirtualSize int64) btcutil.Amount { return minRelayFee } -func (rl *Relayer) dumpPrivKeyAndSignTx(tx *wire.MsgTx, utxo *types.UTXO) (*wire.MsgTx, error) { - // get private key - err := rl.WalletPassphrase(rl.GetWalletPass(), rl.GetWalletLockTime()) - if err != nil { - return nil, err - } - wif, err := rl.DumpPrivKey(utxo.Addr) - if err != nil { +func (rl *Relayer) signTx(tx *wire.MsgTx) (*wire.MsgTx, error) { + // unlock the wallet + if err := rl.WalletPassphrase(rl.GetWalletPass(), rl.GetWalletLockTime()); err != nil { return nil, err } - // add signature/witness depending on the type of the previous address - // if not segwit, add signature; otherwise, add witness - segwit, err := isSegWit(utxo.Addr) + + signedTx, allSigned, err := rl.BTCWallet.SignRawTransactionWithWallet(tx) if err != nil { return nil, err } - // add unlocking script into the input of the tx - tx, err = completeTxIn(tx, segwit, wif.PrivKey, utxo) - if err != nil { - return nil, err + + if !allSigned { + return nil, errors.New("transaction is only partially signed") } - return tx, nil + return signedTx, nil } func (rl *Relayer) convertCkptToTwoTxAndSubmit(ckpt *ckpttypes.RawCheckpointResponse) (*types.CheckpointInfo, error) { @@ -260,18 +256,7 @@ func (rl *Relayer) convertCkptToTwoTxAndSubmit(ckpt *ckpttypes.RawCheckpointResp return nil, err } - utxo, err := rl.PickHighUTXO() - if err != nil { - return nil, err - } - - rl.logger.Debugf("Found one unspent tx with sufficient amount: %v", utxo.TxID) - - tx1, tx2, err := rl.ChainTwoTxAndSend( - utxo, - data1, - data2, - ) + tx1, tx2, err := rl.ChainTwoTxAndSend(data1, data2) if err != nil { return nil, err } @@ -305,20 +290,12 @@ func (rl *Relayer) convertCkptToTwoTxAndSubmit(ckpt *ckpttypes.RawCheckpointResp }, nil } -// ChainTwoTxAndSend consumes one utxo and build two chaining txs: +// ChainTwoTxAndSend builds two chaining txs with the given data: // the second tx consumes the output of the first tx -func (rl *Relayer) ChainTwoTxAndSend( - utxo *types.UTXO, - data1 []byte, - data2 []byte, -) (*types.BtcTxInfo, *types.BtcTxInfo, error) { - +func (rl *Relayer) ChainTwoTxAndSend(data1 []byte, data2 []byte) (*types.BtcTxInfo, *types.BtcTxInfo, error) { // recipient is a change address that all the // remaining balance of the utxo is sent to - tx1, err := rl.buildTxWithData( - utxo, - data1, - ) + tx1, err := rl.buildTxWithData(data1, nil) if err != nil { return nil, nil, fmt.Errorf("failed to add data to tx1: %w", err) } @@ -328,20 +305,9 @@ func (rl *Relayer) ChainTwoTxAndSend( return nil, nil, fmt.Errorf("failed to send tx1 to BTC: %w", err) } - changeUtxo := &types.UTXO{ - TxID: tx1.TxId, - Vout: 1, - ScriptPK: tx1.Tx.TxOut[1].PkScript, - Amount: btcutil.Amount(tx1.Tx.TxOut[1].Value), - Addr: tx1.ChangeAddress, - } - // the second tx consumes the second output (index 1) // of the first tx, as the output at index 0 is OP_RETURN - tx2, err := rl.buildTxWithData( - changeUtxo, - data2, - ) + tx2, err := rl.buildTxWithData(data2, tx1.Tx) if err != nil { return nil, nil, fmt.Errorf("failed to add data to tx2: %w", err) } @@ -356,44 +322,30 @@ func (rl *Relayer) ChainTwoTxAndSend( return tx1, tx2, nil } -// PickHighUTXO picks a UTXO that has the highest amount -func (rl *Relayer) PickHighUTXO() (*types.UTXO, error) { - // get the highest UTXO and UTXOs' sum in the list - topUTXO, sum, err := rl.BTCWallet.GetHighUTXOAndSum() - if err != nil { - return nil, err - } - utxo, err := types.NewUTXO(topUTXO, rl.GetNetParams()) - if err != nil { - return nil, fmt.Errorf("failed to convert ListUnspentResult to UTXO: %w", err) - } - rl.logger.Debugf("pick utxo with id: %v, amount: %v, confirmations: %v", utxo.TxID, utxo.Amount, topUTXO.Confirmations) - - // record metrics of UTXOs' sum - rl.metrics.AvailableBTCBalance.Set(sum) - - return utxo, nil -} - -// buildTxWithData builds a tx with data inserted as OP_RETURN -// note that OP_RETURN is set as the first output of the tx (index 0) -// and the rest of the balance is sent to a new change address -// as the second output with index 1 -func (rl *Relayer) buildTxWithData( - utxo *types.UTXO, - data []byte, -) (*types.BtcTxInfo, error) { - rl.logger.Debugf("Building a BTC tx using %v with data %x", utxo.TxID.String(), data) +// buildTxWithData constructs a Bitcoin transaction with custom data inserted as an OP_RETURN output. +// If `firstTx` is provided, it uses its transaction ID and a predefined output index (`changePosition`) +// to create an input for the new transaction. The OP_RETURN output is added as the first output (index 0). +// +// This function also ensures that the transaction fee is sufficient and signs the transaction before returning it. +// If the UTXO value is insufficient to cover the fee or if the change amount falls below the dust threshold, +// an error is returned. +// +// Parameters: +// - data: The custom data to be inserted into the transaction as an OP_RETURN output. +// - firstTx: An optional transaction used to create an input for the new transaction. +func (rl *Relayer) buildTxWithData(data []byte, firstTx *wire.MsgTx) (*types.BtcTxInfo, error) { tx := wire.NewMsgTx(wire.TxVersion) - outPoint := wire.NewOutPoint(utxo.TxID, utxo.Vout) - txIn := wire.NewTxIn(outPoint, nil, nil) - // Enable replace-by-fee - // See https://river.com/learn/terms/r/replace-by-fee-rbf - txIn.Sequence = math.MaxUint32 - 2 - tx.AddTxIn(txIn) + if firstTx != nil { + txID := firstTx.TxHash() + outPoint := wire.NewOutPoint(&txID, changePosition) + txIn := wire.NewTxIn(outPoint, nil, nil) + // Enable replace-by-fee, see https://river.com/learn/terms/r/replace-by-fee-rbf + txIn.Sequence = math.MaxUint32 - 2 + tx.AddTxIn(txIn) + } - // build txout for data + // build txOut for data builder := txscript.NewScriptBuilder() dataScript, err := builder.AddOp(txscript.OP_RETURN).AddData(data).Script() if err != nil { @@ -401,61 +353,79 @@ func (rl *Relayer) buildTxWithData( } tx.AddTxOut(wire.NewTxOut(0, dataScript)) - // build txout for change - changeAddr, err := rl.GetChangeAddress() - if err != nil { - return nil, fmt.Errorf("failed to get change address: %w", err) - } - rl.logger.Debugf("Got a change address %v", changeAddr.String()) - changeScript, err := txscript.PayToAddrScript(changeAddr) + changePosition := 1 // must declare here as you cannot take address of const needed bellow + feeRate := btcutil.Amount(rl.getFeeRate()).ToBTC() + rawTxResult, err := rl.BTCWallet.FundRawTransaction(tx, btcjson.FundRawTransactionOpts{ + FeeRate: &feeRate, + ChangePosition: &changePosition, + }, nil) if err != nil { return nil, err } - copiedTx := &wire.MsgTx{} - err = copier.Copy(copiedTx, tx) + + rl.logger.Debugf("Building a BTC tx using %s with data %x", rawTxResult.Transaction.TxID(), data) + + _, addresses, _, err := txscript.ExtractPkScriptAddrs( + rawTxResult.Transaction.TxOut[changePosition].PkScript, + rl.GetNetParams(), + ) + if err != nil { return nil, err } - txSize, err := calculateTxVirtualSize(copiedTx, utxo, changeScript) + + if len(addresses) == 0 { + return nil, errors.New("no change address found") + } + + changeAddr := addresses[0] + rl.logger.Debugf("Got a change address %v", changeAddr.String()) + + txSize, err := calculateTxVirtualSize(rawTxResult.Transaction) if err != nil { return nil, err } + + changeAmount := btcutil.Amount(rawTxResult.Transaction.TxOut[changePosition].Value) minRelayFee := rl.calcMinRelayFee(txSize) - if utxo.Amount < minRelayFee { - return nil, fmt.Errorf("the value of the utxo is not sufficient for relaying the tx. Require: %v. Have: %v", minRelayFee, utxo.Amount) + + if changeAmount < minRelayFee { + return nil, fmt.Errorf("the value of the utxo is not sufficient for relaying the tx. Require: %v. Have: %v", minRelayFee, changeAmount) } - txFee := rl.getFeeRate().FeeForVSize(txSize) + + txFee := rawTxResult.Fee // ensuring the tx fee is not lower than the minimum relay fee if txFee < minRelayFee { txFee = minRelayFee } // ensuring the tx fee is not higher than the utxo value - if utxo.Amount < txFee { - return nil, fmt.Errorf("the value of the utxo is not sufficient for paying the calculated fee of the tx. Calculated: %v. Have: %v", txFee, utxo.Amount) + if changeAmount < txFee { + return nil, fmt.Errorf("the value of the utxo is not sufficient for paying the calculated fee of the tx. Calculated: %v. Have: %v", txFee, changeAmount) } - change := utxo.Amount - txFee - tx.AddTxOut(wire.NewTxOut(int64(change), changeScript)) // sign tx - tx, err = rl.dumpPrivKeyAndSignTx(tx, utxo) + tx, err = rl.signTx(rawTxResult.Transaction) if err != nil { return nil, fmt.Errorf("failed to sign tx: %w", err) } // serialization var signedTxBytes bytes.Buffer - err = tx.Serialize(&signedTxBytes) - if err != nil { + if err := tx.Serialize(&signedTxBytes); err != nil { return nil, err } - rl.logger.Debugf("Successfully composed a BTC tx with balance of input: %v, "+ - "tx fee: %v, output value: %v, tx size: %v, hex: %v", - utxo.Amount, txFee, change, txSize, hex.EncodeToString(signedTxBytes.Bytes())) + change := changeAmount - txFee + + if change < dustThreshold { + return nil, fmt.Errorf("change amount is %v less then dust treshold %v", change, dustThreshold) + } + + rl.logger.Debugf("Successfully composed a BTC tx: tx fee: %v, output value: %v, tx size: %v, hex: %v", + txFee, changeAmount, txSize, hex.EncodeToString(signedTxBytes.Bytes())) return &types.BtcTxInfo{ Tx: tx, - Utxo: utxo, ChangeAddress: changeAddr, Size: txSize, Fee: txFee, @@ -490,10 +460,12 @@ func (rl *Relayer) getFeeRate() chainfee.SatPerKVByte { func (rl *Relayer) sendTxToBTC(tx *wire.MsgTx) (*chainhash.Hash, error) { rl.logger.Debugf("Sending tx %v to BTC", tx.TxHash().String()) + ha, err := rl.SendRawTransaction(tx, true) if err != nil { return nil, err } rl.logger.Debugf("Successfully sent tx %v to BTC", tx.TxHash().String()) + return ha, nil } diff --git a/submitter/relayer/utils.go b/submitter/relayer/utils.go index 7f267592..c909ac25 100644 --- a/submitter/relayer/utils.go +++ b/submitter/relayer/utils.go @@ -1,100 +1,16 @@ package relayer import ( - "bytes" "errors" - - "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/mempool" - "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - secp "github.com/decred/dcrd/dcrec/secp256k1/v4" - - "github.com/babylonlabs-io/vigilante/types" ) -func isSegWit(addr btcutil.Address) (bool, error) { - switch addr.(type) { - case *btcutil.AddressPubKeyHash, *btcutil.AddressScriptHash, *btcutil.AddressPubKey: - return false, nil - case *btcutil.AddressWitnessPubKeyHash, *btcutil.AddressWitnessScriptHash: - return true, nil - default: - return false, errors.New("non-supported address type") - } -} - -func calculateTxVirtualSize(tx *wire.MsgTx, utxo *types.UTXO, changeScript []byte) (int64, error) { - tx.AddTxOut(wire.NewTxOut(int64(utxo.Amount), changeScript)) - - // when calculating tx size we can use a random private key - privKey, err := secp.GeneratePrivateKey() - if err != nil { - return 0, err - } - - // add signature/witness depending on the type of the previous address - // if not segwit, add signature; otherwise, add witness - segwit, err := isSegWit(utxo.Addr) - if err != nil { - return 0, err - } - - tx, err = completeTxIn(tx, segwit, privKey, utxo) - if err != nil { - return 0, err - } - - var txBytes bytes.Buffer - err = tx.Serialize(&txBytes) - if err != nil { - return 0, err - } - btcTx, err := btcutil.NewTxFromBytes(txBytes.Bytes()) - if err != nil { - return 0, err - } - - return mempool.GetTxVirtualSize(btcTx), err -} - -func completeTxIn(tx *wire.MsgTx, isSegWit bool, privKey *btcec.PrivateKey, utxo *types.UTXO) (*wire.MsgTx, error) { - if !isSegWit { - sig, err := txscript.SignatureScript( - tx, - 0, - utxo.ScriptPK, - txscript.SigHashAll, - privKey, - true, - ) - if err != nil { - return nil, err - } - tx.TxIn[0].SignatureScript = sig - } else { - sighashes := txscript.NewTxSigHashes( - tx, - // Use the CannedPrevOutputFetcher which is only able to return information about a single UTXO - // See https://github.com/btcsuite/btcd/commit/e781b66e2fb9a354a14bfa7fbdd44038450cc13f - // for details on the output fetchers - txscript.NewCannedPrevOutputFetcher(utxo.ScriptPK, int64(utxo.Amount))) - wit, err := txscript.WitnessSignature( - tx, - sighashes, - 0, - int64(utxo.Amount), - utxo.ScriptPK, - txscript.SigHashAll, - privKey, - true, - ) - if err != nil { - return nil, err - } - tx.TxIn[0].Witness = wit +func calculateTxVirtualSize(tx *wire.MsgTx) (int64, error) { + if tx == nil { + return -1, errors.New("tx param nil") } - return tx, nil + return mempool.GetTxVirtualSize(btcutil.NewTx(tx)), nil } diff --git a/testutil/mocks/btcclient.go b/testutil/mocks/btcclient.go index 8023d652..1bbdb6b9 100644 --- a/testutil/mocks/btcclient.go +++ b/testutil/mocks/btcclient.go @@ -251,6 +251,21 @@ func (mr *MockBTCWalletMockRecorder) DumpPrivKey(address interface{}) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DumpPrivKey", reflect.TypeOf((*MockBTCWallet)(nil).DumpPrivKey), address) } +// FundRawTransaction mocks base method. +func (m *MockBTCWallet) FundRawTransaction(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) (*btcjson.FundRawTransactionResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FundRawTransaction", tx, opts, isWitness) + ret0, _ := ret[0].(*btcjson.FundRawTransactionResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FundRawTransaction indicates an expected call of FundRawTransaction. +func (mr *MockBTCWalletMockRecorder) FundRawTransaction(tx, opts, isWitness interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FundRawTransaction", reflect.TypeOf((*MockBTCWallet)(nil).FundRawTransaction), tx, opts, isWitness) +} + // GetBTCConfig mocks base method. func (m *MockBTCWallet) GetBTCConfig() *config.BTCConfig { m.ctrl.T.Helper() @@ -383,6 +398,22 @@ func (mr *MockBTCWalletMockRecorder) SendRawTransaction(tx, allowHighFees interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRawTransaction", reflect.TypeOf((*MockBTCWallet)(nil).SendRawTransaction), tx, allowHighFees) } +// SignRawTransactionWithWallet mocks base method. +func (m *MockBTCWallet) SignRawTransactionWithWallet(tx *wire.MsgTx) (*wire.MsgTx, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SignRawTransactionWithWallet", tx) + ret0, _ := ret[0].(*wire.MsgTx) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// SignRawTransactionWithWallet indicates an expected call of SignRawTransactionWithWallet. +func (mr *MockBTCWalletMockRecorder) SignRawTransactionWithWallet(tx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignRawTransactionWithWallet", reflect.TypeOf((*MockBTCWallet)(nil).SignRawTransactionWithWallet), tx) +} + // Stop mocks base method. func (m *MockBTCWallet) Stop() { m.ctrl.T.Helper() diff --git a/types/ckpt_info.go b/types/ckpt_info.go index 4c9b93fc..5d64c6b0 100644 --- a/types/ckpt_info.go +++ b/types/ckpt_info.go @@ -21,7 +21,6 @@ type BtcTxInfo struct { TxId *chainhash.Hash Tx *wire.MsgTx ChangeAddress btcutil.Address - Utxo *UTXO // the UTXO used to build this BTC tx Size int64 // the size of the BTC tx Fee btcutil.Amount // tx fee cost by the BTC tx }