mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
Merge pull request #8345 from yyforyongyu/use-testmempoolaccept
lnwallet: perform mempool acceptance check before publishing
This commit is contained in:
commit
d44c72b979
@ -548,9 +548,10 @@ func (c *ChainArbitrator) Start() error {
|
||||
c.activeChannels[chanPoint] = channelArb
|
||||
|
||||
// Republish any closing transactions for this channel.
|
||||
err = c.publishClosingTxs(channel)
|
||||
err = c.republishClosingTxs(channel)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Errorf("Failed to republish closing txs for "+
|
||||
"channel %v", chanPoint)
|
||||
}
|
||||
}
|
||||
|
||||
@ -825,10 +826,10 @@ func (c *ChainArbitrator) dispatchBlocks(
|
||||
}
|
||||
}
|
||||
|
||||
// publishClosingTxs will load any stored cooperative or unilater closing
|
||||
// republishClosingTxs will load any stored cooperative or unilateral closing
|
||||
// transactions and republish them. This helps ensure propagation of the
|
||||
// transactions in the event that prior publications failed.
|
||||
func (c *ChainArbitrator) publishClosingTxs(
|
||||
func (c *ChainArbitrator) republishClosingTxs(
|
||||
channel *channeldb.OpenChannel) error {
|
||||
|
||||
// If the channel has had its unilateral close broadcasted already,
|
||||
|
@ -115,6 +115,12 @@
|
||||
without the user needing to publicly give away a lot of privacy-sensitive
|
||||
data.
|
||||
|
||||
* When publishing transactions in `lnd`, all the transactions will now go
|
||||
through [mempool acceptance
|
||||
check](https://github.com/lightningnetwork/lnd/pull/8345) before being
|
||||
broadcast. This means when a transaction has failed the `testmempoolaccept`
|
||||
check by bitcoind or btcd, the broadcast won't be attempted.
|
||||
|
||||
## RPC Additions
|
||||
|
||||
* [Deprecated](https://github.com/lightningnetwork/lnd/pull/7175)
|
||||
|
2
go.mod
2
go.mod
@ -10,7 +10,7 @@ require (
|
||||
github.com/btcsuite/btcd/btcutil/psbt v1.1.8
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
|
||||
github.com/btcsuite/btcwallet v0.16.10-0.20231129183218-5df09dd43358
|
||||
github.com/btcsuite/btcwallet v0.16.10-0.20240122184004-f1648e92097f
|
||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2
|
||||
github.com/btcsuite/btcwallet/wallet/txrules v1.2.0
|
||||
github.com/btcsuite/btcwallet/walletdb v1.4.0
|
||||
|
4
go.sum
4
go.sum
@ -95,8 +95,8 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtyd
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcwallet v0.16.10-0.20231129183218-5df09dd43358 h1:lZUSo6TISHUJQxpn/AniW5gqaN1iRNS87SDWvV3AHfg=
|
||||
github.com/btcsuite/btcwallet v0.16.10-0.20231129183218-5df09dd43358/go.mod h1:WSKhOJWUmUOHKCKEzdt+jWAHFAE/t4RqVbCwL2pEdiU=
|
||||
github.com/btcsuite/btcwallet v0.16.10-0.20240122184004-f1648e92097f h1:wtA3/5W+SAIQaWEuvnjMJqWEPjf2mWupXBB6KHbFKYA=
|
||||
github.com/btcsuite/btcwallet v0.16.10-0.20240122184004-f1648e92097f/go.mod h1:LzcW/LYkQLgDufv6Ouw4cOIW0YsY+A60MTtc61/OZTU=
|
||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 h1:etuLgGEojecsDOYTII8rYiGHjGyV5xTqsXi+ZQ715UU=
|
||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2/go.mod h1:Zpk/LOb2sKqwP2lmHjaZT9AdaKsHPSbNLm2Uql5IQ/0=
|
||||
github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg=
|
||||
|
21
lnutils/errors.go
Normal file
21
lnutils/errors.go
Normal file
@ -0,0 +1,21 @@
|
||||
package lnutils
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrorAs behaves the same as `errors.As` except there's no need to declare
|
||||
// the target error as a variable first.
|
||||
// Instead of writing:
|
||||
//
|
||||
// var targetErr *TargetErr
|
||||
// errors.As(err, &targetErr)
|
||||
//
|
||||
// We can write:
|
||||
//
|
||||
// lnutils.ErrorAs[*TargetErr](err)
|
||||
//
|
||||
// To save us from declaring the target error variable.
|
||||
func ErrorAs[Target error](err error) bool {
|
||||
var targetErr Target
|
||||
|
||||
return errors.As(err, &targetErr)
|
||||
}
|
@ -23,10 +23,12 @@ import (
|
||||
"github.com/btcsuite/btcwallet/wallet/txrules"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/blockcache"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnutils"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
@ -1199,33 +1201,102 @@ func (b *BtcWallet) ListUnspentWitness(minConfs, maxConfs int32,
|
||||
// already published to the network (either in the mempool or chain) no error
|
||||
// will be returned.
|
||||
func (b *BtcWallet) PublishTransaction(tx *wire.MsgTx, label string) error {
|
||||
if err := b.wallet.PublishTransaction(tx, label); err != nil {
|
||||
// handleErr is a helper closure that handles the error from
|
||||
// PublishTransaction and TestMempoolAccept.
|
||||
handleErr := func(err error) error {
|
||||
// If we failed to publish the transaction, check whether we
|
||||
// got an error of known type.
|
||||
switch err.(type) {
|
||||
switch {
|
||||
// If the wallet reports a double spend, convert it to our
|
||||
// internal ErrDoubleSpend and return.
|
||||
case *base.ErrDoubleSpend:
|
||||
case lnutils.ErrorAs[*base.ErrDoubleSpend](err):
|
||||
return lnwallet.ErrDoubleSpend
|
||||
|
||||
// If the wallet reports a replacement error, return
|
||||
// ErrDoubleSpend, as we currently are never attempting to
|
||||
// replace transactions.
|
||||
case *base.ErrReplacement:
|
||||
case lnutils.ErrorAs[*base.ErrReplacement](err):
|
||||
return lnwallet.ErrDoubleSpend
|
||||
|
||||
// If the wallet reports that fee requirements for accepting the
|
||||
// tx into mempool are not met, convert it to our internal
|
||||
// If the wallet reports that fee requirements for accepting
|
||||
// the tx into mempool are not met, convert it to our internal
|
||||
// ErrMempoolFee and return.
|
||||
case *base.ErrMempoolFee:
|
||||
case lnutils.ErrorAs[*base.ErrMempoolFee](err):
|
||||
return fmt.Errorf("%w: %v", lnwallet.ErrMempoolFee,
|
||||
err.Error())
|
||||
|
||||
default:
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
// For neutrino backend there's no mempool, so we return early by
|
||||
// publishing the transaction.
|
||||
if b.chain.BackEnd() == "neutrino" {
|
||||
err := b.wallet.PublishTransaction(tx, label)
|
||||
|
||||
return handleErr(err)
|
||||
}
|
||||
|
||||
// For non-neutrino nodes, we will first check whether the transaction
|
||||
// can be accepted by the mempool.
|
||||
// Use a max feerate of 0 means the default value will be used when
|
||||
// testing mempool acceptance. The default max feerate is 0.10 BTC/kvb,
|
||||
// or 10,000 sat/vb.
|
||||
results, err := b.chain.TestMempoolAccept([]*wire.MsgTx{tx}, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sanity check that the expected single result is returned.
|
||||
if len(results) != 1 {
|
||||
return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
|
||||
"instead got %v", len(results))
|
||||
}
|
||||
|
||||
result := results[0]
|
||||
log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
|
||||
|
||||
// Once mempool check passed, we can publish the transaction.
|
||||
if result.Allowed {
|
||||
err = b.wallet.PublishTransaction(tx, label)
|
||||
|
||||
return handleErr(err)
|
||||
}
|
||||
|
||||
// If the check failed, there's no need to publish it. We'll handle the
|
||||
// error and return.
|
||||
log.Warnf("Transaction %v not accepted by mempool: %v",
|
||||
tx.TxHash(), result.RejectReason)
|
||||
|
||||
// We need to use the string to create an error type and map it to a
|
||||
// btcwallet error.
|
||||
err = base.MapBroadcastBackendError(errors.New(result.RejectReason))
|
||||
|
||||
//nolint:lll
|
||||
// These two errors are ignored inside `PublishTransaction`:
|
||||
// https://github.com/btcsuite/btcwallet/blob/master/wallet/wallet.go#L3763
|
||||
// To keep our current behavior, we need to ignore the same errors
|
||||
// returned from TestMempoolAccept.
|
||||
//
|
||||
// TODO(yy): since `LightningWallet.PublishTransaction` always publish
|
||||
// the same tx twice, we'd always get ErrInMempool. We should instead
|
||||
// create a new rebroadcaster that monitors the mempool, and only
|
||||
// rebroadcast when the tx is evicted. This way we don't need to
|
||||
// broadcast twice, and can instead return these errors here.
|
||||
switch {
|
||||
// NOTE: In addition to ignoring these errors, we need to call
|
||||
// `PublishTransaction` again because we need to mark the label in the
|
||||
// wallet. We can remove this exception once we have the above TODO
|
||||
// fixed.
|
||||
case lnutils.ErrorAs[*base.ErrInMempool](err),
|
||||
lnutils.ErrorAs[*base.ErrAlreadyConfirmed](err):
|
||||
|
||||
err := b.wallet.PublishTransaction(tx, label)
|
||||
|
||||
return handleErr(err)
|
||||
}
|
||||
|
||||
return handleErr(err)
|
||||
}
|
||||
|
||||
// LabelTransaction adds a label to a transaction. If the tx already
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
@ -1471,15 +1472,20 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
|
||||
// We'll also ensure that the client is able to send our new
|
||||
// notifications when we _create_ transactions ourselves that spend our
|
||||
// own outputs.
|
||||
b := txscript.NewScriptBuilder()
|
||||
b.AddOp(txscript.OP_RETURN)
|
||||
outputScript, err := b.Script()
|
||||
require.NoError(t, err, "unable to make output script")
|
||||
addr, err := alice.NewAddress(
|
||||
lnwallet.WitnessPubKey, false,
|
||||
lnwallet.DefaultAccountName,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
outputScript, err := txscript.PayToAddrScript(addr)
|
||||
require.NoError(t, err)
|
||||
|
||||
burnOutput := wire.NewTxOut(outputAmt, outputScript)
|
||||
tx, err := alice.SendOutputs(
|
||||
[]*wire.TxOut{burnOutput}, 2500, 1, labels.External,
|
||||
)
|
||||
require.NoError(t, err, "unable to create burn tx")
|
||||
require.NoError(t, err, "unable to create tx")
|
||||
txid := tx.TxHash()
|
||||
err = waitForMempoolTx(miner, &txid)
|
||||
require.NoError(t, err, "tx not relayed to miner")
|
||||
@ -1721,187 +1727,206 @@ func testPublishTransaction(r *rpctest.Harness,
|
||||
keyDesc, err := alice.DeriveNextKey(keychain.KeyFamilyMultiSig)
|
||||
require.NoError(t, err, "unable to obtain public key")
|
||||
|
||||
// We will first check that publishing a transaction already in the
|
||||
// mempool does NOT return an error. Create the tx.
|
||||
tx1 := newTx(t, r, keyDesc.PubKey, alice, false)
|
||||
t.Run("no_error_on_duplicate_tx", func(t *testing.T) {
|
||||
// We will first check that publishing a transaction already in
|
||||
// the mempool does NOT return an error. Create the tx.
|
||||
tx1 := newTx(t, r, keyDesc.PubKey, alice, false)
|
||||
|
||||
// Publish the transaction.
|
||||
err = alice.PublishTransaction(tx1, labels.External)
|
||||
require.NoError(t, err, "unable to publish")
|
||||
// Publish the transaction.
|
||||
err = alice.PublishTransaction(tx1, labels.External)
|
||||
require.NoError(t, err)
|
||||
|
||||
txid1 := tx1.TxHash()
|
||||
err = waitForMempoolTx(r, &txid1)
|
||||
require.NoError(t, err, "tx not relayed to miner")
|
||||
txid1 := tx1.TxHash()
|
||||
err = waitForMempoolTx(r, &txid1)
|
||||
require.NoError(t, err, "tx not relayed to miner")
|
||||
|
||||
// Publish the exact same transaction again. This should not return an
|
||||
// error, even though the transaction is already in the mempool.
|
||||
err = alice.PublishTransaction(tx1, labels.External)
|
||||
require.NoError(t, err, "unable to publish")
|
||||
|
||||
// Mine the transaction.
|
||||
if _, err := r.Client.Generate(1); err != nil {
|
||||
t.Fatalf("unable to generate block: %v", err)
|
||||
}
|
||||
|
||||
// We'll now test that we don't get an error if we try to publish a
|
||||
// transaction that is already mined.
|
||||
//
|
||||
// Create a new transaction. We must do this to properly test the
|
||||
// reject messages from our peers. They might only send us a reject
|
||||
// message for a given tx once, so we create a new to make sure it is
|
||||
// not just immediately rejected.
|
||||
tx2 := newTx(t, r, keyDesc.PubKey, alice, false)
|
||||
|
||||
// Publish this tx.
|
||||
err = alice.PublishTransaction(tx2, labels.External)
|
||||
require.NoError(t, err, "unable to publish")
|
||||
|
||||
// Mine the transaction.
|
||||
if err := mineAndAssert(r, tx2); err != nil {
|
||||
t.Fatalf("unable to mine tx: %v", err)
|
||||
}
|
||||
|
||||
// Publish the transaction again. It is already mined, and we don't
|
||||
// expect this to return an error.
|
||||
err = alice.PublishTransaction(tx2, labels.External)
|
||||
require.NoError(t, err, "unable to publish")
|
||||
|
||||
// We'll do the next mempool check on both RBF and non-RBF enabled
|
||||
// transactions.
|
||||
var (
|
||||
txFee = btcutil.Amount(0.005 * btcutil.SatoshiPerBitcoin)
|
||||
tx3, tx3Spend *wire.MsgTx
|
||||
)
|
||||
|
||||
for _, rbf := range []bool{false, true} {
|
||||
// Now we'll try to double spend an output with a different
|
||||
// transaction. Create a new tx and publish it. This is the
|
||||
// output we'll try to double spend.
|
||||
tx3 = newTx(t, r, keyDesc.PubKey, alice, false)
|
||||
err := alice.PublishTransaction(tx3, labels.External)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to publish: %v", err)
|
||||
}
|
||||
// Publish the exact same transaction again. This should not
|
||||
// return an error, even though the transaction is already in
|
||||
// the mempool.
|
||||
err = alice.PublishTransaction(tx1, labels.External)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Mine the transaction.
|
||||
if err := mineAndAssert(r, tx3); err != nil {
|
||||
t.Fatalf("unable to mine tx: %v", err)
|
||||
}
|
||||
_, err := r.Client.Generate(1)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Now we create a transaction that spends the output from the
|
||||
// tx just mined.
|
||||
tx4, err := txFromOutput(
|
||||
tx3, alice.Cfg.Signer, keyDesc.PubKey,
|
||||
keyDesc.PubKey, txFee, rbf,
|
||||
t.Run("no_error_on_minged_tx", func(t *testing.T) {
|
||||
// We'll now test that we don't get an error if we try to
|
||||
// publish a transaction that is already mined.
|
||||
//
|
||||
// Create a new transaction. We must do this to properly test
|
||||
// the reject messages from our peers. They might only send us
|
||||
// a reject message for a given tx once, so we create a new to
|
||||
// make sure it is not just immediately rejected.
|
||||
tx2 := newTx(t, r, keyDesc.PubKey, alice, false)
|
||||
|
||||
// Publish this tx.
|
||||
err = alice.PublishTransaction(tx2, labels.External)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Mine the transaction.
|
||||
err := mineAndAssert(r, tx2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Publish the transaction again. It is already mined, and we
|
||||
// don't expect this to return an error.
|
||||
err = alice.PublishTransaction(tx2, labels.External)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// We'll do the next mempool check on both RBF and non-RBF
|
||||
// enabled transactions.
|
||||
var (
|
||||
txFee = btcutil.Amount(
|
||||
0.005 * btcutil.SatoshiPerBitcoin,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
tx3, tx3Spend *wire.MsgTx
|
||||
)
|
||||
t.Run("rbf_tests", func(t *testing.T) {
|
||||
for _, rbf := range []bool{false, true} {
|
||||
// Now we'll try to double spend an output with a
|
||||
// different transaction. Create a new tx and publish
|
||||
// it. This is the output we'll try to double spend.
|
||||
tx3 = newTx(t, r, keyDesc.PubKey, alice, false)
|
||||
err := alice.PublishTransaction(tx3, labels.External)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Mine the transaction.
|
||||
err = mineAndAssert(r, tx3)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now we create a transaction that spends the output
|
||||
// from the tx just mined.
|
||||
tx4, err := txFromOutput(
|
||||
tx3, alice.Cfg.Signer, keyDesc.PubKey,
|
||||
keyDesc.PubKey, txFee, rbf,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// This should be accepted into the mempool.
|
||||
err = alice.PublishTransaction(tx4, labels.External)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Keep track of the last successfully published tx to
|
||||
// spend tx3.
|
||||
tx3Spend = tx4
|
||||
|
||||
txid4 := tx4.TxHash()
|
||||
err = waitForMempoolTx(r, &txid4)
|
||||
require.NoError(t, err, "tx not relayed to miner")
|
||||
|
||||
// Create a new key we'll pay to, to ensure we create a
|
||||
// unique transaction.
|
||||
keyDesc2, err := alice.DeriveNextKey(
|
||||
keychain.KeyFamilyMultiSig,
|
||||
)
|
||||
require.NoError(t, err, "unable to obtain public key")
|
||||
|
||||
// Create a new transaction that spends the output from
|
||||
// tx3, and that pays to a different address. We expect
|
||||
// this to be rejected because it is a double spend.
|
||||
tx5, err := txFromOutput(
|
||||
tx3, alice.Cfg.Signer, keyDesc.PubKey,
|
||||
keyDesc2.PubKey, txFee, rbf,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = alice.PublishTransaction(tx5, labels.External)
|
||||
require.ErrorIs(t, err, lnwallet.ErrDoubleSpend)
|
||||
|
||||
// Create another transaction that spends the same
|
||||
// output, but has a higher fee. We expect also this tx
|
||||
// to be rejected for non-RBF enabled transactions,
|
||||
// while it should succeed otherwise.
|
||||
pubKey3, err := alice.DeriveNextKey(
|
||||
keychain.KeyFamilyMultiSig,
|
||||
)
|
||||
require.NoError(t, err, "unable to obtain public key")
|
||||
|
||||
tx6, err := txFromOutput(
|
||||
tx3, alice.Cfg.Signer, keyDesc.PubKey,
|
||||
pubKey3.PubKey, 2*txFee, rbf,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect rejection in non-RBF case.
|
||||
expErr := lnwallet.ErrDoubleSpend
|
||||
if rbf {
|
||||
// Expect success in rbf case.
|
||||
expErr = nil
|
||||
tx3Spend = tx6
|
||||
}
|
||||
err = alice.PublishTransaction(tx6, labels.External)
|
||||
require.ErrorIs(t, err, expErr)
|
||||
|
||||
// Mine the tx spending tx3.
|
||||
err = mineAndAssert(r, tx3Spend)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("tx_double_spend", func(t *testing.T) {
|
||||
// At last we try to spend an output already spent by a
|
||||
// confirmed transaction.
|
||||
//
|
||||
// TODO(halseth): we currently skip this test for neutrino, as
|
||||
// the backing btcd node will consider the tx being an orphan,
|
||||
// and will accept it. Should look into if this is the behavior
|
||||
// also for bitcoind, and update test accordingly.
|
||||
if alice.BackEnd() != "neutrino" {
|
||||
// Create another tx spending tx3.
|
||||
pubKey4, err := alice.DeriveNextKey(
|
||||
keychain.KeyFamilyMultiSig,
|
||||
)
|
||||
require.NoError(t, err, "unable to obtain public key")
|
||||
|
||||
tx7, err := txFromOutput(
|
||||
tx3, alice.Cfg.Signer, keyDesc.PubKey,
|
||||
pubKey4.PubKey, txFee, false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect rejection.
|
||||
err = alice.PublishTransaction(tx7, labels.External)
|
||||
require.ErrorIs(t, err, lnwallet.ErrDoubleSpend)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test_tx_size_limit", func(t *testing.T) {
|
||||
// In this test, we'll try to create a massive transaction that
|
||||
// can't be mined as dictacted by widely deployed transaction
|
||||
// policy.
|
||||
//
|
||||
// To do this, we'll take out of the prior transactions, and
|
||||
// add a bunch of outputs, putting it over the max weight
|
||||
// limit.
|
||||
testTx := tx3.Copy()
|
||||
for i := 0; i < blockchain.MaxOutputsPerBlock; i++ {
|
||||
testTx.AddTxOut(&wire.TxOut{
|
||||
Value: tx3.TxOut[0].Value,
|
||||
PkScript: tx3.TxOut[0].PkScript,
|
||||
})
|
||||
}
|
||||
|
||||
// This should be accepted into the mempool.
|
||||
err = alice.PublishTransaction(tx4, labels.External)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to publish: %v", err)
|
||||
// Now broadcast the transaction, we should get an error that
|
||||
// the weight is too large.
|
||||
//
|
||||
// TODO(roasbeef): we can't use Unwrap() here as TxRuleError
|
||||
// doesn't define it
|
||||
err := alice.PublishTransaction(testTx, labels.External)
|
||||
|
||||
errStr := "bad-txns-oversize"
|
||||
|
||||
// For neutrino backend, the error string in not mapped.
|
||||
//
|
||||
// TODO(yy): unify error matching in neutrino too.
|
||||
if alice.BackEnd() == "neutrino" {
|
||||
errStr = "serialized transaction is too big"
|
||||
}
|
||||
|
||||
// Keep track of the last successfully published tx to spend
|
||||
// tx3.
|
||||
tx3Spend = tx4
|
||||
|
||||
txid4 := tx4.TxHash()
|
||||
err = waitForMempoolTx(r, &txid4)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
// Create a new key we'll pay to, to ensure we create a unique
|
||||
// transaction.
|
||||
keyDesc2, err := alice.DeriveNextKey(
|
||||
keychain.KeyFamilyMultiSig,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to obtain public key: %v", err)
|
||||
}
|
||||
|
||||
// Create a new transaction that spends the output from tx3,
|
||||
// and that pays to a different address. We expect this to be
|
||||
// rejected because it is a double spend.
|
||||
tx5, err := txFromOutput(
|
||||
tx3, alice.Cfg.Signer, keyDesc.PubKey,
|
||||
keyDesc2.PubKey, txFee, rbf,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = alice.PublishTransaction(tx5, labels.External)
|
||||
if err != lnwallet.ErrDoubleSpend {
|
||||
t.Fatalf("expected ErrDoubleSpend, got: %v", err)
|
||||
}
|
||||
|
||||
// Create another transaction that spends the same output, but
|
||||
// has a higher fee. We expect also this tx to be rejected for
|
||||
// non-RBF enabled transactions, while it should succeed
|
||||
// otherwise.
|
||||
pubKey3, err := alice.DeriveNextKey(keychain.KeyFamilyMultiSig)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to obtain public key: %v", err)
|
||||
}
|
||||
tx6, err := txFromOutput(
|
||||
tx3, alice.Cfg.Signer, keyDesc.PubKey,
|
||||
pubKey3.PubKey, 2*txFee, rbf,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Expect rejection in non-RBF case.
|
||||
expErr := lnwallet.ErrDoubleSpend
|
||||
if rbf {
|
||||
// Expect success in rbf case.
|
||||
expErr = nil
|
||||
tx3Spend = tx6
|
||||
}
|
||||
err = alice.PublishTransaction(tx6, labels.External)
|
||||
if err != expErr {
|
||||
t.Fatalf("expected ErrDoubleSpend, got: %v", err)
|
||||
}
|
||||
|
||||
// Mine the tx spending tx3.
|
||||
if err := mineAndAssert(r, tx3Spend); err != nil {
|
||||
t.Fatalf("unable to mine tx: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// At last we try to spend an output already spent by a confirmed
|
||||
// transaction.
|
||||
// TODO(halseth): we currently skip this test for neutrino, as the
|
||||
// backing btcd node will consider the tx being an orphan, and will
|
||||
// accept it. Should look into if this is the behavior also for
|
||||
// bitcoind, and update test accordingly.
|
||||
if alice.BackEnd() != "neutrino" {
|
||||
// Create another tx spending tx3.
|
||||
pubKey4, err := alice.DeriveNextKey(
|
||||
keychain.KeyFamilyMultiSig,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to obtain public key: %v", err)
|
||||
}
|
||||
tx7, err := txFromOutput(
|
||||
tx3, alice.Cfg.Signer, keyDesc.PubKey,
|
||||
pubKey4.PubKey, txFee, false,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Expect rejection.
|
||||
err = alice.PublishTransaction(tx7, labels.External)
|
||||
if err != lnwallet.ErrDoubleSpend {
|
||||
t.Fatalf("expected ErrDoubleSpend, got: %v", err)
|
||||
}
|
||||
}
|
||||
require.Contains(t, err.Error(), errStr)
|
||||
})
|
||||
}
|
||||
|
||||
func testSignOutputUsingTweaks(r *rpctest.Harness,
|
||||
@ -3244,9 +3269,15 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
|
||||
case "bitcoind":
|
||||
// Start a bitcoind instance.
|
||||
tempBitcoindDir := t.TempDir()
|
||||
zmqBlockHost := "ipc:///" + tempBitcoindDir + "/blocks.socket"
|
||||
zmqTxHost := "ipc:///" + tempBitcoindDir + "/tx.socket"
|
||||
|
||||
rpcPort := getFreePort()
|
||||
zmqBlockPort := getFreePort()
|
||||
zmqTxPort := getFreePort()
|
||||
zmqBlockHost := fmt.Sprintf("tcp://127.0.0.1:%d",
|
||||
zmqBlockPort)
|
||||
zmqTxHost := fmt.Sprintf("tcp://127.0.0.1:%d",
|
||||
zmqTxPort)
|
||||
|
||||
bitcoind := exec.Command(
|
||||
"bitcoind",
|
||||
"-datadir="+tempBitcoindDir,
|
||||
|
Loading…
Reference in New Issue
Block a user