mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
Merge pull request #1228 from halseth/fee-estimation-rpc
Fee estimation RPC
This commit is contained in:
commit
158a32c4e1
@ -144,6 +144,52 @@ func newAddress(ctx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var estimateFeeCommand = cli.Command{
|
||||
Name: "estimatefee",
|
||||
Category: "On-chain",
|
||||
Usage: "Get fee estimates for sending bitcoin on-chain to multiple addresses.",
|
||||
ArgsUsage: "send-json-string [--conf_target=N]",
|
||||
Description: `
|
||||
Get fee estimates for sending a transaction paying the specified amount(s) to the passed address(es).
|
||||
|
||||
The send-json-string' param decodes addresses and the amount to send respectively in the following format:
|
||||
|
||||
'{"ExampleAddr": NumCoinsInSatoshis, "SecondAddr": NumCoins}'
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
cli.Int64Flag{
|
||||
Name: "conf_target",
|
||||
Usage: "(optional) the number of blocks that the transaction *should* " +
|
||||
"confirm in",
|
||||
},
|
||||
},
|
||||
Action: actionDecorator(estimateFees),
|
||||
}
|
||||
|
||||
func estimateFees(ctx *cli.Context) error {
|
||||
var amountToAddr map[string]int64
|
||||
|
||||
jsonMap := ctx.Args().First()
|
||||
if err := json.Unmarshal([]byte(jsonMap), &amountToAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctxb := context.Background()
|
||||
client, cleanUp := getClient(ctx)
|
||||
defer cleanUp()
|
||||
|
||||
resp, err := client.EstimateFee(ctxb, &lnrpc.EstimateFeeRequest{
|
||||
AddrToAmount: amountToAddr,
|
||||
TargetConf: int32(ctx.Int64("conf_target")),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printRespJSON(resp)
|
||||
return nil
|
||||
}
|
||||
|
||||
var sendCoinsCommand = cli.Command{
|
||||
Name: "sendcoins",
|
||||
Category: "On-chain",
|
||||
|
@ -257,6 +257,7 @@ func main() {
|
||||
unlockCommand,
|
||||
changePasswordCommand,
|
||||
newAddressCommand,
|
||||
estimateFeeCommand,
|
||||
sendManyCommand,
|
||||
sendCoinsCommand,
|
||||
listUnspentCommand,
|
||||
|
1277
lnrpc/rpc.pb.go
1277
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@ -111,6 +111,23 @@ func request_Lightning_GetTransactions_0(ctx context.Context, marshaler runtime.
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_Lightning_EstimateFee_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
|
||||
func request_Lightning_EstimateFee_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq EstimateFeeRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_Lightning_EstimateFee_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.EstimateFee(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_Lightning_SendCoins_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq SendCoinsRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
@ -1006,6 +1023,35 @@ func RegisterLightningHandler(ctx context.Context, mux *runtime.ServeMux, conn *
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Lightning_EstimateFee_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
go func(done <-chan struct{}, closed <-chan bool) {
|
||||
select {
|
||||
case <-done:
|
||||
case <-closed:
|
||||
cancel()
|
||||
}
|
||||
}(ctx.Done(), cn.CloseNotify())
|
||||
}
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Lightning_EstimateFee_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Lightning_EstimateFee_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_Lightning_SendCoins_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
@ -1944,6 +1990,8 @@ var (
|
||||
|
||||
pattern_Lightning_GetTransactions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "transactions"}, ""))
|
||||
|
||||
pattern_Lightning_EstimateFee_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "transactions", "fee"}, ""))
|
||||
|
||||
pattern_Lightning_SendCoins_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "transactions"}, ""))
|
||||
|
||||
pattern_Lightning_ListUnspent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "utxos"}, ""))
|
||||
@ -2016,6 +2064,8 @@ var (
|
||||
|
||||
forward_Lightning_GetTransactions_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Lightning_EstimateFee_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Lightning_SendCoins_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Lightning_ListUnspent_0 = runtime.ForwardResponseMessage
|
||||
|
@ -220,6 +220,16 @@ service Lightning {
|
||||
};
|
||||
}
|
||||
|
||||
/** lncli: `estimatefee`
|
||||
EstimateFee asks the chain backend to estimate the fee rate and total fees
|
||||
for a transaction that pays to multiple specified outputs.
|
||||
*/
|
||||
rpc EstimateFee (EstimateFeeRequest) returns (EstimateFeeResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/transactions/fee"
|
||||
};
|
||||
}
|
||||
|
||||
/** lncli: `sendcoins`
|
||||
SendCoins executes a request to send coins to a particular address. Unlike
|
||||
SendMany, this RPC call only allows creating a single output at a time. If
|
||||
@ -839,6 +849,22 @@ message LightningAddress {
|
||||
string host = 2 [json_name = "host"];
|
||||
}
|
||||
|
||||
message EstimateFeeRequest {
|
||||
/// The map from addresses to amounts for the transaction.
|
||||
map<string, int64> AddrToAmount = 1;
|
||||
|
||||
/// The target number of blocks that this transaction should be confirmed by.
|
||||
int32 target_conf = 2;
|
||||
}
|
||||
|
||||
message EstimateFeeResponse {
|
||||
/// The total fee in satoshis.
|
||||
int64 fee_sat = 1 [json_name = "fee_sat"];
|
||||
|
||||
/// The fee rate in satoshi/byte.
|
||||
int64 feerate_sat_per_byte = 2 [json_name = "feerate_sat_per_byte"];
|
||||
}
|
||||
|
||||
message SendManyRequest {
|
||||
/// The map from addresses to amounts
|
||||
map<string, int64> AddrToAmount = 1;
|
||||
|
@ -1044,6 +1044,33 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/transactions/fee": {
|
||||
"get": {
|
||||
"summary": "* lncli: `estimatefee`\nEstimateFee asks the chain backend to estimate the fee rate and total fees\nfor a transaction that pays to multiple specified outputs.",
|
||||
"operationId": "EstimateFee",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/lnrpcEstimateFeeResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "target_conf",
|
||||
"description": "/ The target number of blocks that this transaction should be confirmed by.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Lightning"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/unlockwallet": {
|
||||
"post": {
|
||||
"summary": "* lncli: `unlock`\nUnlockWallet is used at startup of lnd to provide a password to unlock\nthe wallet database.",
|
||||
@ -1747,6 +1774,21 @@
|
||||
"lnrpcDisconnectPeerResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"lnrpcEstimateFeeResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fee_sat": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "/ The total fee in satoshis."
|
||||
},
|
||||
"feerate_sat_per_byte": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "/ The fee rate in satoshi/byte."
|
||||
}
|
||||
}
|
||||
},
|
||||
"lnrpcFeeLimit": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -17,6 +17,8 @@ import (
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
base "github.com/btcsuite/btcwallet/wallet"
|
||||
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
||||
"github.com/btcsuite/btcwallet/wallet/txrules"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
@ -297,9 +299,45 @@ func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut,
|
||||
// SendOutputs.
|
||||
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
|
||||
|
||||
// Sanity check outputs.
|
||||
if len(outputs) < 1 {
|
||||
return nil, lnwallet.ErrNoOutputs
|
||||
}
|
||||
return b.wallet.SendOutputs(outputs, defaultAccount, 1, feeSatPerKB)
|
||||
}
|
||||
|
||||
// CreateSimpleTx creates a Bitcoin transaction paying to the specified
|
||||
// outputs. The transaction is not broadcasted to the network, but a new change
|
||||
// address might be created in the wallet database. In the case the wallet has
|
||||
// insufficient funds, or the outputs are non-standard, an error should be
|
||||
// returned. This method also takes the target fee expressed in sat/kw that
|
||||
// should be used when crafting the transaction.
|
||||
//
|
||||
// NOTE: The dryRun argument can be set true to create a tx that doesn't alter
|
||||
// the database. A tx created with this set to true SHOULD NOT be broadcasted.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut,
|
||||
feeRate lnwallet.SatPerKWeight, dryRun bool) (*txauthor.AuthoredTx, error) {
|
||||
|
||||
// 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.
|
||||
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
|
||||
|
||||
// Sanity check outputs.
|
||||
if len(outputs) < 1 {
|
||||
return nil, lnwallet.ErrNoOutputs
|
||||
}
|
||||
for _, output := range outputs {
|
||||
err := txrules.CheckOutput(output, feeSatPerKB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return b.wallet.CreateSimpleTx(defaultAccount, outputs, 1, feeSatPerKB, dryRun)
|
||||
}
|
||||
|
||||
// LockOutpoint marks an outpoint as locked meaning it will no longer be deemed
|
||||
// as eligible for coin selection. Locking outputs are utilized in order to
|
||||
// avoid race conditions when selecting inputs for usage when funding a
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
@ -48,6 +49,10 @@ var (
|
||||
ErrNotMine = errors.New("the passed output doesn't belong to the wallet")
|
||||
)
|
||||
|
||||
// ErrNoOutputs is returned if we try to create a transaction with no outputs
|
||||
// or send coins to a set of outputs that is empty.
|
||||
var ErrNoOutputs = errors.New("no outputs")
|
||||
|
||||
// Utxo is an unspent output denoted by its outpoint, and output value of the
|
||||
// original output.
|
||||
type Utxo struct {
|
||||
@ -169,6 +174,19 @@ type WalletController interface {
|
||||
SendOutputs(outputs []*wire.TxOut,
|
||||
feeRate SatPerKWeight) (*wire.MsgTx, error)
|
||||
|
||||
// CreateSimpleTx creates a Bitcoin transaction paying to the specified
|
||||
// outputs. The transaction is not broadcasted to the network. In the
|
||||
// case the wallet has insufficient funds, or the outputs are
|
||||
// non-standard, an error should be returned. This method also takes
|
||||
// the target fee expressed in sat/kw that should be used when crafting
|
||||
// the transaction.
|
||||
//
|
||||
// NOTE: The dryRun argument can be set true to create a tx that
|
||||
// doesn't alter the database. A tx created with this set to true
|
||||
// SHOULD NOT be broadcasted.
|
||||
CreateSimpleTx(outputs []*wire.TxOut, feeRate SatPerKWeight,
|
||||
dryRun bool) (*txauthor.AuthoredTx, error)
|
||||
|
||||
// ListUnspentWitness returns all unspent outputs which are version 0
|
||||
// witness programs. The 'minconfirms' and 'maxconfirms' parameters
|
||||
// indicate the minimum and maximum number of confirmations an output
|
||||
|
@ -2221,6 +2221,187 @@ func testLastUnusedAddr(miner *rpctest.Harness,
|
||||
}
|
||||
}
|
||||
|
||||
// testCreateSimpleTx checks that a call to CreateSimpleTx will return a
|
||||
// transaction that is equal to the one that is being created by SendOutputs in
|
||||
// a subsequent call.
|
||||
func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
_ *lnwallet.LightningWallet, t *testing.T) {
|
||||
|
||||
// Send some money from the miner to the wallet
|
||||
err := loadTestCredits(r, w, 20, 4)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to send money to lnwallet: %v", err)
|
||||
}
|
||||
|
||||
// The test cases we will run through for all backends.
|
||||
testCases := []struct {
|
||||
outVals []int64
|
||||
feeRate lnwallet.SatPerKWeight
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
outVals: []int64{},
|
||||
feeRate: 2500,
|
||||
valid: false, // No outputs.
|
||||
},
|
||||
|
||||
{
|
||||
outVals: []int64{1e3},
|
||||
feeRate: 2500,
|
||||
valid: false, // Dust output.
|
||||
},
|
||||
|
||||
{
|
||||
outVals: []int64{1e8},
|
||||
feeRate: 2500,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
|
||||
feeRate: 2500,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
|
||||
feeRate: 12500,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
|
||||
feeRate: 50000,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5, 1e8, 2e8,
|
||||
1e8, 2e7, 3e5},
|
||||
feeRate: 44250,
|
||||
valid: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
feeRate := test.feeRate
|
||||
|
||||
// Grab some fresh addresses from the miner that we will send
|
||||
// to.
|
||||
outputs := make([]*wire.TxOut, len(test.outVals))
|
||||
for i, outVal := range test.outVals {
|
||||
minerAddr, err := r.NewAddress()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate address for "+
|
||||
"miner: %v", err)
|
||||
}
|
||||
script, err := txscript.PayToAddrScript(minerAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create pay to addr "+
|
||||
"script: %v", err)
|
||||
}
|
||||
output := &wire.TxOut{
|
||||
Value: outVal,
|
||||
PkScript: script,
|
||||
}
|
||||
|
||||
outputs[i] = output
|
||||
}
|
||||
|
||||
// Now try creating a tx spending to these outputs.
|
||||
createTx, createErr := w.CreateSimpleTx(
|
||||
outputs, feeRate, true,
|
||||
)
|
||||
if test.valid == (createErr != nil) {
|
||||
fmt.Println(spew.Sdump(createTx.Tx))
|
||||
t.Fatalf("got unexpected error when creating tx: %v",
|
||||
createErr)
|
||||
}
|
||||
|
||||
// Also send to these outputs. This should result in a tx
|
||||
// _very_ similar to the one we just created being sent. The
|
||||
// only difference is that the dry run tx is not signed, and
|
||||
// that the change output position might be different.
|
||||
tx, sendErr := w.SendOutputs(outputs, feeRate)
|
||||
if test.valid == (sendErr != nil) {
|
||||
t.Fatalf("got unexpected error when sending tx: %v",
|
||||
sendErr)
|
||||
}
|
||||
|
||||
// We expected either both to not fail, or both to fail with
|
||||
// the same error.
|
||||
if createErr != sendErr {
|
||||
t.Fatalf("error creating tx (%v) different "+
|
||||
"from error sending outputs (%v)",
|
||||
createErr, sendErr)
|
||||
}
|
||||
|
||||
// If we expected the creation to fail, then this test is over.
|
||||
if !test.valid {
|
||||
continue
|
||||
}
|
||||
|
||||
txid := tx.TxHash()
|
||||
err = waitForMempoolTx(r, &txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
// Helper method to check that the two txs are similar.
|
||||
assertSimilarTx := func(a, b *wire.MsgTx) error {
|
||||
if a.Version != b.Version {
|
||||
return fmt.Errorf("different versions: "+
|
||||
"%v vs %v", a.Version, b.Version)
|
||||
}
|
||||
if a.LockTime != b.LockTime {
|
||||
return fmt.Errorf("different locktimes: "+
|
||||
"%v vs %v", a.LockTime, b.LockTime)
|
||||
}
|
||||
if len(a.TxIn) != len(b.TxIn) {
|
||||
return fmt.Errorf("different number of "+
|
||||
"inputs: %v vs %v", len(a.TxIn),
|
||||
len(b.TxIn))
|
||||
}
|
||||
if len(a.TxOut) != len(b.TxOut) {
|
||||
return fmt.Errorf("different number of "+
|
||||
"outputs: %v vs %v", len(a.TxOut),
|
||||
len(b.TxOut))
|
||||
}
|
||||
|
||||
// They should be spending the same inputs.
|
||||
for i := range a.TxIn {
|
||||
prevA := a.TxIn[i].PreviousOutPoint
|
||||
prevB := b.TxIn[i].PreviousOutPoint
|
||||
if prevA != prevB {
|
||||
return fmt.Errorf("different inputs: "+
|
||||
"%v vs %v", spew.Sdump(prevA),
|
||||
spew.Sdump(prevB))
|
||||
}
|
||||
}
|
||||
|
||||
// They should have the same outputs. Since the change
|
||||
// output position gets randomized, they are not
|
||||
// guaranteed to be in the same order.
|
||||
for _, outA := range a.TxOut {
|
||||
found := false
|
||||
for _, outB := range b.TxOut {
|
||||
if reflect.DeepEqual(outA, outB) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("did not find "+
|
||||
"output %v", spew.Sdump(outA))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Assert that our "template tx" was similar to the one that
|
||||
// ended up being sent.
|
||||
if err := assertSimilarTx(createTx.Tx, tx); err != nil {
|
||||
t.Fatalf("transactions not similar: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type walletTestCase struct {
|
||||
name string
|
||||
test func(miner *rpctest.Harness, alice, bob *lnwallet.LightningWallet,
|
||||
@ -2283,6 +2464,10 @@ var walletTests = []walletTestCase{
|
||||
name: "reorg wallet balance",
|
||||
test: testReorgWalletBalance,
|
||||
},
|
||||
{
|
||||
name: "create simple tx",
|
||||
test: testCreateSimpleTx,
|
||||
},
|
||||
}
|
||||
|
||||
func clearWalletStates(a, b *lnwallet.LightningWallet) error {
|
||||
|
7
mock.go
7
mock.go
@ -11,6 +11,7 @@ import (
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
||||
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
@ -241,6 +242,12 @@ func (*mockWalletController) SendOutputs(outputs []*wire.TxOut,
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (*mockWalletController) CreateSimpleTx(outputs []*wire.TxOut,
|
||||
_ lnwallet.SatPerKWeight, _ bool) (*txauthor.AuthoredTx, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ListUnspentWitness is called by the wallet when doing coin selection. We just
|
||||
// need one unspent for the funding transaction.
|
||||
func (m *mockWalletController) ListUnspentWitness(minconfirms,
|
||||
|
58
rpcserver.go
58
rpcserver.go
@ -25,6 +25,7 @@ import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
@ -236,6 +237,10 @@ var (
|
||||
Entity: "onchain",
|
||||
Action: "read",
|
||||
}},
|
||||
"/lnrpc.Lightning/EstimateFee": {{
|
||||
Entity: "onchain",
|
||||
Action: "read",
|
||||
}},
|
||||
"/lnrpc.Lightning/ChannelBalance": {{
|
||||
Entity: "offchain",
|
||||
Action: "read",
|
||||
@ -800,6 +805,59 @@ func (r *rpcServer) ListUnspent(ctx context.Context,
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// EstimateFee handles a request for estimating the fee for sending a
|
||||
// transaction spending to multiple specified outputs in parallel.
|
||||
func (r *rpcServer) EstimateFee(ctx context.Context,
|
||||
in *lnrpc.EstimateFeeRequest) (*lnrpc.EstimateFeeResponse, error) {
|
||||
|
||||
// Create the list of outputs we are spending to.
|
||||
outputs, err := addrPairsToOutputs(in.AddrToAmount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Query the fee estimator for the fee rate for the given confirmation
|
||||
// target.
|
||||
target := in.TargetConf
|
||||
feePerKw, err := sweep.DetermineFeePerKw(
|
||||
r.server.cc.feeEstimator, sweep.FeePreference{
|
||||
ConfTarget: uint32(target),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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.
|
||||
var tx *txauthor.AuthoredTx
|
||||
wallet := r.server.cc.wallet
|
||||
err = wallet.WithCoinSelectLock(func() error {
|
||||
tx, err = wallet.CreateSimpleTx(outputs, feePerKw, true)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Use the created tx to calculate the total fee.
|
||||
totalOutput := int64(0)
|
||||
for _, out := range tx.Tx.TxOut {
|
||||
totalOutput += out.Value
|
||||
}
|
||||
totalFee := int64(tx.TotalInput) - totalOutput
|
||||
|
||||
resp := &lnrpc.EstimateFeeResponse{
|
||||
FeeSat: totalFee,
|
||||
FeerateSatPerByte: int64(feePerKw.FeePerKVByte() / 1000),
|
||||
}
|
||||
|
||||
rpcsLog.Debugf("[estimatefee] fee estimate for conf target %d: %v",
|
||||
target, resp)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SendCoins executes a request to send coins to a particular address. Unlike
|
||||
// SendMany, this RPC call only allows creating a single output at a time.
|
||||
func (r *rpcServer) SendCoins(ctx context.Context,
|
||||
|
Loading…
Reference in New Issue
Block a user