rpc: minConfs and spendUnconfirmed for EstimateFee

This commit is contained in:
Tom Kirkpatrick 2021-04-22 19:25:50 +02:00
parent 76706c7473
commit 2f80283ec2
No known key found for this signature in database
GPG Key ID: 72203A8EC5967EA8
8 changed files with 855 additions and 738 deletions

File diff suppressed because it is too large Load Diff

View File

@ -899,6 +899,13 @@ message EstimateFeeRequest {
// The target number of blocks that this transaction should be confirmed // The target number of blocks that this transaction should be confirmed
// by. // by.
int32 target_conf = 2; int32 target_conf = 2;
// The minimum number of confirmations each one of your outputs used for
// the transaction must satisfy.
int32 min_confs = 3;
// Whether unconfirmed outputs should be used as inputs for the transaction.
bool spend_unconfirmed = 4;
} }
message EstimateFeeResponse { message EstimateFeeResponse {

View File

@ -2034,6 +2034,22 @@
"required": false, "required": false,
"type": "integer", "type": "integer",
"format": "int32" "format": "int32"
},
{
"name": "min_confs",
"description": "The minimum number of confirmations each one of your outputs used for\nthe transaction must satisfy.",
"in": "query",
"required": false,
"type": "integer",
"format": "int32"
},
{
"name": "spend_unconfirmed",
"description": "Whether unconfirmed outputs should be used as inputs for the transaction.",
"in": "query",
"required": false,
"type": "boolean",
"format": "boolean"
} }
], ],
"tags": [ "tags": [

View File

@ -107,7 +107,7 @@ func (w *WalletController) SendOutputs(outputs []*wire.TxOut,
// CreateSimpleTx currently returns dummy values. // CreateSimpleTx currently returns dummy values.
func (w *WalletController) CreateSimpleTx(outputs []*wire.TxOut, func (w *WalletController) CreateSimpleTx(outputs []*wire.TxOut,
_ chainfee.SatPerKWeight, _ bool) (*txauthor.AuthoredTx, error) { _ chainfee.SatPerKWeight, _ int32, _ bool) (*txauthor.AuthoredTx, error) {
return nil, nil return nil, nil
} }

View File

@ -519,7 +519,8 @@ func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut,
// //
// This is a part of the WalletController interface. // This is a part of the WalletController interface.
func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut, func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut,
feeRate chainfee.SatPerKWeight, dryRun bool) (*txauthor.AuthoredTx, error) { feeRate chainfee.SatPerKWeight, minConfs int32,
dryRun bool) (*txauthor.AuthoredTx, error) {
// The fee rate is passed in using units of sat/kw, so we'll convert // The fee rate is passed in using units of sat/kw, so we'll convert
// this to sat/KB as the CreateSimpleTx method requires this unit. // this to sat/KB as the CreateSimpleTx method requires this unit.
@ -529,6 +530,12 @@ func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut,
if len(outputs) < 1 { if len(outputs) < 1 {
return nil, lnwallet.ErrNoOutputs return nil, lnwallet.ErrNoOutputs
} }
// Sanity check minConfs.
if minConfs < 0 {
return nil, lnwallet.ErrInvalidMinconf
}
for _, output := range outputs { for _, output := range outputs {
// When checking an output for things like dusty-ness, we'll // When checking an output for things like dusty-ness, we'll
// use the default mempool relay fee rather than the target // use the default mempool relay fee rather than the target
@ -544,7 +551,7 @@ func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut,
} }
return b.wallet.CreateSimpleTx( return b.wallet.CreateSimpleTx(
nil, defaultAccount, outputs, 1, feeSatPerKB, dryRun, nil, defaultAccount, outputs, minConfs, feeSatPerKB, dryRun,
) )
} }

View File

@ -251,7 +251,7 @@ type WalletController interface {
// //
// NOTE: This method requires the global coin selection lock to be held. // NOTE: This method requires the global coin selection lock to be held.
CreateSimpleTx(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, CreateSimpleTx(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
dryRun bool) (*txauthor.AuthoredTx, error) minConfs int32, dryRun bool) (*txauthor.AuthoredTx, error)
// ListUnspentWitness returns all unspent outputs which are version 0 // ListUnspentWitness returns all unspent outputs which are version 0
// witness programs. The 'minConfs' and 'maxConfs' parameters // witness programs. The 'minConfs' and 'maxConfs' parameters

View File

@ -2547,6 +2547,8 @@ func testLastUnusedAddr(miner *rpctest.Harness,
// testCreateSimpleTx checks that a call to CreateSimpleTx will return a // testCreateSimpleTx checks that a call to CreateSimpleTx will return a
// transaction that is equal to the one that is being created by SendOutputs in // transaction that is equal to the one that is being created by SendOutputs in
// a subsequent call. // a subsequent call.
// All test cases are doubled-up: one for testing unconfirmed inputs,
// one for testing only confirmed inputs.
func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet, func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
_ *lnwallet.LightningWallet, t *testing.T) { _ *lnwallet.LightningWallet, t *testing.T) {
@ -2558,51 +2560,108 @@ func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
// The test cases we will run through for all backends. // The test cases we will run through for all backends.
testCases := []struct { testCases := []struct {
outVals []int64 outVals []int64
feeRate chainfee.SatPerKWeight feeRate chainfee.SatPerKWeight
valid bool valid bool
unconfirmed bool
}{ }{
{ {
outVals: []int64{}, outVals: []int64{},
feeRate: 2500, feeRate: 2500,
valid: false, // No outputs. valid: false, // No outputs.
unconfirmed: false,
},
{
outVals: []int64{},
feeRate: 2500,
valid: false, // No outputs.
unconfirmed: true,
}, },
{ {
outVals: []int64{200}, outVals: []int64{200},
feeRate: 2500, feeRate: 2500,
valid: false, // Dust output. valid: false, // Dust output.
unconfirmed: false,
},
{
outVals: []int64{200},
feeRate: 2500,
valid: false, // Dust output.
unconfirmed: true,
}, },
{ {
outVals: []int64{1e8}, outVals: []int64{1e8},
feeRate: 2500, feeRate: 2500,
valid: true, valid: true,
unconfirmed: false,
}, },
{ {
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5}, outVals: []int64{1e8},
feeRate: 2500, feeRate: 2500,
valid: true, valid: true,
unconfirmed: true,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
feeRate: 2500,
valid: true,
unconfirmed: false,
}, },
{ {
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5}, outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
feeRate: 12500, feeRate: 2500,
valid: true, valid: true,
unconfirmed: true,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
feeRate: 12500,
valid: true,
unconfirmed: false,
}, },
{ {
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5}, outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
feeRate: 50000, feeRate: 12500,
valid: true, valid: true,
unconfirmed: true,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
feeRate: 50000,
valid: true,
unconfirmed: false,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
feeRate: 50000,
valid: true,
unconfirmed: true,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5, 1e8, 2e8,
1e8, 2e7, 3e5},
feeRate: 44250,
valid: true,
unconfirmed: false,
}, },
{ {
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5, 1e8, 2e8, outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5, 1e8, 2e8,
1e8, 2e7, 3e5}, 1e8, 2e7, 3e5},
feeRate: 44250, feeRate: 44250,
valid: true, valid: true,
unconfirmed: true,
}, },
} }
for i, test := range testCases { for i, test := range testCases {
var minConfs int32 = 1
feeRate := test.feeRate feeRate := test.feeRate
// Grab some fresh addresses from the miner that we will send // Grab some fresh addresses from the miner that we will send
@ -2629,7 +2688,7 @@ func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
// Now try creating a tx spending to these outputs. // Now try creating a tx spending to these outputs.
createTx, createErr := w.CreateSimpleTx( createTx, createErr := w.CreateSimpleTx(
outputs, feeRate, true, outputs, feeRate, minConfs, true,
) )
switch { switch {
case test.valid && createErr != nil: case test.valid && createErr != nil:
@ -2646,7 +2705,7 @@ func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
// _very_ similar to the one we just created being sent. The // _very_ similar to the one we just created being sent. The
// only difference is that the dry run tx is not signed, and // only difference is that the dry run tx is not signed, and
// that the change output position might be different. // that the change output position might be different.
tx, sendErr := w.SendOutputs(outputs, feeRate, 1, labels.External) tx, sendErr := w.SendOutputs(outputs, feeRate, minConfs, labels.External)
switch { switch {
case test.valid && sendErr != nil: case test.valid && sendErr != nil:
t.Fatalf("got unexpected error when sending tx: %v", t.Fatalf("got unexpected error when sending tx: %v",

View File

@ -975,7 +975,7 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
// We first do a dry run, to sanity check we won't spend our wallet // We first do a dry run, to sanity check we won't spend our wallet
// balance below the reserved amount. // balance below the reserved amount.
authoredTx, err := r.server.cc.Wallet.CreateSimpleTx( authoredTx, err := r.server.cc.Wallet.CreateSimpleTx(
outputs, feeRate, true, outputs, feeRate, minConfs, true,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -1073,12 +1073,21 @@ func (r *rpcServer) EstimateFee(ctx context.Context,
return nil, err return nil, err
} }
// Then, we'll extract the minimum number of confirmations that each
// output we use to fund the transaction should satisfy.
minConfs, err := lnrpc.ExtractMinConfs(
in.GetMinConfs(), in.GetSpendUnconfirmed(),
)
if err != nil {
return nil, err
}
// We will ask the wallet to create a tx using this fee rate. We set // We will ask the wallet to create a tx using this fee rate. We set
// dryRun=true to avoid inflating the change addresses in the db. // dryRun=true to avoid inflating the change addresses in the db.
var tx *txauthor.AuthoredTx var tx *txauthor.AuthoredTx
wallet := r.server.cc.Wallet wallet := r.server.cc.Wallet
err = wallet.WithCoinSelectLock(func() error { err = wallet.WithCoinSelectLock(func() error {
tx, err = wallet.CreateSimpleTx(outputs, feePerKw, true) tx, err = wallet.CreateSimpleTx(outputs, feePerKw, minConfs, true)
return err return err
}) })
if err != nil { if err != nil {