2018-12-10 11:57:24 +01:00
|
|
|
// 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"
|
|
|
|
|
2021-08-26 00:58:28 +02:00
|
|
|
"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"
|
2021-03-29 16:36:41 +02:00
|
|
|
"github.com/stretchr/testify/require"
|
2018-12-10 11:57:24 +01:00
|
|
|
)
|
|
|
|
|
2020-08-27 21:14:55 +02:00
|
|
|
var (
|
|
|
|
testBlockHash, _ = chainhash.NewHashFromStr(
|
|
|
|
"00000000000000017188b968a371bab95aa43522665353b646e41865abae" +
|
|
|
|
"02a4",
|
|
|
|
)
|
|
|
|
testBlockHeight int32 = 276425
|
|
|
|
)
|
|
|
|
|
2018-12-10 11:57:24 +01:00
|
|
|
// 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) {
|
2020-04-30 09:08:12 +02:00
|
|
|
w, cleanup := testWallet(t)
|
|
|
|
defer cleanup()
|
2018-12-10 11:57:24 +01:00
|
|
|
|
|
|
|
// Create an address we can use to send some coins to.
|
2021-02-18 01:24:14 +01:00
|
|
|
keyScope := waddrmgr.KeyScopeBIP0049Plus
|
|
|
|
addr, err := w.CurrentAddress(0, keyScope)
|
2018-12-10 11:57:24 +01:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
}
|
2020-08-27 21:14:55 +02:00
|
|
|
addUtxo(t, w, incomingTx)
|
2018-12-10 11:57:24 +01:00
|
|
|
|
|
|
|
// 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.
|
2021-03-29 16:36:41 +02:00
|
|
|
dryRunTx, err := w.txToOutputs(
|
|
|
|
txOuts, nil, 0, 1, 1000, CoinSelectionLargest, true,
|
|
|
|
)
|
2018-12-10 11:57:24 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to author tx: %v", err)
|
|
|
|
}
|
|
|
|
change := dryRunTx.Tx.TxOut[dryRunTx.ChangeIndex]
|
|
|
|
|
|
|
|
addresses, err := w.AccountAddresses(0)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to get addresses: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(addresses) != 1 {
|
|
|
|
t.Fatalf("expected 1 address, found %v", len(addresses))
|
|
|
|
}
|
|
|
|
|
2021-03-29 16:36:41 +02:00
|
|
|
dryRunTx2, err := w.txToOutputs(
|
|
|
|
txOuts, nil, 0, 1, 1000, CoinSelectionLargest, true,
|
|
|
|
)
|
2018-12-10 11:57:24 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to author tx: %v", err)
|
|
|
|
}
|
|
|
|
change2 := dryRunTx2.Tx.TxOut[dryRunTx2.ChangeIndex]
|
|
|
|
|
|
|
|
addresses, err = w.AccountAddresses(0)
|
|
|
|
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.
|
2021-03-29 16:36:41 +02:00
|
|
|
tx, err := w.txToOutputs(
|
|
|
|
txOuts, nil, 0, 1, 1000, CoinSelectionLargest, false,
|
|
|
|
)
|
2018-12-10 11:57:24 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to author tx: %v", err)
|
|
|
|
}
|
|
|
|
change3 := tx.Tx.TxOut[tx.ChangeIndex]
|
|
|
|
|
|
|
|
addresses, err = w.AccountAddresses(0)
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
2020-08-27 21:14:55 +02:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2021-03-29 16:36:41 +02:00
|
|
|
// 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
|
|
|
|
}
|
2020-08-27 21:14:55 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
t.Fatalf("failed inserting tx: %v", err)
|
|
|
|
}
|
|
|
|
}
|
2021-03-29 16:36:41 +02:00
|
|
|
|
|
|
|
// 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.KeyScopeBIP0049Plus
|
|
|
|
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)
|
|
|
|
}
|