Skip to content

Commit

Permalink
fix(submitter): handle no change output (#79)
Browse files Browse the repository at this point in the history
Handles a case where there are no change outputs
  • Loading branch information
Lazar955 authored Oct 15, 2024
1 parent 47956ed commit 8284925
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 27 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

### Improvements

* [#79](https://github.com/babylonlabs-io/vigilante/pull/79) handle no change output when building tx

* [#77](https://github.com/babylonlabs-io/vigilante/pull/77) add arm64 static build

* [#76](https://github.com/babylonlabs-io/vigilante/pull/76) add goreleaser
Expand Down
1 change: 1 addition & 0 deletions cmd/vigilante/cmd/submitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func GetSubmitterCmd() *cobra.Command {
cfg.Common.MaxRetryTimes,
submitterMetrics,
dbBackend,
cfg.BTC.WalletName,
)
if err != nil {
panic(fmt.Errorf("failed to create vigilante submitter: %w", err))
Expand Down
1 change: 1 addition & 0 deletions e2etest/monitor_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func TestMonitorBootstrap(t *testing.T) {
tm.Config.Common.MaxRetryTimes,
metrics.NewSubmitterMetrics(),
testutil.MakeTestBackend(t),
tm.Config.BTC.WalletName,
)

vigilantSubmitter.Start()
Expand Down
2 changes: 2 additions & 0 deletions e2etest/submitter_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func TestSubmitterSubmission(t *testing.T) {
tm.Config.Common.MaxRetryTimes,
metrics.NewSubmitterMetrics(),
testutil.MakeTestBackend(t),
tm.Config.BTC.WalletName,
)

vigilantSubmitter.Start()
Expand Down Expand Up @@ -144,6 +145,7 @@ func TestSubmitterSubmissionReplace(t *testing.T) {
tm.Config.Common.MaxRetryTimes,
metrics.NewSubmitterMetrics(),
testutil.MakeTestBackend(t),
tm.Config.BTC.WalletName,
)

vigilantSubmitter.Start()
Expand Down
2 changes: 1 addition & 1 deletion submitter/relayer/change_address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestGetChangeAddress(t *testing.T) {
cfg := config.DefaultSubmitterConfig()
logger, err := config.NewRootLogger("auto", "debug")
require.NoError(t, err)
testRelayer := relayer.New(wallet, []byte("bbnt"), btctxformatter.CurrentVersion, submitterAddr,
testRelayer := relayer.New(wallet, btcConfig.WalletName, []byte("bbnt"), btctxformatter.CurrentVersion, submitterAddr,
submitterMetrics.RelayerMetrics, nil, &cfg, logger, testutil.MakeTestBackend(t))

// 1. only SegWit Bech32 addresses
Expand Down
69 changes: 48 additions & 21 deletions submitter/relayer/relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@ type Relayer struct {
metrics *metrics.RelayerMetrics
config *config.SubmitterConfig
logger *zap.SugaredLogger
walletName string
}

func New(
wallet btcclient.BTCWallet,
walletName string,
tag btctxformatter.BabylonTag,
version btctxformatter.FormatVersion,
submitterAddress sdk.AccAddress,
Expand All @@ -74,6 +76,7 @@ func New(
return &Relayer{
Estimator: est,
BTCWallet: wallet,
walletName: walletName,
store: subStore,
tag: tag,
version: version,
Expand Down Expand Up @@ -487,7 +490,9 @@ func (rl *Relayer) ChainTwoTxAndSend(data1 []byte, data2 []byte) (*types.BtcTxIn
func (rl *Relayer) buildTxWithData(data []byte, firstTx *wire.MsgTx) (*types.BtcTxInfo, error) {
tx := wire.NewMsgTx(wire.TxVersion)

if firstTx != nil {
isSecondTx := firstTx != nil

if isSecondTx {
txID := firstTx.TxHash()
outPoint := wire.NewOutPoint(&txID, changePosition)
txIn := wire.NewTxIn(outPoint, nil, nil)
Expand All @@ -514,33 +519,56 @@ func (rl *Relayer) buildTxWithData(data []byte, firstTx *wire.MsgTx) (*types.Btc
return nil, err
}

rl.logger.Debugf("Building a BTC tx using %s with data %x", rawTxResult.Transaction.TxID(), data)
// we want to ensure that firstTx has change output, but for the second transaction we can ignore this
hasChange := len(rawTxResult.Transaction.TxOut) > changePosition
// let's manually add a change output with 546 satoshis
if !isSecondTx && !hasChange {
changeAddr, err := rl.BTCWallet.GetRawChangeAddress(rl.walletName)
if err != nil {
return nil, fmt.Errorf("err getting raw change address %w", err)
}

_, addresses, _, err := txscript.ExtractPkScriptAddrs(
rawTxResult.Transaction.TxOut[changePosition].PkScript,
rl.GetNetParams(),
)
changePkScript, err := txscript.PayToAddrScript(changeAddr)
if err != nil {
return nil, fmt.Errorf("failed to create script for change address: %s err %w", changeAddr, err)
}

if err != nil {
return nil, err
changeOutput := wire.NewTxOut(int64(dustThreshold), changePkScript)
rawTxResult.Transaction.AddTxOut(changeOutput)
}

if len(addresses) == 0 {
return nil, errors.New("no change address found")
}
rl.logger.Debugf("Building a BTC tx using %s with data %x", rawTxResult.Transaction.TxID(), data)

changeAddr := addresses[0]
rl.logger.Debugf("Got a change address %v", changeAddr.String())
if hasChange {
_, addresses, _, err := txscript.ExtractPkScriptAddrs(
rawTxResult.Transaction.TxOut[changePosition].PkScript,
rl.GetNetParams(),
)

if err != nil {
return nil, err
}

if len(addresses) == 0 {
return nil, errors.New("no change address found")
}

rl.logger.Debugf("Got a change address %v", addresses[0].String())
}

txSize, err := calculateTxVirtualSize(rawTxResult.Transaction)
if err != nil {
return nil, err
}

changeAmount := btcutil.Amount(rawTxResult.Transaction.TxOut[changePosition].Value)
var changeAmount btcutil.Amount
if hasChange {
changeAmount = btcutil.Amount(rawTxResult.Transaction.TxOut[changePosition].Value)
}

minRelayFee := rl.calcMinRelayFee(txSize)

if changeAmount < minRelayFee {
if hasChange && changeAmount < minRelayFee {
return nil, fmt.Errorf("the value of the utxo is not sufficient for relaying the tx. Require: %v. Have: %v", minRelayFee, changeAmount)
}

Expand All @@ -550,7 +578,7 @@ func (rl *Relayer) buildTxWithData(data []byte, firstTx *wire.MsgTx) (*types.Btc
txFee = minRelayFee
}
// ensuring the tx fee is not higher than the utxo value
if changeAmount < txFee {
if hasChange && 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)
}

Expand All @@ -568,18 +596,17 @@ func (rl *Relayer) buildTxWithData(data []byte, firstTx *wire.MsgTx) (*types.Btc

change := changeAmount - txFee

if change < dustThreshold {
if hasChange && 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,
ChangeAddress: changeAddr,
Size: txSize,
Fee: txFee,
Tx: tx,
Size: txSize,
Fee: txFee,
}, nil
}

Expand Down
2 changes: 2 additions & 0 deletions submitter/submitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func New(
retrySleepTime, maxRetrySleepTime time.Duration, maxRetryTimes uint,
submitterMetrics *metrics.SubmitterMetrics,
db kvdb.Backend,
walletName string,
) (*Submitter, error) {
logger := parentLogger.With(zap.String("module", "submitter"))
var (
Expand Down Expand Up @@ -80,6 +81,7 @@ func New(

r := relayer.New(
btcWallet,
walletName,
checkpointTag,
btctxformatter.CurrentVersion,
submitterAddr,
Expand Down
9 changes: 4 additions & 5 deletions types/ckpt_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ type CheckpointInfo struct {

// BtcTxInfo stores information of a BTC tx as part of a checkpoint
type BtcTxInfo struct {
TxId *chainhash.Hash
Tx *wire.MsgTx
ChangeAddress btcutil.Address
Size int64 // the size of the BTC tx
Fee btcutil.Amount // tx fee cost by the BTC tx
TxId *chainhash.Hash
Tx *wire.MsgTx
Size int64 // the size of the BTC tx
Fee btcutil.Amount // tx fee cost by the BTC tx
}

0 comments on commit 8284925

Please sign in to comment.