lbcwallet/wallet/createtx_test.go
2022-09-28 11:48:23 -07:00

301 lines
7.5 KiB
Go

// Copyright (c) 2018 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package wallet
import (
"bytes"
"testing"
"time"
"github.com/lbryio/lbcd/chaincfg"
"github.com/lbryio/lbcd/chaincfg/chainhash"
"github.com/lbryio/lbcd/txscript"
"github.com/lbryio/lbcd/wire"
btcutil "github.com/lbryio/lbcutil"
"github.com/lbryio/lbcwallet/waddrmgr"
"github.com/lbryio/lbcwallet/wallet/txauthor"
"github.com/lbryio/lbcwallet/walletdb"
_ "github.com/lbryio/lbcwallet/walletdb/bdb"
"github.com/lbryio/lbcwallet/wtxmgr"
"github.com/stretchr/testify/require"
)
var (
testBlockHash, _ = chainhash.NewHashFromStr(
"00000000000000017188b968a371bab95aa43522665353b646e41865abae" +
"02a4",
)
testBlockHeight int32 = 276425
)
// TestTxToOutput checks that no new address is added to he database if we
// request a dry run of the txToOutputs call. It also makes sure a subsequent
// non-dry run call produces a similar transaction to the dry-run.
func TestTxToOutputsDryRun(t *testing.T) {
w, cleanup := testWallet(t)
defer cleanup()
// Create an address we can use to send some coins to.
keyScope := waddrmgr.KeyScopeBIP0049
addr, err := w.CurrentAddress(0, keyScope)
if err != nil {
t.Fatalf("unable to get current address: %v", addr)
}
p2shAddr, err := txscript.PayToAddrScript(addr)
if err != nil {
t.Fatalf("unable to convert wallet address to p2sh: %v", err)
}
// Add an output paying to the wallet's address to the database.
txOut := wire.NewTxOut(100000, p2shAddr)
incomingTx := &wire.MsgTx{
TxIn: []*wire.TxIn{
{},
},
TxOut: []*wire.TxOut{
txOut,
},
}
addUtxo(t, w, incomingTx)
// Now tell the wallet to create a transaction paying to the specified
// outputs.
txOuts := []*wire.TxOut{
{
PkScript: p2shAddr,
Value: 10000,
},
{
PkScript: p2shAddr,
Value: 20000,
},
}
// First do a few dry-runs, making sure the number of addresses in the
// database us not inflated.
dryRunTx, err := w.txToOutputs(
txOuts, nil, 0, 1, 1000, CoinSelectionLargest, true,
)
if err != nil {
t.Fatalf("unable to author tx: %v", err)
}
change := dryRunTx.Tx.TxOut[dryRunTx.ChangeIndex]
addresses, err := w.AccountAddresses(0, nil)
if err != nil {
t.Fatalf("unable to get addresses: %v", err)
}
if len(addresses) != 1 {
t.Fatalf("expected 1 address, found %v", len(addresses))
}
dryRunTx2, err := w.txToOutputs(
txOuts, nil, 0, 1, 1000, CoinSelectionLargest, true,
)
if err != nil {
t.Fatalf("unable to author tx: %v", err)
}
change2 := dryRunTx2.Tx.TxOut[dryRunTx2.ChangeIndex]
addresses, err = w.AccountAddresses(0, nil)
if err != nil {
t.Fatalf("unable to get addresses: %v", err)
}
if len(addresses) != 1 {
t.Fatalf("expected 1 address, found %v", len(addresses))
}
// The two dry-run TXs should be invalid, since they don't have
// signatures.
err = validateMsgTx(
dryRunTx.Tx, dryRunTx.PrevScripts, dryRunTx.PrevInputValues,
)
if err == nil {
t.Fatalf("Expected tx to be invalid")
}
err = validateMsgTx(
dryRunTx2.Tx, dryRunTx2.PrevScripts, dryRunTx2.PrevInputValues,
)
if err == nil {
t.Fatalf("Expected tx to be invalid")
}
// Now we do a proper, non-dry run. This should add a change address
// to the database.
tx, err := w.txToOutputs(
txOuts, nil, 0, 1, 1000, CoinSelectionLargest, false,
)
if err != nil {
t.Fatalf("unable to author tx: %v", err)
}
change3 := tx.Tx.TxOut[tx.ChangeIndex]
addresses, err = w.AccountAddresses(0, nil)
if err != nil {
t.Fatalf("unable to get addresses: %v", err)
}
if len(addresses) != 2 {
t.Fatalf("expected 2 addresses, found %v", len(addresses))
}
err = validateMsgTx(tx.Tx, tx.PrevScripts, tx.PrevInputValues)
if err != nil {
t.Fatalf("Expected tx to be valid: %v", err)
}
// Finally, we check that all the transaction were using the same
// change address.
if !bytes.Equal(change.PkScript, change2.PkScript) {
t.Fatalf("first dry-run using different change address " +
"than second")
}
if !bytes.Equal(change2.PkScript, change3.PkScript) {
t.Fatalf("dry-run using different change address " +
"than wet run")
}
}
// addUtxo add the given transaction to the wallet's database marked as a
// confirmed UTXO .
func addUtxo(t *testing.T, w *Wallet, incomingTx *wire.MsgTx) {
var b bytes.Buffer
if err := incomingTx.Serialize(&b); err != nil {
t.Fatalf("unable to serialize tx: %v", err)
}
txBytes := b.Bytes()
rec, err := wtxmgr.NewTxRecord(txBytes, time.Now())
if err != nil {
t.Fatalf("unable to create tx record: %v", err)
}
// The block meta will be inserted to tell the wallet this is a
// confirmed transaction.
block := &wtxmgr.BlockMeta{
Block: wtxmgr.Block{
Hash: *testBlockHash,
Height: testBlockHeight,
},
Time: time.Unix(1387737310, 0),
}
if err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(wtxmgrNamespaceKey)
err = w.TxStore.InsertTx(ns, rec, block)
if err != nil {
return err
}
// Add all tx outputs as credits.
for i := 0; i < len(incomingTx.TxOut); i++ {
err = w.TxStore.AddCredit(
ns, rec, block, uint32(i), false,
)
if err != nil {
return err
}
}
return nil
}); err != nil {
t.Fatalf("failed inserting tx: %v", err)
}
}
// TestInputYield verifies the functioning of the inputYieldsPositively.
func _TestInputYield(t *testing.T) {
addr, _ := btcutil.DecodeAddress("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", &chaincfg.MainNetParams)
pkScript, err := txscript.PayToAddrScript(addr)
require.NoError(t, err)
credit := &wtxmgr.Credit{
Amount: 1000,
PkScript: pkScript,
}
// At 10 sat/b this input is yielding positively.
require.True(t, inputYieldsPositively(credit, 10000))
// At 20 sat/b this input is yielding negatively.
require.False(t, inputYieldsPositively(credit, 20000))
}
// TestTxToOutputsRandom tests random coin selection.
func TestTxToOutputsRandom(t *testing.T) {
w, cleanup := testWallet(t)
defer cleanup()
// Create an address we can use to send some coins to.
keyScope := waddrmgr.KeyScopeBIP0049
addr, err := w.CurrentAddress(0, keyScope)
if err != nil {
t.Fatalf("unable to get current address: %v", addr)
}
p2shAddr, err := txscript.PayToAddrScript(addr)
if err != nil {
t.Fatalf("unable to convert wallet address to p2sh: %v", err)
}
// Add a set of utxos to the wallet.
incomingTx := &wire.MsgTx{
TxIn: []*wire.TxIn{
{},
},
TxOut: []*wire.TxOut{},
}
for amt := int64(5000); amt <= 125000; amt += 10000 {
incomingTx.AddTxOut(wire.NewTxOut(amt, p2shAddr))
}
addUtxo(t, w, incomingTx)
// Now tell the wallet to create a transaction paying to the specified
// outputs.
txOuts := []*wire.TxOut{
{
PkScript: p2shAddr,
Value: 50000,
},
{
PkScript: p2shAddr,
Value: 100000,
},
}
const (
feeSatPerKb = 100000
maxIterations = 100
)
createTx := func() *txauthor.AuthoredTx {
tx, err := w.txToOutputs(
txOuts, nil, 0, 1, feeSatPerKb, CoinSelectionRandom, true,
)
require.NoError(t, err)
return tx
}
firstTx := createTx()
var isRandom bool
for iteration := 0; iteration < maxIterations; iteration++ {
tx := createTx()
// Check to see if we are getting a total input value.
// We consider this proof that the randomization works.
if tx.TotalInput != firstTx.TotalInput {
isRandom = true
}
// At the used fee rate of 100 sat/b, the 5000 sat input is
// negatively yielding. We don't expect it to ever be selected.
for _, inputValue := range tx.PrevInputValues {
require.NotEqual(t, inputValue, btcutil.Amount(5000))
}
}
require.True(t, isRandom)
}