mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
If a SignTx call is slow, the whole presign() function could timeout if all the calls are done sequentially. In this commit each call is done in a separate goroutine to reduce total latency.
1905 lines
39 KiB
Go
1905 lines
39 KiB
Go
package sweepbatcher
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestOrderedSweeps checks that methods batch.getOrderedSweeps and
|
|
// batch.getSweepsGroups works properly.
|
|
func TestOrderedSweeps(t *testing.T) {
|
|
// Prepare the necessary data for test cases.
|
|
op1 := wire.OutPoint{Hash: chainhash.Hash{1, 1, 1}, Index: 1}
|
|
op2 := wire.OutPoint{Hash: chainhash.Hash{2, 2, 2}, Index: 2}
|
|
op3 := wire.OutPoint{Hash: chainhash.Hash{3, 3, 3}, Index: 3}
|
|
op4 := wire.OutPoint{Hash: chainhash.Hash{4, 4, 4}, Index: 4}
|
|
op5 := wire.OutPoint{Hash: chainhash.Hash{5, 5, 5}, Index: 5}
|
|
op6 := wire.OutPoint{Hash: chainhash.Hash{6, 6, 6}, Index: 6}
|
|
|
|
swapHash1 := lntypes.Hash{1, 1}
|
|
swapHash2 := lntypes.Hash{2, 2}
|
|
swapHash3 := lntypes.Hash{3, 3}
|
|
|
|
ctx := context.Background()
|
|
|
|
cases := []struct {
|
|
name string
|
|
sweeps []sweep
|
|
skippedTxns map[chainhash.Hash]struct{}
|
|
|
|
// Testing errors.
|
|
skipStore bool
|
|
reverseStore bool
|
|
replaceSweeps map[wire.OutPoint]sweep
|
|
|
|
wantGroups [][]sweep
|
|
wantErr1 string
|
|
wantErr2 string
|
|
}{
|
|
{
|
|
name: "no sweeps",
|
|
sweeps: []sweep{},
|
|
wantGroups: [][]sweep{},
|
|
},
|
|
|
|
{
|
|
name: "one sweep",
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash1,
|
|
},
|
|
},
|
|
wantGroups: [][]sweep{
|
|
{
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "one sweep, skipped",
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash1,
|
|
},
|
|
},
|
|
skippedTxns: map[chainhash.Hash]struct{}{
|
|
op1.Hash: {},
|
|
},
|
|
wantGroups: [][]sweep{},
|
|
},
|
|
|
|
{
|
|
name: "two sweeps, one swap",
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op2,
|
|
swapHash: swapHash1,
|
|
},
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash1,
|
|
},
|
|
},
|
|
wantGroups: [][]sweep{
|
|
{
|
|
{
|
|
outpoint: op2,
|
|
swapHash: swapHash1,
|
|
},
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "two sweeps, one swap, one skipped",
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op2,
|
|
swapHash: swapHash1,
|
|
},
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash1,
|
|
},
|
|
},
|
|
skippedTxns: map[chainhash.Hash]struct{}{
|
|
op1.Hash: {},
|
|
},
|
|
wantGroups: [][]sweep{
|
|
{
|
|
{
|
|
outpoint: op2,
|
|
swapHash: swapHash1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "two sweeps, two swap",
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op2,
|
|
swapHash: swapHash1,
|
|
},
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash2,
|
|
},
|
|
},
|
|
wantGroups: [][]sweep{
|
|
{
|
|
{
|
|
outpoint: op2,
|
|
swapHash: swapHash1,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "many sweeps and swaps",
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash1,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
swapHash: swapHash1,
|
|
},
|
|
{
|
|
outpoint: op3,
|
|
swapHash: swapHash1,
|
|
},
|
|
{
|
|
outpoint: op4,
|
|
swapHash: swapHash2,
|
|
},
|
|
{
|
|
outpoint: op5,
|
|
swapHash: swapHash3,
|
|
},
|
|
{
|
|
outpoint: op6,
|
|
swapHash: swapHash3,
|
|
},
|
|
},
|
|
wantGroups: [][]sweep{
|
|
{
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash1,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
swapHash: swapHash1,
|
|
},
|
|
{
|
|
outpoint: op3,
|
|
swapHash: swapHash1,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
outpoint: op4,
|
|
swapHash: swapHash2,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
outpoint: op5,
|
|
swapHash: swapHash3,
|
|
},
|
|
{
|
|
outpoint: op6,
|
|
swapHash: swapHash3,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "error: sweeps not stored in DB",
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash1,
|
|
},
|
|
},
|
|
skipStore: true,
|
|
wantErr1: "returned 0 sweeps, len(b.sweeps) is 1",
|
|
},
|
|
|
|
{
|
|
name: "error: wrong order in DB",
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash1,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
swapHash: swapHash2,
|
|
},
|
|
},
|
|
reverseStore: true,
|
|
},
|
|
|
|
{
|
|
name: "error: extra sweep in DB",
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash1,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
swapHash: swapHash2,
|
|
},
|
|
},
|
|
replaceSweeps: map[wire.OutPoint]sweep{
|
|
op2: {
|
|
outpoint: op3,
|
|
swapHash: swapHash3,
|
|
},
|
|
},
|
|
wantErr1: "returned unknown sweep",
|
|
},
|
|
|
|
{
|
|
name: "error: swaps interleaved",
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
swapHash: swapHash1,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
swapHash: swapHash2,
|
|
},
|
|
{
|
|
outpoint: op3,
|
|
swapHash: swapHash1,
|
|
},
|
|
},
|
|
wantErr2: "3 groups of sweeps and 2 distinct swaps",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// Create a map of sweeps.
|
|
m := make(map[wire.OutPoint]sweep, len(tc.sweeps))
|
|
for _, s := range tc.sweeps {
|
|
require.NotContains(t, m, s.outpoint)
|
|
m[s.outpoint] = s
|
|
}
|
|
|
|
// Create a batch.
|
|
b := &batch{
|
|
sweeps: m,
|
|
store: NewStoreMock(),
|
|
cfg: &batchConfig{
|
|
skippedTxns: tc.skippedTxns,
|
|
},
|
|
}
|
|
|
|
// Store the sweeps in mock store.
|
|
switch {
|
|
// Don't create DB sweeps at all.
|
|
case tc.skipStore:
|
|
|
|
// Reverse the order of DB sweeps to ID incorrect IDs.
|
|
case tc.reverseStore:
|
|
for i := len(tc.sweeps) - 1; i >= 0; i-- {
|
|
s := tc.sweeps[i]
|
|
const completed = false
|
|
err := b.persistSweep(ctx, s, completed)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Working DB sweeps.
|
|
default:
|
|
for _, s := range tc.sweeps {
|
|
const completed = false
|
|
err := b.persistSweep(ctx, s, completed)
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
// Replace some sweeps to test an error.
|
|
for removed, added := range tc.replaceSweeps {
|
|
require.Contains(t, m, removed)
|
|
delete(m, removed)
|
|
require.NotContains(t, m, added.outpoint)
|
|
m[added.outpoint] = added
|
|
}
|
|
|
|
// Remove skipped sweeps from the batch to make it
|
|
// match with what is read from DB after filtering.
|
|
for op := range m {
|
|
if _, has := tc.skippedTxns[op.Hash]; has {
|
|
delete(m, op)
|
|
}
|
|
}
|
|
|
|
// Now run the tested functions.
|
|
orderedSweeps, err := b.getOrderedSweeps(ctx)
|
|
if tc.wantErr1 != "" {
|
|
require.ErrorContains(t, err, tc.wantErr1)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
if tc.reverseStore {
|
|
require.NotEqual(t, tc.sweeps, orderedSweeps)
|
|
return
|
|
}
|
|
|
|
// The wanted list of sweeps matches the input order.
|
|
notSkipped := make([]sweep, 0, len(tc.sweeps))
|
|
for _, s := range tc.sweeps {
|
|
_, has := tc.skippedTxns[s.outpoint.Hash]
|
|
if has {
|
|
continue
|
|
}
|
|
notSkipped = append(notSkipped, s)
|
|
}
|
|
require.Equal(t, notSkipped, orderedSweeps)
|
|
|
|
groups, err := b.getSweepsGroups(ctx)
|
|
if tc.wantErr2 != "" {
|
|
require.ErrorContains(t, err, tc.wantErr2)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.wantGroups, groups)
|
|
})
|
|
}
|
|
}
|
|
|
|
// mockPresignedTxChecker is an implementation of presignedTxChecker used in
|
|
// TestEnsurePresigned.
|
|
type mockPresignedTxChecker struct {
|
|
// primarySweepID is the value the mock matches this argument against.
|
|
primarySweepID wire.OutPoint
|
|
|
|
// destPkScript is the value returned by mock.DestPkScript.
|
|
destPkScript []byte
|
|
|
|
// signTxCalls is the number of SignTx calls.
|
|
signTxCalls int
|
|
|
|
// recordedInputAmt is the saved value of inputAmt argument of SignTx.
|
|
recordedInputAmt btcutil.Amount
|
|
|
|
// recordedMinRelayFee is the saved value of minRelayFee argument of
|
|
// SignTx.
|
|
recordedMinRelayFee chainfee.SatPerKWeight
|
|
|
|
// recordedFeeRate is the saved value of feeRate argument of SignTx.
|
|
recordedFeeRate chainfee.SatPerKWeight
|
|
|
|
// recordedLoadOnly is the saved value of loadOnly argument of SignTx.
|
|
recordedLoadOnly bool
|
|
|
|
// destPkScriptErr is the error returned by DestPkScript (if any).
|
|
destPkScriptErr error
|
|
|
|
// signedTxErr is the error returned by SignTx (if any).
|
|
signedTxErr error
|
|
}
|
|
|
|
// DestPkScript returns destination pkScript used by the sweep batch.
|
|
func (m *mockPresignedTxChecker) DestPkScript(ctx context.Context,
|
|
primarySweepID wire.OutPoint) ([]byte, error) {
|
|
|
|
if primarySweepID != m.primarySweepID {
|
|
return nil, fmt.Errorf("primarySweepID mismatch")
|
|
}
|
|
|
|
if m.destPkScriptErr != nil {
|
|
return nil, m.destPkScriptErr
|
|
}
|
|
|
|
return m.destPkScript, nil
|
|
}
|
|
|
|
// SignTx records all its argumentd and returned a "presigned" tx.
|
|
func (m *mockPresignedTxChecker) SignTx(ctx context.Context,
|
|
primarySweepID wire.OutPoint, tx *wire.MsgTx, inputAmt btcutil.Amount,
|
|
minRelayFee, feeRate chainfee.SatPerKWeight,
|
|
loadOnly bool) (*wire.MsgTx, error) {
|
|
|
|
m.signTxCalls++
|
|
|
|
if primarySweepID != m.primarySweepID {
|
|
return nil, fmt.Errorf("primarySweepID mismatch")
|
|
}
|
|
|
|
m.recordedInputAmt = inputAmt
|
|
m.recordedMinRelayFee = minRelayFee
|
|
m.recordedFeeRate = feeRate
|
|
m.recordedLoadOnly = loadOnly
|
|
|
|
if m.signedTxErr != nil {
|
|
return nil, m.signedTxErr
|
|
}
|
|
|
|
// Pretend that we have a presigned transaction.
|
|
tx = tx.Copy()
|
|
for i := range tx.TxIn {
|
|
tx.TxIn[i].Witness = wire.TxWitness{
|
|
make([]byte, 64),
|
|
}
|
|
}
|
|
|
|
return tx, nil
|
|
}
|
|
|
|
// TestEnsurePresigned checks that function ensurePresigned works correctly.
|
|
func TestEnsurePresigned(t *testing.T) {
|
|
// Prepare the necessary data for test cases.
|
|
op1 := wire.OutPoint{
|
|
Hash: chainhash.Hash{1, 1, 1},
|
|
Index: 1,
|
|
}
|
|
op2 := wire.OutPoint{
|
|
Hash: chainhash.Hash{2, 2, 2},
|
|
Index: 2,
|
|
}
|
|
|
|
batchPkScript, err := txscript.PayToAddrScript(destAddr)
|
|
require.NoError(t, err)
|
|
|
|
ctx := context.Background()
|
|
|
|
cases := []struct {
|
|
name string
|
|
primarySweepID wire.OutPoint
|
|
sweeps []*sweep
|
|
destPkScript []byte
|
|
minRelayFeeRate chainfee.SatPerKWeight
|
|
wantInputAmt btcutil.Amount
|
|
destPkScriptErr error
|
|
signedTxErr error
|
|
}{
|
|
{
|
|
name: "one input",
|
|
primarySweepID: op1,
|
|
sweeps: []*sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destPkScript: batchPkScript,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantInputAmt: 1_000_000,
|
|
},
|
|
|
|
{
|
|
name: "one input, higher minRelayFeeRate",
|
|
primarySweepID: op1,
|
|
sweeps: []*sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destPkScript: batchPkScript,
|
|
minRelayFeeRate: 1000,
|
|
wantInputAmt: 1_000_000,
|
|
},
|
|
|
|
{
|
|
name: "two inputs",
|
|
primarySweepID: op1,
|
|
sweeps: []*sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
value: 2_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destPkScript: batchPkScript,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantInputAmt: 3_000_000,
|
|
},
|
|
|
|
{
|
|
name: "error: DestPkScript fails",
|
|
primarySweepID: op1,
|
|
sweeps: []*sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
destPkScriptErr: fmt.Errorf("test DestPkScript error"),
|
|
},
|
|
|
|
{
|
|
name: "error: SignTx fails",
|
|
primarySweepID: op1,
|
|
sweeps: []*sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destPkScript: batchPkScript,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
signedTxErr: fmt.Errorf("test SignTx error"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c := &mockPresignedTxChecker{
|
|
primarySweepID: tc.primarySweepID,
|
|
destPkScript: tc.destPkScript,
|
|
destPkScriptErr: tc.destPkScriptErr,
|
|
signedTxErr: tc.signedTxErr,
|
|
}
|
|
|
|
err := ensurePresigned(
|
|
ctx, tc.sweeps, c, tc.minRelayFeeRate,
|
|
&chaincfg.RegressionNetParams,
|
|
)
|
|
switch {
|
|
case tc.destPkScriptErr != nil:
|
|
require.ErrorIs(t, err, tc.destPkScriptErr)
|
|
case tc.signedTxErr != nil:
|
|
require.ErrorIs(t, err, tc.signedTxErr)
|
|
default:
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, c.signTxCalls)
|
|
require.Equal(
|
|
t, tc.wantInputAmt, c.recordedInputAmt,
|
|
)
|
|
require.Equal(
|
|
t, tc.minRelayFeeRate,
|
|
c.recordedMinRelayFee,
|
|
)
|
|
require.Equal(
|
|
t, tc.minRelayFeeRate,
|
|
c.recordedFeeRate,
|
|
)
|
|
require.True(t, c.recordedLoadOnly)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// hasInput returns if the transaction spends the UTXO.
|
|
func hasInput(tx *wire.MsgTx, utxo wire.OutPoint) bool {
|
|
for _, txIn := range tx.TxIn {
|
|
if txIn.PreviousOutPoint == utxo {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// mockPresigner is an implementation of Presigner used in TestPresign.
|
|
type mockPresigner struct {
|
|
// outputs collects outputs of presigned transactions.
|
|
outputs []btcutil.Amount
|
|
|
|
// lockTimes collects LockTime's of presigned transactions.
|
|
lockTimes []uint32
|
|
|
|
// failAt is optional index of a call at which it fails, 1 based.
|
|
failAt int
|
|
|
|
// mu protects the state.
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// SignTx memorizes the value of the output and fails if the number of
|
|
// calls previously made is failAt.
|
|
func (p *mockPresigner) SignTx(ctx context.Context,
|
|
primarySweepID wire.OutPoint, tx *wire.MsgTx, inputAmt btcutil.Amount,
|
|
minRelayFee, feeRate chainfee.SatPerKWeight,
|
|
loadOnly bool) (*wire.MsgTx, error) {
|
|
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if ctx.Err() != nil {
|
|
return nil, ctx.Err()
|
|
}
|
|
|
|
if !hasInput(tx, primarySweepID) {
|
|
return nil, fmt.Errorf("primarySweepID %v not in tx",
|
|
primarySweepID)
|
|
}
|
|
|
|
if len(p.outputs)+1 == p.failAt {
|
|
return nil, fmt.Errorf("test error in SignTx")
|
|
}
|
|
|
|
p.outputs = append(p.outputs, btcutil.Amount(tx.TxOut[0].Value))
|
|
p.lockTimes = append(p.lockTimes, tx.LockTime)
|
|
|
|
return tx, nil
|
|
}
|
|
|
|
// TestPresign checks that function presign presigns correct set of transactions
|
|
// and handles edge cases properly.
|
|
func TestPresign(t *testing.T) {
|
|
// Prepare the necessary data for test cases.
|
|
op1 := wire.OutPoint{
|
|
Hash: chainhash.Hash{1, 1, 1},
|
|
Index: 1,
|
|
}
|
|
op2 := wire.OutPoint{
|
|
Hash: chainhash.Hash{2, 2, 2},
|
|
Index: 2,
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
cases := []struct {
|
|
name string
|
|
presigner presigner
|
|
primarySweepID wire.OutPoint
|
|
sweeps []sweep
|
|
destAddr btcutil.Address
|
|
nextBlockFeeRate chainfee.SatPerKWeight
|
|
minRelayFeeRate chainfee.SatPerKWeight
|
|
wantErr string
|
|
wantOutputs []btcutil.Amount
|
|
wantLockTimes []uint32
|
|
}{
|
|
{
|
|
name: "error: no presigner",
|
|
primarySweepID: op1,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantErr: "presigner is not installed",
|
|
},
|
|
|
|
{
|
|
name: "error: no sweeps",
|
|
primarySweepID: op1,
|
|
presigner: &mockPresigner{},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantErr: "there are no sweeps",
|
|
},
|
|
|
|
{
|
|
name: "error: no destAddr",
|
|
presigner: &mockPresigner{},
|
|
primarySweepID: op1,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
nextBlockFeeRate: chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantErr: "unsupported address type <nil>",
|
|
},
|
|
|
|
{
|
|
name: "error: zero nextBlockFeeRate",
|
|
presigner: &mockPresigner{},
|
|
primarySweepID: op1,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
value: 2_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantErr: "nextBlockFeeRate is not set",
|
|
},
|
|
|
|
{
|
|
name: "error: zero minRelayFeeRate",
|
|
presigner: &mockPresigner{},
|
|
primarySweepID: op1,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
value: 2_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: chainfee.FeePerKwFloor,
|
|
wantErr: "minRelayFeeRate is not set",
|
|
},
|
|
|
|
{
|
|
name: "error: timeout is not set",
|
|
presigner: &mockPresigner{},
|
|
primarySweepID: op1,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
value: 2_000_000,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantErr: "timeout is invalid: 0",
|
|
},
|
|
|
|
{
|
|
name: "error: primary not set",
|
|
presigner: &mockPresigner{},
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
value: 2_000_000,
|
|
timeout: 1100,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantErr: "not in tx",
|
|
},
|
|
|
|
{
|
|
name: "error: primary not in tx",
|
|
presigner: &mockPresigner{},
|
|
primarySweepID: op2,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantErr: "not in tx",
|
|
},
|
|
|
|
{
|
|
name: "one sweep",
|
|
presigner: &mockPresigner{},
|
|
primarySweepID: op1,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantOutputs: []btcutil.Amount{
|
|
999900, 999880, 999856, 999827, 999793, 999752,
|
|
999702, 999643, 999572, 999486, 999384, 999260,
|
|
999113, 998935, 998723, 998467, 998161, 997793,
|
|
997352, 996823, 996187, 995425, 994510, 993413,
|
|
992096, 990515, 988618, 986342, 983610, 980332,
|
|
976399, 971679, 966015, 959218, 951062, 941274,
|
|
929530, 915435, 898523, 878227, 853873, 824648,
|
|
},
|
|
wantLockTimes: []uint32{
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 950, 950, 950,
|
|
950, 950, 950, 950, 950, 950, 950, 950, 950,
|
|
950, 950, 950, 950,
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "higher minRelayFeeRate, fewer txns",
|
|
presigner: &mockPresigner{},
|
|
primarySweepID: op1,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: 10 * chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: 10 * chainfee.FeePerKwFloor,
|
|
wantOutputs: []btcutil.Amount{
|
|
998998, 998797, 998557, 998269, 997923, 997507,
|
|
997009, 996411, 995694, 994833, 993800, 992560,
|
|
991072, 989286, 987144, 984573, 981488, 977786,
|
|
973343, 968012, 961614, 953937, 944725, 933670,
|
|
920405, 904486, 885383, 862460, 834952,
|
|
},
|
|
wantLockTimes: []uint32{
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 950, 950,
|
|
950, 950, 950, 950, 950, 950, 950, 950, 950,
|
|
950, 950, 950, 950, 950,
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "two sweeps",
|
|
presigner: &mockPresigner{},
|
|
primarySweepID: op1,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
value: 2_000_000,
|
|
timeout: 1100,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantOutputs: []btcutil.Amount{
|
|
2999841, 2999810, 2999773, 2999728, 2999673,
|
|
2999608, 2999530, 2999436, 2999323, 2999188,
|
|
2999026, 2998831, 2998598, 2998317, 2997981,
|
|
2997577, 2997093, 2996512, 2995814, 2994977,
|
|
2993973, 2992768, 2991322, 2989587, 2987505,
|
|
2985006, 2982007, 2978409, 2974091, 2968910,
|
|
2962691, 2955230, 2946276, 2935532, 2922639,
|
|
2907167, 2888600, 2866320, 2839584, 2807501,
|
|
2769001, 2722802,
|
|
},
|
|
wantLockTimes: []uint32{
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 950, 950, 950,
|
|
950, 950, 950, 950, 950, 950, 950, 950, 950,
|
|
950, 950, 950, 950,
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "two sweeps, another primary",
|
|
presigner: &mockPresigner{},
|
|
primarySweepID: op2,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
value: 2_000_000,
|
|
timeout: 1100,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantOutputs: []btcutil.Amount{
|
|
2999841, 2999810, 2999773, 2999728, 2999673,
|
|
2999608, 2999530, 2999436, 2999323, 2999188,
|
|
2999026, 2998831, 2998598, 2998317, 2997981,
|
|
2997577, 2997093, 2996512, 2995814, 2994977,
|
|
2993973, 2992768, 2991322, 2989587, 2987505,
|
|
2985006, 2982007, 2978409, 2974091, 2968910,
|
|
2962691, 2955230, 2946276, 2935532, 2922639,
|
|
2907167, 2888600, 2866320, 2839584, 2807501,
|
|
2769001, 2722802,
|
|
},
|
|
wantLockTimes: []uint32{
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 950, 950, 950,
|
|
950, 950, 950, 950, 950, 950, 950, 950, 950,
|
|
950, 950, 950, 950,
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "timeout < 50",
|
|
presigner: &mockPresigner{},
|
|
primarySweepID: op1,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 40,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
value: 2_000_000,
|
|
timeout: 40,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: 50 * chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantOutputs: []btcutil.Amount{
|
|
2999841, 2999810, 2999773, 2999728, 2999673,
|
|
2999608, 2999530, 2999436, 2999323, 2999188,
|
|
2999026, 2998831, 2998598, 2998317, 2997981,
|
|
2997577, 2997093, 2996512, 2995814, 2994977,
|
|
2993973, 2992768, 2991322, 2989587, 2987505,
|
|
2985006, 2982007, 2978409, 2974091, 2968910,
|
|
2962691, 2955230, 2946276, 2935532, 2922639,
|
|
2907167, 2888600, 2866320, 2839584, 2807501,
|
|
2769001, 2722802,
|
|
},
|
|
wantLockTimes: []uint32{
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "high current feerate => locktime later",
|
|
presigner: &mockPresigner{},
|
|
primarySweepID: op1,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000_000,
|
|
timeout: 1000,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
value: 2_000_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: 50 * chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantOutputs: []btcutil.Amount{
|
|
2999841, 2999810, 2999773, 2999728, 2999673,
|
|
2999608, 2999530, 2999436, 2999323, 2999188,
|
|
2999026, 2998831, 2998598, 2998317, 2997981,
|
|
2997577, 2997093, 2996512, 2995814, 2994977,
|
|
2993973, 2992768, 2991322, 2989587, 2987505,
|
|
2985006, 2982007, 2978409, 2974091, 2968910,
|
|
2962691, 2955230, 2946276, 2935532, 2922639,
|
|
2907167, 2888600, 2866320, 2839584, 2807501,
|
|
2769001, 2722802,
|
|
},
|
|
wantLockTimes: []uint32{
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 950, 950, 950, 950, 950, 950, 950,
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "small amount => fewer steps until " +
|
|
"clamped",
|
|
presigner: &mockPresigner{},
|
|
primarySweepID: op1,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000,
|
|
timeout: 1000,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
value: 2_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantOutputs: []btcutil.Amount{
|
|
2841, 2810, 2773, 2728, 2673, 2608, 2530, 2436,
|
|
2400,
|
|
},
|
|
wantLockTimes: []uint32{
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "third signing fails",
|
|
presigner: &mockPresigner{
|
|
failAt: 3,
|
|
},
|
|
primarySweepID: op1,
|
|
sweeps: []sweep{
|
|
{
|
|
outpoint: op1,
|
|
value: 1_000,
|
|
timeout: 1000,
|
|
},
|
|
{
|
|
outpoint: op2,
|
|
value: 2_000,
|
|
timeout: 1000,
|
|
},
|
|
},
|
|
destAddr: destAddr,
|
|
nextBlockFeeRate: chainfee.FeePerKwFloor,
|
|
minRelayFeeRate: chainfee.FeePerKwFloor,
|
|
wantErr: "test error in SignTx",
|
|
},
|
|
}
|
|
|
|
type pair struct {
|
|
output btcutil.Amount
|
|
lockTime uint32
|
|
}
|
|
|
|
zip := func(outputs []btcutil.Amount, lockTimes []uint32) []pair {
|
|
require.Equal(t, len(outputs), len(lockTimes))
|
|
|
|
pairs := make([]pair, len(outputs))
|
|
for i, output := range outputs {
|
|
pairs[i] = pair{
|
|
output: output,
|
|
lockTime: lockTimes[i],
|
|
}
|
|
}
|
|
|
|
return pairs
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := presign(
|
|
ctx, tc.presigner, tc.destAddr,
|
|
tc.primarySweepID, tc.sweeps,
|
|
tc.nextBlockFeeRate, tc.minRelayFeeRate,
|
|
)
|
|
if tc.wantErr != "" {
|
|
require.Error(t, err)
|
|
require.ErrorContains(t, err, tc.wantErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
p := tc.presigner.(*mockPresigner)
|
|
require.ElementsMatch(
|
|
t,
|
|
zip(tc.wantOutputs, tc.wantLockTimes),
|
|
zip(p.outputs, p.lockTimes),
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestCheckSignedTx tests that function CheckSignedTx checks all the criteria
|
|
// of PresignedHelper.SignTx correctly.
|
|
func TestCheckSignedTx(t *testing.T) {
|
|
// Prepare the necessary data for test cases.
|
|
op1 := wire.OutPoint{
|
|
Hash: chainhash.Hash{1, 1, 1},
|
|
Index: 1,
|
|
}
|
|
op2 := wire.OutPoint{
|
|
Hash: chainhash.Hash{2, 2, 2},
|
|
Index: 2,
|
|
}
|
|
|
|
batchPkScript, err := txscript.PayToAddrScript(destAddr)
|
|
require.NoError(t, err)
|
|
|
|
cases := []struct {
|
|
name string
|
|
unsignedTx *wire.MsgTx
|
|
signedTx *wire.MsgTx
|
|
inputAmt btcutil.Amount
|
|
minRelayFee chainfee.SatPerKWeight
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "success",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
},
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "",
|
|
},
|
|
|
|
{
|
|
name: "unsigned input in signedTx",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
},
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "is not signed",
|
|
},
|
|
|
|
{
|
|
name: "bad locktime",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
},
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_001,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "locktime",
|
|
},
|
|
|
|
{
|
|
name: "bad version",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
},
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 3,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "version",
|
|
},
|
|
|
|
{
|
|
name: "missing input",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
},
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "is missing in signed tx",
|
|
},
|
|
|
|
{
|
|
name: "extra input",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "is new in signed tx",
|
|
},
|
|
|
|
{
|
|
name: "mismatch of sequence numbers",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
},
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 3,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "sequence mismatch",
|
|
},
|
|
|
|
{
|
|
name: "extra output in unsignedTx",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
},
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "unsigned tx has 2 outputs, signed tx " +
|
|
"has 1 outputs, should be equal",
|
|
},
|
|
|
|
{
|
|
name: "extra output in signedTx",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
},
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "unsigned tx has 1 outputs, signed tx " +
|
|
"has 2 outputs, should be equal",
|
|
},
|
|
|
|
{
|
|
name: "pkscript mismatch",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: []byte{0xaf, 0xfe}, // Just to make it different.
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "mismatch of output pkScript",
|
|
},
|
|
|
|
{
|
|
name: "value mismatch, first output",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 1_337_000, // Just to make it different.
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "",
|
|
},
|
|
|
|
{
|
|
name: "value mismatch, change output",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
{
|
|
Value: 1_337_000,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2_493_300,
|
|
PkScript: batchPkScript,
|
|
},
|
|
{
|
|
Value: 1_338, // Just to make it different.
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "mismatch of output value",
|
|
},
|
|
|
|
{
|
|
name: "mismatch of output pk_script",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
},
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript[1:],
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 253,
|
|
wantErr: "mismatch of output pkScript",
|
|
},
|
|
|
|
{
|
|
name: "too low feerate in signedTx",
|
|
unsignedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
},
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 800_000,
|
|
},
|
|
signedTx: &wire.MsgTx{
|
|
Version: 2,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: op2,
|
|
Sequence: 2,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
{
|
|
PreviousOutPoint: op1,
|
|
Sequence: 1,
|
|
Witness: wire.TxWitness{
|
|
[]byte("test"),
|
|
},
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 2999374,
|
|
PkScript: batchPkScript,
|
|
},
|
|
},
|
|
LockTime: 799_999,
|
|
},
|
|
inputAmt: 3_000_000,
|
|
minRelayFee: 250_000,
|
|
wantErr: "is lower than minRelayFee",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := CheckSignedTx(
|
|
tc.unsignedTx, tc.signedTx, tc.inputAmt,
|
|
tc.minRelayFee,
|
|
)
|
|
if tc.wantErr != "" {
|
|
require.Error(t, err)
|
|
require.ErrorContains(t, err, tc.wantErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|