From 50a8f188a38f93698dc73110e796bc00944580c0 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Mon, 27 May 2019 16:14:50 -0700 Subject: [PATCH 1/4] sweep: add lastFeeRate field to PendingInput This will serve useful when exposing the pending inputs over RPC, since we currently don't keep track of the last fee rate used for an input. --- sweep/sweeper.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sweep/sweeper.go b/sweep/sweeper.go index 9c8e59c7b..1785d70bc 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -80,6 +80,10 @@ type pendingInput struct { // map it into a fee rate whenever we attempt to cluster inputs for a // sweep. feePreference FeePreference + + // lastFeeRate is the most recent fee rate used for this input within a + // transaction broadcast to the network. + lastFeeRate lnwallet.SatPerKWeight } // pendingInputs is a type alias for a set of pending inputs. @@ -580,6 +584,8 @@ func (s *UtxoSweeper) clusterBySweepFeeRate() []inputCluster { inputs = make(pendingInputs) bucketInputs[bucket] = inputs } + + input.lastFeeRate = feeRate inputs[op] = input inputFeeRates[op] = feeRate } From d75fac26bc47d5e38de0fa19eeda68a31aef4e88 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Thu, 16 May 2019 19:52:46 -0700 Subject: [PATCH 2/4] sweep: expose pending input sweeps within the UtxoSweeper --- sweep/sweeper.go | 105 +++++++++++++++++++++++++++++++++++++++--- sweep/sweeper_test.go | 91 ++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 6 deletions(-) diff --git a/sweep/sweeper.go b/sweep/sweeper.go index 1785d70bc..6fe398559 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -46,6 +46,11 @@ var ( // for the configured max number of attempts. ErrTooManyAttempts = errors.New("sweep failed after max attempts") + // ErrSweeperShuttingDown is an error returned when a client attempts to + // make a request to the UtxoSweeper, but it is unable to handle it as + // it is/has already been stoppepd. + ErrSweeperShuttingDown = errors.New("utxo sweeper shutting down") + // DefaultMaxSweepAttempts specifies the default maximum number of times // an input is included in a publish attempt before giving up and // returning an error to the caller. @@ -96,6 +101,38 @@ type inputCluster struct { inputs pendingInputs } +// pendingSweepsReq is an internal message we'll use to represent an external +// caller's intent to retrieve all of the pending inputs the UtxoSweeper is +// attempting to sweep. +type pendingSweepsReq struct { + respChan chan map[wire.OutPoint]*PendingInput +} + +// PendingInput contains information about an input that is currently being +// swept by the UtxoSweeper. +type PendingInput struct { + // OutPoint is the identify outpoint of the input being swept. + OutPoint wire.OutPoint + + // WitnessType is the witness type of the input being swept. + WitnessType input.WitnessType + + // Amount is the amount of the input being swept. + Amount btcutil.Amount + + // LastFeeRate is the most recent fee rate used for the input being + // swept within a transaction broadcast to the network. + LastFeeRate lnwallet.SatPerKWeight + + // BroadcastAttempts is the number of attempts we've made to sweept the + // input. + BroadcastAttempts int + + // NextBroadcastHeight is the next height of the chain at which we'll + // attempt to broadcast a transaction sweeping the input. + NextBroadcastHeight uint32 +} + // UtxoSweeper is responsible for sweeping outputs back into the wallet type UtxoSweeper struct { started uint32 // To be used atomically. @@ -106,6 +143,11 @@ type UtxoSweeper struct { newInputs chan *sweepInputMessage spendChan chan *chainntnfs.SpendDetail + // pendingSweepsReq is a channel that will be sent requests by external + // callers in order to retrieve the set of pending inputs the + // UtxoSweeper is attempting to sweep. + pendingSweepsReqs chan *pendingSweepsReq + // pendingInputs is the total set of inputs the UtxoSweeper has been // requested to sweep. pendingInputs pendingInputs @@ -212,11 +254,12 @@ type sweepInputMessage struct { // New returns a new Sweeper instance. func New(cfg *UtxoSweeperConfig) *UtxoSweeper { return &UtxoSweeper{ - cfg: cfg, - newInputs: make(chan *sweepInputMessage), - spendChan: make(chan *chainntnfs.SpendDetail), - quit: make(chan struct{}), - pendingInputs: make(pendingInputs), + cfg: cfg, + newInputs: make(chan *sweepInputMessage), + spendChan: make(chan *chainntnfs.SpendDetail), + pendingSweepsReqs: make(chan *pendingSweepsReq), + quit: make(chan struct{}), + pendingInputs: make(pendingInputs), } } @@ -340,7 +383,7 @@ func (s *UtxoSweeper) SweepInput(input input.Input, select { case s.newInputs <- sweeperInput: case <-s.quit: - return nil, fmt.Errorf("sweeper shutting down") + return nil, ErrSweeperShuttingDown } return sweeperInput.resultChan, nil @@ -484,6 +527,11 @@ func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch, log.Errorf("schedule sweep: %v", err) } + // A new external request has been received to retrieve all of + // the inputs we're currently attempting to sweep. + case req := <-s.pendingSweepsReqs: + req.respChan <- s.handlePendingSweepsReq(req) + // The timer expires and we are going to (re)sweep. case <-s.timer: log.Debugf("Sweep timer expired") @@ -886,6 +934,51 @@ func (s *UtxoSweeper) waitForSpend(outpoint wire.OutPoint, return spendEvent.Cancel, nil } +// PendingInputs returns the set of inputs that the UtxoSweeper is currently +// attempting to sweep. +func (s *UtxoSweeper) PendingInputs() (map[wire.OutPoint]*PendingInput, error) { + respChan := make(chan map[wire.OutPoint]*PendingInput, 1) + select { + case s.pendingSweepsReqs <- &pendingSweepsReq{ + respChan: respChan, + }: + case <-s.quit: + return nil, ErrSweeperShuttingDown + } + + select { + case pendingSweeps := <-respChan: + return pendingSweeps, nil + case <-s.quit: + return nil, ErrSweeperShuttingDown + } +} + +// handlePendingSweepsReq handles a request to retrieve all pending inputs the +// UtxoSweeper is attempting to sweep. +func (s *UtxoSweeper) handlePendingSweepsReq( + req *pendingSweepsReq) map[wire.OutPoint]*PendingInput { + + pendingInputs := make(map[wire.OutPoint]*PendingInput, len(s.pendingInputs)) + for _, pendingInput := range s.pendingInputs { + // Only the exported fields are set, as we expect the response + // to only be consumed externally. + op := *pendingInput.input.OutPoint() + pendingInputs[op] = &PendingInput{ + OutPoint: op, + WitnessType: pendingInput.input.WitnessType(), + Amount: btcutil.Amount( + pendingInput.input.SignDesc().Output.Value, + ), + LastFeeRate: pendingInput.lastFeeRate, + BroadcastAttempts: pendingInput.publishAttempts, + NextBroadcastHeight: uint32(pendingInput.minPublishHeight), + } + } + + return pendingInputs +} + // CreateSweepTx accepts a list of inputs and signs and generates a txn that // spends from them. This method also makes an accurate fee estimate before // generating the required witnesses. diff --git a/sweep/sweeper_test.go b/sweep/sweeper_test.go index f7ec2173c..d494b996b 100644 --- a/sweep/sweeper_test.go +++ b/sweep/sweeper_test.go @@ -263,6 +263,29 @@ func (ctx *sweeperTestContext) expectResult(c chan Result, expected error) { } } +func (ctx *sweeperTestContext) assertPendingInputs(inputs ...input.Input) { + ctx.t.Helper() + + inputSet := make(map[wire.OutPoint]struct{}, len(inputs)) + for _, input := range inputs { + inputSet[*input.OutPoint()] = struct{}{} + } + + pendingInputs, err := ctx.sweeper.PendingInputs() + if err != nil { + ctx.t.Fatal(err) + } + if len(pendingInputs) != len(inputSet) { + ctx.t.Fatalf("expected %d pending inputs, got %d", + len(inputSet), len(pendingInputs)) + } + for input := range pendingInputs { + if _, ok := inputSet[input]; !ok { + ctx.t.Fatalf("found unexpected input %v", input) + } + } +} + // receiveSpendTx receives the transaction sent through the given resultChan. func receiveSpendTx(t *testing.T, resultChan chan Result) *wire.MsgTx { t.Helper() @@ -1032,3 +1055,71 @@ func TestDifferentFeePreferences(t *testing.T) { ctx.finish(1) } + +// TestPendingInputs ensures that the sweeper correctly determines the inputs +// pending to be swept. +func TestPendingInputs(t *testing.T) { + ctx := createSweeperTestContext(t) + + // Throughout this test, we'll be attempting to sweep three inputs, two + // with the higher fee preference, and the last with the lower. We do + // this to ensure the sweeper can return all pending inputs, even those + // with different fee preferences. + const ( + lowFeeRate = 5000 + highFeeRate = 10000 + ) + + lowFeePref := FeePreference{ + ConfTarget: 12, + } + ctx.estimator.blocksToFee[lowFeePref.ConfTarget] = lowFeeRate + + highFeePref := FeePreference{ + ConfTarget: 6, + } + ctx.estimator.blocksToFee[highFeePref.ConfTarget] = highFeeRate + + input1 := spendableInputs[0] + resultChan1, err := ctx.sweeper.SweepInput(input1, highFeePref) + if err != nil { + t.Fatal(err) + } + input2 := spendableInputs[1] + if _, err := ctx.sweeper.SweepInput(input2, highFeePref); err != nil { + t.Fatal(err) + } + input3 := spendableInputs[2] + resultChan3, err := ctx.sweeper.SweepInput(input3, lowFeePref) + if err != nil { + t.Fatal(err) + } + + // We should expect to see all inputs pending. + ctx.assertPendingInputs(input1, input2, input3) + + // We should expect to see both sweep transactions broadcast. The higher + // fee rate sweep should be broadcast first. We'll remove the lower fee + // rate sweep to ensure we can detect pending inputs after a sweep. + // Once the higher fee rate sweep confirms, we should no longer see + // those inputs pending. + ctx.tick() + ctx.receiveTx() + lowFeeRateTx := ctx.receiveTx() + ctx.backend.deleteUnconfirmed(lowFeeRateTx.TxHash()) + ctx.backend.mine() + ctx.expectResult(resultChan1, nil) + ctx.assertPendingInputs(input3) + + // We'll then trigger a new block to rebroadcast the lower fee rate + // sweep. Once again we'll ensure those inputs are no longer pending + // once the sweep transaction confirms. + ctx.backend.notifier.NotifyEpoch(101) + ctx.tick() + ctx.receiveTx() + ctx.backend.mine() + ctx.expectResult(resultChan3, nil) + ctx.assertPendingInputs() + + ctx.finish(1) +} From d01f88ecdc9fed78e71547103b2ced49ad0e7f61 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Thu, 16 May 2019 19:53:25 -0700 Subject: [PATCH 3/4] lnrpc/walletrpc: expose pending input sweeps within UtxoSweeper over RPC --- lnrpc/walletrpc/config_active.go | 5 + lnrpc/walletrpc/walletkit.pb.go | 413 +++++++++++++++++++++++++--- lnrpc/walletrpc/walletkit.proto | 134 +++++++++ lnrpc/walletrpc/walletkit_server.go | 78 ++++++ rpcserver.go | 2 +- subrpcserver_config.go | 7 +- 6 files changed, 603 insertions(+), 36 deletions(-) diff --git a/lnrpc/walletrpc/config_active.go b/lnrpc/walletrpc/config_active.go index 6f7a207ee..77dc0e574 100644 --- a/lnrpc/walletrpc/config_active.go +++ b/lnrpc/walletrpc/config_active.go @@ -6,6 +6,7 @@ import ( "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/macaroons" + "github.com/lightningnetwork/lnd/sweep" ) // Config is the primary configuration struct for the WalletKit RPC server. It @@ -38,4 +39,8 @@ type Config struct { // KeyRing is an interface that the WalletKit will use to derive any // keys due to incoming client requests. KeyRing keychain.KeyRing + + // Sweeper is the central batching engine of lnd. It is responsible for + // sweeping inputs in batches back into the wallet. + Sweeper *sweep.UtxoSweeper } diff --git a/lnrpc/walletrpc/walletkit.pb.go b/lnrpc/walletrpc/walletkit.pb.go index 60a4a9a9f..ffdb00bc5 100644 --- a/lnrpc/walletrpc/walletkit.pb.go +++ b/lnrpc/walletrpc/walletkit.pb.go @@ -7,6 +7,7 @@ import ( context "context" fmt "fmt" proto "github.com/golang/protobuf/proto" + lnrpc "github.com/lightningnetwork/lnd/lnrpc" signrpc "github.com/lightningnetwork/lnd/lnrpc/signrpc" grpc "google.golang.org/grpc" math "math" @@ -23,6 +24,108 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +type WitnessType int32 + +const ( + WitnessType_UNKNOWN_WITNESS WitnessType = 0 + // + //A witness that allows us to spend the output of a commitment transaction + //after a relative lock-time lockout. + WitnessType_COMMITMENT_TIME_LOCK WitnessType = 1 + // + //A witness that allows us to spend a settled no-delay output immediately on a + //counterparty's commitment transaction. + WitnessType_COMMITMENT_NO_DELAY WitnessType = 2 + // + //A witness that allows us to sweep the settled output of a malicious + //counterparty's who broadcasts a revoked commitment transaction. + WitnessType_COMMITMENT_REVOKE WitnessType = 3 + // + //A witness that allows us to sweep an HTLC which we offered to the remote + //party in the case that they broadcast a revoked commitment state. + WitnessType_HTLC_OFFERED_REVOKE WitnessType = 4 + // + //A witness that allows us to sweep an HTLC output sent to us in the case that + //the remote party broadcasts a revoked commitment state. + WitnessType_HTLC_ACCEPTED_REVOKE WitnessType = 5 + // + //A witness that allows us to sweep an HTLC output that we extended to a + //party, but was never fulfilled. This HTLC output isn't directly on the + //commitment transaction, but is the result of a confirmed second-level HTLC + //transaction. As a result, we can only spend this after a CSV delay. + WitnessType_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL WitnessType = 6 + // + //A witness that allows us to sweep an HTLC output that was offered to us, and + //for which we have a payment preimage. This HTLC output isn't directly on our + //commitment transaction, but is the result of confirmed second-level HTLC + //transaction. As a result, we can only spend this after a CSV delay. + WitnessType_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL WitnessType = 7 + // + //A witness that allows us to sweep an HTLC that we offered to the remote + //party which lies in the commitment transaction of the remote party. We can + //spend this output after the absolute CLTV timeout of the HTLC as passed. + WitnessType_HTLC_OFFERED_REMOTE_TIMEOUT WitnessType = 8 + // + //A witness that allows us to sweep an HTLC that was offered to us by the + //remote party. We use this witness in the case that the remote party goes to + //chain, and we know the pre-image to the HTLC. We can sweep this without any + //additional timeout. + WitnessType_HTLC_ACCEPTED_REMOTE_SUCCESS WitnessType = 9 + // + //A witness that allows us to sweep an HTLC from the remote party's commitment + //transaction in the case that the broadcast a revoked commitment, but then + //also immediately attempt to go to the second level to claim the HTLC. + WitnessType_HTLC_SECOND_LEVEL_REVOKE WitnessType = 10 + // + //A witness type that allows us to spend a regular p2wkh output that's sent to + //an output which is under complete control of the backing wallet. + WitnessType_WITNESS_KEY_HASH WitnessType = 11 + // + //A witness type that allows us to sweep an output that sends to a nested P2SH + //script that pays to a key solely under our control. + WitnessType_NESTED_WITNESS_KEY_HASH WitnessType = 12 +) + +var WitnessType_name = map[int32]string{ + 0: "UNKNOWN_WITNESS", + 1: "COMMITMENT_TIME_LOCK", + 2: "COMMITMENT_NO_DELAY", + 3: "COMMITMENT_REVOKE", + 4: "HTLC_OFFERED_REVOKE", + 5: "HTLC_ACCEPTED_REVOKE", + 6: "HTLC_OFFERED_TIMEOUT_SECOND_LEVEL", + 7: "HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL", + 8: "HTLC_OFFERED_REMOTE_TIMEOUT", + 9: "HTLC_ACCEPTED_REMOTE_SUCCESS", + 10: "HTLC_SECOND_LEVEL_REVOKE", + 11: "WITNESS_KEY_HASH", + 12: "NESTED_WITNESS_KEY_HASH", +} + +var WitnessType_value = map[string]int32{ + "UNKNOWN_WITNESS": 0, + "COMMITMENT_TIME_LOCK": 1, + "COMMITMENT_NO_DELAY": 2, + "COMMITMENT_REVOKE": 3, + "HTLC_OFFERED_REVOKE": 4, + "HTLC_ACCEPTED_REVOKE": 5, + "HTLC_OFFERED_TIMEOUT_SECOND_LEVEL": 6, + "HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL": 7, + "HTLC_OFFERED_REMOTE_TIMEOUT": 8, + "HTLC_ACCEPTED_REMOTE_SUCCESS": 9, + "HTLC_SECOND_LEVEL_REVOKE": 10, + "WITNESS_KEY_HASH": 11, + "NESTED_WITNESS_KEY_HASH": 12, +} + +func (x WitnessType) String() string { + return proto.EnumName(WitnessType_name, int32(x)) +} + +func (WitnessType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_6cc6942ac78249e5, []int{0} +} + type KeyReq struct { //* //Is the key finger print of the root pubkey that this request is targeting. @@ -411,7 +514,170 @@ func (m *EstimateFeeResponse) GetSatPerKw() int64 { return 0 } +type PendingSweep struct { + // The outpoint of the output we're attempting to sweep. + Outpoint *lnrpc.OutPoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` + // The witness type of the output we're attempting to sweep. + WitnessType WitnessType `protobuf:"varint,2,opt,name=witness_type,proto3,enum=walletrpc.WitnessType" json:"witness_type,omitempty"` + // The value of the output we're attempting to sweep. + AmountSat uint32 `protobuf:"varint,3,opt,name=amount_sat,proto3" json:"amount_sat,omitempty"` + // + //The fee rate we'll use to sweep the output. The fee rate is only determined + //once a sweeping transaction for the output is created, so it's possible for + //this to be 0 before this. + SatPerByte uint32 `protobuf:"varint,4,opt,name=sat_per_byte,proto3" json:"sat_per_byte,omitempty"` + // The number of broadcast attempts we've made to sweep the output. + BroadcastAttempts uint32 `protobuf:"varint,5,opt,name=broadcast_attempts,proto3" json:"broadcast_attempts,omitempty"` + // + //The next height of the chain at which we'll attempt to broadcast the + //sweep transaction of the output. + NextBroadcastHeight uint32 `protobuf:"varint,6,opt,name=next_broadcast_height,proto3" json:"next_broadcast_height,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PendingSweep) Reset() { *m = PendingSweep{} } +func (m *PendingSweep) String() string { return proto.CompactTextString(m) } +func (*PendingSweep) ProtoMessage() {} +func (*PendingSweep) Descriptor() ([]byte, []int) { + return fileDescriptor_6cc6942ac78249e5, []int{9} +} + +func (m *PendingSweep) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PendingSweep.Unmarshal(m, b) +} +func (m *PendingSweep) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PendingSweep.Marshal(b, m, deterministic) +} +func (m *PendingSweep) XXX_Merge(src proto.Message) { + xxx_messageInfo_PendingSweep.Merge(m, src) +} +func (m *PendingSweep) XXX_Size() int { + return xxx_messageInfo_PendingSweep.Size(m) +} +func (m *PendingSweep) XXX_DiscardUnknown() { + xxx_messageInfo_PendingSweep.DiscardUnknown(m) +} + +var xxx_messageInfo_PendingSweep proto.InternalMessageInfo + +func (m *PendingSweep) GetOutpoint() *lnrpc.OutPoint { + if m != nil { + return m.Outpoint + } + return nil +} + +func (m *PendingSweep) GetWitnessType() WitnessType { + if m != nil { + return m.WitnessType + } + return WitnessType_UNKNOWN_WITNESS +} + +func (m *PendingSweep) GetAmountSat() uint32 { + if m != nil { + return m.AmountSat + } + return 0 +} + +func (m *PendingSweep) GetSatPerByte() uint32 { + if m != nil { + return m.SatPerByte + } + return 0 +} + +func (m *PendingSweep) GetBroadcastAttempts() uint32 { + if m != nil { + return m.BroadcastAttempts + } + return 0 +} + +func (m *PendingSweep) GetNextBroadcastHeight() uint32 { + if m != nil { + return m.NextBroadcastHeight + } + return 0 +} + +type PendingSweepsRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PendingSweepsRequest) Reset() { *m = PendingSweepsRequest{} } +func (m *PendingSweepsRequest) String() string { return proto.CompactTextString(m) } +func (*PendingSweepsRequest) ProtoMessage() {} +func (*PendingSweepsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_6cc6942ac78249e5, []int{10} +} + +func (m *PendingSweepsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PendingSweepsRequest.Unmarshal(m, b) +} +func (m *PendingSweepsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PendingSweepsRequest.Marshal(b, m, deterministic) +} +func (m *PendingSweepsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_PendingSweepsRequest.Merge(m, src) +} +func (m *PendingSweepsRequest) XXX_Size() int { + return xxx_messageInfo_PendingSweepsRequest.Size(m) +} +func (m *PendingSweepsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_PendingSweepsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_PendingSweepsRequest proto.InternalMessageInfo + +type PendingSweepsResponse struct { + // + //The set of outputs currently being swept by lnd's central batching engine. + PendingSweeps []*PendingSweep `protobuf:"bytes,1,rep,name=pending_sweeps,proto3" json:"pending_sweeps,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PendingSweepsResponse) Reset() { *m = PendingSweepsResponse{} } +func (m *PendingSweepsResponse) String() string { return proto.CompactTextString(m) } +func (*PendingSweepsResponse) ProtoMessage() {} +func (*PendingSweepsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6cc6942ac78249e5, []int{11} +} + +func (m *PendingSweepsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PendingSweepsResponse.Unmarshal(m, b) +} +func (m *PendingSweepsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PendingSweepsResponse.Marshal(b, m, deterministic) +} +func (m *PendingSweepsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_PendingSweepsResponse.Merge(m, src) +} +func (m *PendingSweepsResponse) XXX_Size() int { + return xxx_messageInfo_PendingSweepsResponse.Size(m) +} +func (m *PendingSweepsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_PendingSweepsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_PendingSweepsResponse proto.InternalMessageInfo + +func (m *PendingSweepsResponse) GetPendingSweeps() []*PendingSweep { + if m != nil { + return m.PendingSweeps + } + return nil +} + func init() { + proto.RegisterEnum("walletrpc.WitnessType", WitnessType_name, WitnessType_value) proto.RegisterType((*KeyReq)(nil), "walletrpc.KeyReq") proto.RegisterType((*AddrRequest)(nil), "walletrpc.AddrRequest") proto.RegisterType((*AddrResponse)(nil), "walletrpc.AddrResponse") @@ -421,45 +687,73 @@ func init() { proto.RegisterType((*SendOutputsResponse)(nil), "walletrpc.SendOutputsResponse") proto.RegisterType((*EstimateFeeRequest)(nil), "walletrpc.EstimateFeeRequest") proto.RegisterType((*EstimateFeeResponse)(nil), "walletrpc.EstimateFeeResponse") + proto.RegisterType((*PendingSweep)(nil), "walletrpc.PendingSweep") + proto.RegisterType((*PendingSweepsRequest)(nil), "walletrpc.PendingSweepsRequest") + proto.RegisterType((*PendingSweepsResponse)(nil), "walletrpc.PendingSweepsResponse") } func init() { proto.RegisterFile("walletrpc/walletkit.proto", fileDescriptor_6cc6942ac78249e5) } var fileDescriptor_6cc6942ac78249e5 = []byte{ - // 524 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0x5d, 0x6f, 0xd3, 0x30, - 0x14, 0xd5, 0x56, 0x56, 0xd6, 0xdb, 0x76, 0x80, 0xcb, 0x46, 0x89, 0x18, 0x4c, 0x81, 0x87, 0x3e, - 0xa0, 0x54, 0x6c, 0x02, 0x21, 0x78, 0x02, 0x6d, 0xd3, 0xa4, 0x4e, 0xac, 0x84, 0x4a, 0x48, 0x08, - 0x29, 0x72, 0x93, 0xbb, 0xd4, 0x6a, 0x6a, 0x67, 0xce, 0x0d, 0x49, 0xfe, 0x0f, 0x3f, 0x14, 0xe5, - 0xa3, 0x5d, 0x4a, 0x19, 0x4f, 0x71, 0x8e, 0xcf, 0x3d, 0xbe, 0xd7, 0xe7, 0x18, 0x9e, 0x26, 0x3c, - 0x08, 0x90, 0x74, 0xe8, 0x0e, 0xcb, 0xd5, 0x5c, 0x90, 0x15, 0x6a, 0x45, 0x8a, 0xb5, 0x56, 0x5b, - 0xc6, 0xe3, 0x48, 0xf8, 0x32, 0xe7, 0xe4, 0x5f, 0xd4, 0x25, 0xc1, 0xfc, 0x0a, 0xcd, 0x11, 0x66, - 0x36, 0xde, 0xb0, 0x01, 0x3c, 0x9c, 0x63, 0xe6, 0x5c, 0x0b, 0xe9, 0xa3, 0x76, 0x42, 0x2d, 0x24, - 0xf5, 0xb7, 0x8e, 0xb6, 0x06, 0x3b, 0xf6, 0xde, 0x1c, 0xb3, 0xf3, 0x02, 0x1e, 0xe7, 0x28, 0x3b, - 0x04, 0x28, 0x98, 0x7c, 0x21, 0x82, 0xac, 0xbf, 0x5d, 0x70, 0x5a, 0x39, 0xa7, 0x00, 0xcc, 0x2e, - 0xb4, 0x3f, 0x79, 0x9e, 0xb6, 0xf1, 0x26, 0xc6, 0x88, 0x4c, 0x13, 0x3a, 0xe5, 0x6f, 0x14, 0x2a, - 0x19, 0x21, 0x63, 0x70, 0x8f, 0x7b, 0x9e, 0x2e, 0xb4, 0x5b, 0x76, 0xb1, 0x36, 0x5f, 0x41, 0x7b, - 0xa2, 0xb9, 0x8c, 0xb8, 0x4b, 0x42, 0x49, 0xb6, 0x0f, 0x4d, 0x4a, 0x9d, 0x19, 0xa6, 0x05, 0xa9, - 0x63, 0xef, 0x50, 0x7a, 0x81, 0xa9, 0xf9, 0x0e, 0x1e, 0x8c, 0xe3, 0x69, 0x20, 0xa2, 0xd9, 0x4a, - 0xec, 0x25, 0x74, 0xc3, 0x12, 0x72, 0x50, 0x6b, 0xb5, 0x54, 0xed, 0x54, 0xe0, 0x59, 0x8e, 0x99, - 0x3f, 0x81, 0x7d, 0x43, 0xe9, 0x5d, 0xc5, 0x14, 0xc6, 0x14, 0x55, 0x7d, 0xb1, 0x67, 0x00, 0x11, - 0x27, 0x27, 0x44, 0xed, 0xcc, 0x93, 0xa2, 0xae, 0x61, 0xef, 0x46, 0x9c, 0xc6, 0xa8, 0x47, 0x09, - 0x1b, 0xc0, 0x7d, 0x55, 0xf2, 0xfb, 0xdb, 0x47, 0x8d, 0x41, 0xfb, 0x78, 0xcf, 0xaa, 0xee, 0xcf, - 0x9a, 0xa4, 0x57, 0x31, 0xd9, 0xcb, 0x6d, 0xf3, 0x35, 0xf4, 0xd6, 0xd4, 0xab, 0xce, 0xf6, 0xa1, - 0xa9, 0x79, 0xe2, 0xd0, 0x6a, 0x06, 0xcd, 0x93, 0x49, 0x6a, 0xbe, 0x05, 0x76, 0x16, 0x91, 0x58, - 0x70, 0xc2, 0x73, 0xc4, 0x65, 0x2f, 0x2f, 0xa0, 0xed, 0x2a, 0x79, 0xed, 0x10, 0xd7, 0x3e, 0x2e, - 0xaf, 0x1d, 0x72, 0x68, 0x52, 0x20, 0xe6, 0x09, 0xf4, 0xd6, 0xca, 0xaa, 0x43, 0xfe, 0x3b, 0xc3, - 0xf1, 0xef, 0x06, 0xb4, 0xbe, 0x17, 0xfe, 0x8f, 0x04, 0xb1, 0x0f, 0xd0, 0x3d, 0x45, 0x2d, 0x7e, - 0xe1, 0x17, 0x4c, 0x69, 0x84, 0x19, 0x7b, 0x64, 0xad, 0xc2, 0x61, 0x95, 0x19, 0x30, 0x0e, 0x56, - 0x43, 0x8e, 0x30, 0x3b, 0xc5, 0xc8, 0xd5, 0x22, 0x24, 0xa5, 0xd9, 0x7b, 0x68, 0x95, 0xb5, 0x79, - 0x5d, 0xaf, 0x4e, 0xba, 0x54, 0x2e, 0x27, 0xa5, 0xef, 0xac, 0xfc, 0x08, 0xbb, 0xf9, 0x79, 0x79, - 0x02, 0xd8, 0x41, 0xed, 0xc0, 0x5a, 0x42, 0x8c, 0x27, 0x1b, 0x78, 0x35, 0xde, 0x05, 0xb0, 0xca, - 0xf0, 0x7a, 0x3a, 0xea, 0x32, 0x35, 0xdc, 0x30, 0x6a, 0xf8, 0xdf, 0x39, 0xb9, 0x84, 0x76, 0xcd, - 0x24, 0x76, 0x58, 0xa3, 0x6e, 0x46, 0xc3, 0x78, 0x7e, 0xd7, 0xf6, 0xad, 0x5a, 0xcd, 0x8d, 0x35, - 0xb5, 0x4d, 0x73, 0xd7, 0xd4, 0xfe, 0x61, 0xe2, 0xe7, 0x37, 0x3f, 0x86, 0xbe, 0xa0, 0x59, 0x3c, - 0xb5, 0x5c, 0xb5, 0x18, 0x06, 0xc2, 0x9f, 0x91, 0x14, 0xd2, 0x97, 0x48, 0x89, 0xd2, 0xf3, 0x61, - 0x20, 0xbd, 0x61, 0x20, 0x6f, 0x1f, 0xb7, 0x0e, 0xdd, 0x69, 0xb3, 0x78, 0xbc, 0x27, 0x7f, 0x02, - 0x00, 0x00, 0xff, 0xff, 0x2d, 0xbb, 0xcd, 0x97, 0xfa, 0x03, 0x00, 0x00, + // 918 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x55, 0x5d, 0x6f, 0xe2, 0x46, + 0x14, 0x2d, 0x21, 0x61, 0xc3, 0x05, 0x12, 0xef, 0x10, 0x12, 0x97, 0xcd, 0x6e, 0xa8, 0xfb, 0x21, + 0xd4, 0x56, 0xa0, 0x66, 0xdb, 0xaa, 0x6a, 0x1f, 0xaa, 0x14, 0x1c, 0x11, 0xf1, 0x61, 0x6a, 0x3b, + 0x9b, 0x6e, 0x55, 0x69, 0x64, 0x60, 0x16, 0x2c, 0xc0, 0x76, 0xc6, 0x43, 0xc1, 0xaf, 0xfd, 0x27, + 0xfd, 0x97, 0x7d, 0xac, 0x3c, 0xb6, 0xc9, 0x98, 0x24, 0xfb, 0x14, 0xe7, 0x9c, 0x73, 0xcf, 0xdc, + 0xb9, 0x33, 0x73, 0x80, 0x4f, 0xd7, 0xd6, 0x62, 0x41, 0x18, 0xf5, 0xc6, 0xcd, 0xe8, 0x6b, 0x6e, + 0xb3, 0x86, 0x47, 0x5d, 0xe6, 0xa2, 0xfc, 0x96, 0xaa, 0xe6, 0xa9, 0x37, 0x8e, 0xd0, 0xea, 0x89, + 0x6f, 0x4f, 0x9d, 0x50, 0x1e, 0xfe, 0x25, 0x34, 0x42, 0x95, 0xdf, 0x21, 0xd7, 0x25, 0x81, 0x4e, + 0xee, 0x51, 0x1d, 0xa4, 0x39, 0x09, 0xf0, 0x07, 0xdb, 0x99, 0x12, 0x8a, 0x3d, 0x6a, 0x3b, 0x4c, + 0xce, 0xd4, 0x32, 0xf5, 0x03, 0xfd, 0x68, 0x4e, 0x82, 0x6b, 0x0e, 0x0f, 0x43, 0x14, 0xbd, 0x06, + 0xe0, 0x4a, 0x6b, 0x69, 0x2f, 0x02, 0x79, 0x8f, 0x6b, 0xf2, 0xa1, 0x86, 0x03, 0x4a, 0x09, 0x0a, + 0x57, 0x93, 0x09, 0xd5, 0xc9, 0xfd, 0x8a, 0xf8, 0x4c, 0x51, 0xa0, 0x18, 0xfd, 0xeb, 0x7b, 0xae, + 0xe3, 0x13, 0x84, 0x60, 0xdf, 0x9a, 0x4c, 0x28, 0xf7, 0xce, 0xeb, 0xfc, 0x5b, 0xf9, 0x02, 0x0a, + 0x26, 0xb5, 0x1c, 0xdf, 0x1a, 0x33, 0xdb, 0x75, 0x50, 0x05, 0x72, 0x6c, 0x83, 0x67, 0x64, 0xc3, + 0x45, 0x45, 0xfd, 0x80, 0x6d, 0x3a, 0x64, 0xa3, 0xfc, 0x08, 0xc7, 0xc3, 0xd5, 0x68, 0x61, 0xfb, + 0xb3, 0xad, 0xd9, 0xe7, 0x50, 0xf2, 0x22, 0x08, 0x13, 0x4a, 0xdd, 0xc4, 0xb5, 0x18, 0x83, 0x6a, + 0x88, 0x29, 0x7f, 0x01, 0x32, 0x88, 0x33, 0xd1, 0x56, 0xcc, 0x5b, 0x31, 0x3f, 0xee, 0x0b, 0x9d, + 0x03, 0xf8, 0x16, 0xc3, 0x1e, 0xa1, 0x78, 0xbe, 0xe6, 0x75, 0x59, 0xfd, 0xd0, 0xb7, 0xd8, 0x90, + 0xd0, 0xee, 0x1a, 0xd5, 0xe1, 0x85, 0x1b, 0xe9, 0xe5, 0xbd, 0x5a, 0xb6, 0x5e, 0xb8, 0x3c, 0x6a, + 0xc4, 0xf3, 0x6b, 0x98, 0x1b, 0x6d, 0xc5, 0xf4, 0x84, 0x56, 0xbe, 0x85, 0x72, 0xca, 0x3d, 0xee, + 0xac, 0x02, 0x39, 0x6a, 0xad, 0x31, 0xdb, 0xee, 0x81, 0x5a, 0x6b, 0x73, 0xa3, 0xfc, 0x00, 0x48, + 0xf5, 0x99, 0xbd, 0xb4, 0x18, 0xb9, 0x26, 0x24, 0xe9, 0xe5, 0x02, 0x0a, 0x63, 0xd7, 0xf9, 0x80, + 0x99, 0x45, 0xa7, 0x24, 0x19, 0x3b, 0x84, 0x90, 0xc9, 0x11, 0xe5, 0x2d, 0x94, 0x53, 0x65, 0xf1, + 0x22, 0x1f, 0xdd, 0x83, 0xf2, 0xef, 0x1e, 0x14, 0x87, 0xc4, 0x99, 0xd8, 0xce, 0xd4, 0x58, 0x13, + 0xe2, 0xa1, 0x6f, 0xe0, 0x30, 0xec, 0xda, 0x4d, 0x8e, 0xb6, 0x70, 0x79, 0xdc, 0x58, 0xf0, 0x3d, + 0x69, 0x2b, 0x36, 0x0c, 0x61, 0x7d, 0x2b, 0x40, 0x3f, 0x43, 0x71, 0x6d, 0x33, 0x87, 0xf8, 0x3e, + 0x66, 0x81, 0x47, 0xf8, 0x39, 0x1f, 0x5d, 0x9e, 0x36, 0xb6, 0x97, 0xab, 0x71, 0x17, 0xd1, 0x66, + 0xe0, 0x11, 0x3d, 0xa5, 0x45, 0x6f, 0x00, 0xac, 0xa5, 0xbb, 0x72, 0x18, 0xf6, 0x2d, 0x26, 0x67, + 0x6b, 0x99, 0x7a, 0x49, 0x17, 0x10, 0xa4, 0x40, 0x31, 0xe9, 0x7b, 0x14, 0x30, 0x22, 0xef, 0x73, + 0x45, 0x0a, 0x43, 0x0d, 0x40, 0x23, 0xea, 0x5a, 0x93, 0xb1, 0xe5, 0x33, 0x6c, 0x31, 0x46, 0x96, + 0x1e, 0xf3, 0xe5, 0x03, 0xae, 0x7c, 0x82, 0x41, 0xdf, 0x43, 0xc5, 0x21, 0x1b, 0x86, 0x1f, 0xa8, + 0x19, 0xb1, 0xa7, 0x33, 0x26, 0xe7, 0x78, 0xc9, 0xd3, 0xa4, 0x72, 0x0a, 0x27, 0xe2, 0x88, 0x92, + 0xdb, 0xa1, 0xfc, 0x01, 0x95, 0x1d, 0x3c, 0x1e, 0xf9, 0xaf, 0x70, 0xe4, 0x45, 0x04, 0xf6, 0x39, + 0x23, 0x67, 0xf8, 0xfd, 0x38, 0x13, 0x06, 0x23, 0x56, 0xea, 0x3b, 0xf2, 0xaf, 0xff, 0xc9, 0x42, + 0x41, 0x98, 0x1c, 0x2a, 0xc3, 0xf1, 0xed, 0xa0, 0x3b, 0xd0, 0xee, 0x06, 0xf8, 0xee, 0xc6, 0x1c, + 0xa8, 0x86, 0x21, 0x7d, 0x82, 0x64, 0x38, 0x69, 0x69, 0xfd, 0xfe, 0x8d, 0xd9, 0x57, 0x07, 0x26, + 0x36, 0x6f, 0xfa, 0x2a, 0xee, 0x69, 0xad, 0xae, 0x94, 0x41, 0x67, 0x50, 0x16, 0x98, 0x81, 0x86, + 0xdb, 0x6a, 0xef, 0xea, 0xbd, 0xb4, 0x87, 0x2a, 0xf0, 0x52, 0x20, 0x74, 0xf5, 0x9d, 0xd6, 0x55, + 0xa5, 0x6c, 0xa8, 0xef, 0x98, 0xbd, 0x16, 0xd6, 0xae, 0xaf, 0x55, 0x5d, 0x6d, 0x27, 0xc4, 0x7e, + 0xb8, 0x04, 0x27, 0xae, 0x5a, 0x2d, 0x75, 0x68, 0x3e, 0x30, 0x07, 0xe8, 0x4b, 0xf8, 0x2c, 0x55, + 0x12, 0x2e, 0xaf, 0xdd, 0x9a, 0xd8, 0x50, 0x5b, 0xda, 0xa0, 0x8d, 0x7b, 0xea, 0x3b, 0xb5, 0x27, + 0xe5, 0xd0, 0x57, 0xa0, 0xa4, 0x0d, 0x8c, 0xdb, 0x56, 0x4b, 0x35, 0x8c, 0xb4, 0xee, 0x05, 0xba, + 0x80, 0x57, 0x3b, 0x1d, 0xf4, 0x35, 0x53, 0x4d, 0x5c, 0xa5, 0x43, 0x54, 0x83, 0xf3, 0xdd, 0x4e, + 0xb8, 0x22, 0xf6, 0x93, 0xf2, 0xe8, 0x1c, 0x64, 0xae, 0x10, 0x9d, 0x93, 0x7e, 0x01, 0x9d, 0x80, + 0x14, 0x4f, 0x0e, 0x77, 0xd5, 0xf7, 0xb8, 0x73, 0x65, 0x74, 0xa4, 0x02, 0x7a, 0x05, 0x67, 0x03, + 0xd5, 0x08, 0xed, 0x1e, 0x91, 0xc5, 0xcb, 0xff, 0xb2, 0x90, 0xbf, 0xe3, 0xe7, 0xd5, 0xb5, 0xc3, + 0xab, 0x5e, 0x6a, 0x13, 0x6a, 0xff, 0x4d, 0x06, 0x64, 0xc3, 0xba, 0x24, 0x40, 0x2f, 0x85, 0xc3, + 0x8c, 0xe2, 0xb1, 0x7a, 0xba, 0x7d, 0xff, 0x5d, 0x12, 0xb4, 0x89, 0x3f, 0xa6, 0xb6, 0xc7, 0x5c, + 0x8a, 0x7e, 0x82, 0x7c, 0x54, 0x1b, 0xd6, 0x95, 0x45, 0x51, 0xcf, 0x1d, 0x5b, 0xcc, 0xa5, 0xcf, + 0x56, 0xfe, 0x02, 0x87, 0xe1, 0x7a, 0x61, 0x38, 0x22, 0xf1, 0x59, 0x09, 0xe1, 0x59, 0x3d, 0x7b, + 0x84, 0xc7, 0xd7, 0xb0, 0x03, 0x28, 0xce, 0x42, 0x31, 0x38, 0x45, 0x1b, 0x01, 0xaf, 0x56, 0xc5, + 0xcb, 0xb9, 0x13, 0xa1, 0x3d, 0x28, 0x08, 0xf9, 0x85, 0x5e, 0x0b, 0xd2, 0xc7, 0xa9, 0x59, 0x7d, + 0xf3, 0x1c, 0xfd, 0xe0, 0x26, 0x04, 0x55, 0xca, 0xed, 0x71, 0xee, 0xa5, 0xdc, 0x9e, 0xca, 0x37, + 0x1d, 0x4a, 0xa9, 0x57, 0x88, 0x2e, 0x9e, 0x79, 0x65, 0xdb, 0xfe, 0x6a, 0xcf, 0x0b, 0x22, 0xcf, + 0xdf, 0xbe, 0xfb, 0xb3, 0x39, 0xb5, 0xd9, 0x6c, 0x35, 0x6a, 0x8c, 0xdd, 0x65, 0x73, 0x11, 0xa6, + 0x80, 0x63, 0x3b, 0x53, 0x87, 0xb0, 0xb5, 0x4b, 0xe7, 0xcd, 0x85, 0x33, 0x69, 0xf2, 0x4c, 0x6c, + 0x6e, 0x8d, 0x46, 0x39, 0xfe, 0x5b, 0xf9, 0xf6, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xbf, 0xc4, + 0xea, 0x93, 0x74, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -502,6 +796,16 @@ type WalletKitClient interface { //determine the fee (in sat/kw) to attach to a transaction in order to //achieve the confirmation target. EstimateFee(ctx context.Context, in *EstimateFeeRequest, opts ...grpc.CallOption) (*EstimateFeeResponse, error) + // + //PendingSweeps returns lists of on-chain outputs that lnd is currently + //attempting to sweep within its central batching engine. Outputs with similar + //fee rates are batched together in order to sweep them within a single + //transaction. + // + //NOTE: Some of the fields within PendingSweepsRequest are not guaranteed to + //remain supported. This is an advanced API that depends on the internals of + //the UtxoSweeper, so things may change. + PendingSweeps(ctx context.Context, in *PendingSweepsRequest, opts ...grpc.CallOption) (*PendingSweepsResponse, error) } type walletKitClient struct { @@ -566,6 +870,15 @@ func (c *walletKitClient) EstimateFee(ctx context.Context, in *EstimateFeeReques return out, nil } +func (c *walletKitClient) PendingSweeps(ctx context.Context, in *PendingSweepsRequest, opts ...grpc.CallOption) (*PendingSweepsResponse, error) { + out := new(PendingSweepsResponse) + err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/PendingSweeps", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // WalletKitServer is the server API for WalletKit service. type WalletKitServer interface { //* @@ -596,6 +909,16 @@ type WalletKitServer interface { //determine the fee (in sat/kw) to attach to a transaction in order to //achieve the confirmation target. EstimateFee(context.Context, *EstimateFeeRequest) (*EstimateFeeResponse, error) + // + //PendingSweeps returns lists of on-chain outputs that lnd is currently + //attempting to sweep within its central batching engine. Outputs with similar + //fee rates are batched together in order to sweep them within a single + //transaction. + // + //NOTE: Some of the fields within PendingSweepsRequest are not guaranteed to + //remain supported. This is an advanced API that depends on the internals of + //the UtxoSweeper, so things may change. + PendingSweeps(context.Context, *PendingSweepsRequest) (*PendingSweepsResponse, error) } func RegisterWalletKitServer(s *grpc.Server, srv WalletKitServer) { @@ -710,6 +1033,24 @@ func _WalletKit_EstimateFee_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } +func _WalletKit_PendingSweeps_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PendingSweepsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletKitServer).PendingSweeps(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/walletrpc.WalletKit/PendingSweeps", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletKitServer).PendingSweeps(ctx, req.(*PendingSweepsRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _WalletKit_serviceDesc = grpc.ServiceDesc{ ServiceName: "walletrpc.WalletKit", HandlerType: (*WalletKitServer)(nil), @@ -738,6 +1079,10 @@ var _WalletKit_serviceDesc = grpc.ServiceDesc{ MethodName: "EstimateFee", Handler: _WalletKit_EstimateFee_Handler, }, + { + MethodName: "PendingSweeps", + Handler: _WalletKit_PendingSweeps_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "walletrpc/walletkit.proto", diff --git a/lnrpc/walletrpc/walletkit.proto b/lnrpc/walletrpc/walletkit.proto index e1b0518bd..eba003442 100644 --- a/lnrpc/walletrpc/walletkit.proto +++ b/lnrpc/walletrpc/walletkit.proto @@ -1,5 +1,6 @@ syntax = "proto3"; +import "rpc.proto"; import "signrpc/signer.proto"; package walletrpc; @@ -81,6 +82,127 @@ message EstimateFeeResponse { int64 sat_per_kw = 1; } +enum WitnessType { + UNKNOWN_WITNESS = 0; + + /* + A witness that allows us to spend the output of a commitment transaction + after a relative lock-time lockout. + */ + COMMITMENT_TIME_LOCK = 1; + + /* + A witness that allows us to spend a settled no-delay output immediately on a + counterparty's commitment transaction. + */ + COMMITMENT_NO_DELAY = 2; + + /* + A witness that allows us to sweep the settled output of a malicious + counterparty's who broadcasts a revoked commitment transaction. + */ + COMMITMENT_REVOKE = 3; + + /* + A witness that allows us to sweep an HTLC which we offered to the remote + party in the case that they broadcast a revoked commitment state. + */ + HTLC_OFFERED_REVOKE = 4; + + /* + A witness that allows us to sweep an HTLC output sent to us in the case that + the remote party broadcasts a revoked commitment state. + */ + HTLC_ACCEPTED_REVOKE = 5; + + /* + A witness that allows us to sweep an HTLC output that we extended to a + party, but was never fulfilled. This HTLC output isn't directly on the + commitment transaction, but is the result of a confirmed second-level HTLC + transaction. As a result, we can only spend this after a CSV delay. + */ + HTLC_OFFERED_TIMEOUT_SECOND_LEVEL = 6; + + /* + A witness that allows us to sweep an HTLC output that was offered to us, and + for which we have a payment preimage. This HTLC output isn't directly on our + commitment transaction, but is the result of confirmed second-level HTLC + transaction. As a result, we can only spend this after a CSV delay. + */ + HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL = 7; + + /* + A witness that allows us to sweep an HTLC that we offered to the remote + party which lies in the commitment transaction of the remote party. We can + spend this output after the absolute CLTV timeout of the HTLC as passed. + */ + HTLC_OFFERED_REMOTE_TIMEOUT = 8; + + /* + A witness that allows us to sweep an HTLC that was offered to us by the + remote party. We use this witness in the case that the remote party goes to + chain, and we know the pre-image to the HTLC. We can sweep this without any + additional timeout. + */ + HTLC_ACCEPTED_REMOTE_SUCCESS = 9; + + /* + A witness that allows us to sweep an HTLC from the remote party's commitment + transaction in the case that the broadcast a revoked commitment, but then + also immediately attempt to go to the second level to claim the HTLC. + */ + HTLC_SECOND_LEVEL_REVOKE = 10; + + /* + A witness type that allows us to spend a regular p2wkh output that's sent to + an output which is under complete control of the backing wallet. + */ + WITNESS_KEY_HASH = 11; + + /* + A witness type that allows us to sweep an output that sends to a nested P2SH + script that pays to a key solely under our control. + */ + NESTED_WITNESS_KEY_HASH = 12; +} + +message PendingSweep { + // The outpoint of the output we're attempting to sweep. + lnrpc.OutPoint outpoint = 1 [json_name = "outpoint"]; + + // The witness type of the output we're attempting to sweep. + WitnessType witness_type = 2 [json_name = "witness_type"]; + + // The value of the output we're attempting to sweep. + uint32 amount_sat = 3 [json_name = "amount_sat"]; + + /* + The fee rate we'll use to sweep the output. The fee rate is only determined + once a sweeping transaction for the output is created, so it's possible for + this to be 0 before this. + */ + uint32 sat_per_byte = 4 [json_name = "sat_per_byte"]; + + // The number of broadcast attempts we've made to sweep the output. + uint32 broadcast_attempts = 5 [json_name = "broadcast_attempts"]; + + /* + The next height of the chain at which we'll attempt to broadcast the + sweep transaction of the output. + */ + uint32 next_broadcast_height = 6 [json_name = "next_broadcast_height"]; +} + +message PendingSweepsRequest { +} + +message PendingSweepsResponse { + /* + The set of outputs currently being swept by lnd's central batching engine. + */ + repeated PendingSweep pending_sweeps = 1 [json_name = "pending_sweeps"]; +} + service WalletKit { /** DeriveNextKey attempts to derive the *next* key within the key family @@ -121,4 +243,16 @@ service WalletKit { achieve the confirmation target. */ rpc EstimateFee(EstimateFeeRequest) returns (EstimateFeeResponse); + + /* + PendingSweeps returns lists of on-chain outputs that lnd is currently + attempting to sweep within its central batching engine. Outputs with similar + fee rates are batched together in order to sweep them within a single + transaction. + + NOTE: Some of the fields within PendingSweepsRequest are not guaranteed to + remain supported. This is an advanced API that depends on the internals of + the UtxoSweeper, so things may change. + */ + rpc PendingSweeps(PendingSweepsRequest) returns (PendingSweepsResponse); } diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 3e998fe23..f2e04f593 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -10,6 +10,7 @@ import ( "path/filepath" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" @@ -74,6 +75,10 @@ var ( Entity: "onchain", Action: "read", }}, + "/walletrpc.WalletKit/PendingSweeps": {{ + Entity: "onchain", + Action: "read", + }}, } // DefaultWalletKitMacFilename is the default name of the wallet kit @@ -331,3 +336,76 @@ func (w *WalletKit) EstimateFee(ctx context.Context, SatPerKw: int64(satPerKw), }, nil } + +// PendingSweeps returns lists of on-chain outputs that lnd is currently +// attempting to sweep within its central batching engine. Outputs with similar +// fee rates are batched together in order to sweep them within a single +// transaction. The fee rate of each sweeping transaction is determined by +// taking the average fee rate of all the outputs it's trying to sweep. +func (w *WalletKit) PendingSweeps(ctx context.Context, + in *PendingSweepsRequest) (*PendingSweepsResponse, error) { + + // Retrieve all of the outputs the UtxoSweeper is currently trying to + // sweep. + pendingInputs, err := w.cfg.Sweeper.PendingInputs() + if err != nil { + return nil, err + } + + // Convert them into their respective RPC format. + rpcPendingSweeps := make([]*PendingSweep, 0, len(pendingInputs)) + for _, pendingInput := range pendingInputs { + var witnessType WitnessType + switch pendingInput.WitnessType { + case input.CommitmentTimeLock: + witnessType = WitnessType_COMMITMENT_TIME_LOCK + case input.CommitmentNoDelay: + witnessType = WitnessType_COMMITMENT_NO_DELAY + case input.CommitmentRevoke: + witnessType = WitnessType_COMMITMENT_REVOKE + case input.HtlcOfferedRevoke: + witnessType = WitnessType_HTLC_OFFERED_REVOKE + case input.HtlcAcceptedRevoke: + witnessType = WitnessType_HTLC_ACCEPTED_REVOKE + case input.HtlcOfferedTimeoutSecondLevel: + witnessType = WitnessType_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL + case input.HtlcAcceptedSuccessSecondLevel: + witnessType = WitnessType_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL + case input.HtlcOfferedRemoteTimeout: + witnessType = WitnessType_HTLC_OFFERED_REMOTE_TIMEOUT + case input.HtlcAcceptedRemoteSuccess: + witnessType = WitnessType_HTLC_ACCEPTED_REMOTE_SUCCESS + case input.HtlcSecondLevelRevoke: + witnessType = WitnessType_HTLC_SECOND_LEVEL_REVOKE + case input.WitnessKeyHash: + witnessType = WitnessType_WITNESS_KEY_HASH + case input.NestedWitnessKeyHash: + witnessType = WitnessType_NESTED_WITNESS_KEY_HASH + default: + log.Warnf("Unhandled witness type %v for input %v", + pendingInput.WitnessType, pendingInput.OutPoint) + } + + op := &lnrpc.OutPoint{ + TxidBytes: pendingInput.OutPoint.Hash[:], + OutputIndex: pendingInput.OutPoint.Index, + } + amountSat := uint32(pendingInput.Amount) + satPerByte := uint32(pendingInput.LastFeeRate.FeePerKVByte() / 1000) + broadcastAttempts := uint32(pendingInput.BroadcastAttempts) + nextBroadcastHeight := uint32(pendingInput.NextBroadcastHeight) + + rpcPendingSweeps = append(rpcPendingSweeps, &PendingSweep{ + Outpoint: op, + WitnessType: witnessType, + AmountSat: amountSat, + SatPerByte: satPerByte, + BroadcastAttempts: broadcastAttempts, + NextBroadcastHeight: nextBroadcastHeight, + }) + } + + return &PendingSweepsResponse{ + PendingSweeps: rpcPendingSweeps, + }, nil +} diff --git a/rpcserver.go b/rpcserver.go index b827b65b1..dca0a93a9 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -491,7 +491,7 @@ func newRPCServer(s *server, macService *macaroons.Service, err = subServerCgs.PopulateDependencies( s.cc, networkDir, macService, atpl, invoiceRegistry, s.htlcSwitch, activeNetParams.Params, s.chanRouter, - routerBackend, s.nodeSigner, s.chanDB, + routerBackend, s.nodeSigner, s.chanDB, s.sweeper, ) if err != nil { return nil, err diff --git a/subrpcserver_config.go b/subrpcserver_config.go index d99faeab6..cb9b20768 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -18,6 +18,7 @@ import ( "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/netann" "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/sweep" ) // subRPCServerConfigs is special sub-config in the main configuration that @@ -72,7 +73,8 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl, chanRouter *routing.ChannelRouter, routerBackend *routerrpc.RouterBackend, nodeSigner *netann.NodeSigner, - chanDB *channeldb.DB) error { + chanDB *channeldb.DB, + sweeper *sweep.UtxoSweeper) error { // First, we'll use reflect to obtain a version of the config struct // that allows us to programmatically inspect its fields. @@ -129,6 +131,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl, subCfgValue.FieldByName("KeyRing").Set( reflect.ValueOf(cc.keyRing), ) + subCfgValue.FieldByName("Sweeper").Set( + reflect.ValueOf(sweeper), + ) case *autopilotrpc.Config: subCfgValue := extractReflectValue(subCfg) From 1d8b70eaa6b054fdddb5d8c7a792b31ade69950c Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Thu, 16 May 2019 19:53:42 -0700 Subject: [PATCH 4/4] cmd/lncli: expose pending input sweeps within UtxoSweeper over lncli --- cmd/lncli/main.go | 1 + cmd/lncli/types.go | 5 +- cmd/lncli/walletrpc_active.go | 83 ++++++++++++++++++++++++++++++++++ cmd/lncli/walletrpc_default.go | 10 ++++ cmd/lncli/walletrpc_types.go | 27 +++++++++++ 5 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 cmd/lncli/walletrpc_active.go create mode 100644 cmd/lncli/walletrpc_default.go create mode 100644 cmd/lncli/walletrpc_types.go diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index 73a6ce757..51917e4e5 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -304,6 +304,7 @@ func main() { app.Commands = append(app.Commands, autopilotCommands()...) app.Commands = append(app.Commands, invoicesCommands()...) app.Commands = append(app.Commands, routerCommands()...) + app.Commands = append(app.Commands, walletCommands()...) if err := app.Run(os.Args); err != nil { fatal(err) diff --git a/cmd/lncli/types.go b/cmd/lncli/types.go index 80f7cdf68..78d640f94 100644 --- a/cmd/lncli/types.go +++ b/cmd/lncli/types.go @@ -3,6 +3,7 @@ package main import ( "fmt" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnrpc" ) @@ -11,7 +12,9 @@ type OutPoint string // NewOutPointFromProto formats the lnrpc.OutPoint into an OutPoint for display. func NewOutPointFromProto(op *lnrpc.OutPoint) OutPoint { - return OutPoint(fmt.Sprintf("%s:%d", op.TxidStr, op.OutputIndex)) + var hash chainhash.Hash + copy(hash[:], op.TxidBytes) + return OutPoint(fmt.Sprintf("%v:%d", hash, op.OutputIndex)) } // Utxo displays information about an unspent output, including its address, diff --git a/cmd/lncli/walletrpc_active.go b/cmd/lncli/walletrpc_active.go new file mode 100644 index 000000000..e86819bb4 --- /dev/null +++ b/cmd/lncli/walletrpc_active.go @@ -0,0 +1,83 @@ +// +build walletrpc + +package main + +import ( + "context" + "sort" + + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/urfave/cli" +) + +// walletCommands will return the set of commands to enable for walletrpc +// builds. +func walletCommands() []cli.Command { + return []cli.Command{ + { + Name: "wallet", + Category: "Wallet", + Usage: "Interact with the wallet.", + Description: "", + Subcommands: []cli.Command{ + pendingSweepsCommand, + }, + }, + } +} + +func getWalletClient(ctx *cli.Context) (walletrpc.WalletKitClient, func()) { + conn := getClientConn(ctx, false) + cleanUp := func() { + conn.Close() + } + return walletrpc.NewWalletKitClient(conn), cleanUp +} + +var pendingSweepsCommand = cli.Command{ + Name: "pendingsweeps", + Usage: "List all outputs that are pending to be swept within lnd.", + ArgsUsage: "", + Description: ` + List all on-chain outputs that lnd is currently attempting to sweep + within its central batching engine. Outputs with similar fee rates are + batched together in order to sweep them within a single transaction. + `, + Flags: []cli.Flag{}, + Action: actionDecorator(pendingSweeps), +} + +func pendingSweeps(ctx *cli.Context) error { + ctxb := context.Background() + client, cleanUp := getWalletClient(ctx) + defer cleanUp() + + req := &walletrpc.PendingSweepsRequest{} + resp, err := client.PendingSweeps(ctxb, req) + if err != nil { + return err + } + + // Sort them in ascending fee rate order for display purposes. + sort.Slice(resp.PendingSweeps, func(i, j int) bool { + return resp.PendingSweeps[i].SatPerByte < + resp.PendingSweeps[j].SatPerByte + }) + + var pendingSweepsResp = struct { + PendingSweeps []*PendingSweep `json:"pending_sweeps"` + }{ + PendingSweeps: make([]*PendingSweep, 0, len(resp.PendingSweeps)), + } + + for _, protoPendingSweep := range resp.PendingSweeps { + pendingSweep := NewPendingSweepFromProto(protoPendingSweep) + pendingSweepsResp.PendingSweeps = append( + pendingSweepsResp.PendingSweeps, pendingSweep, + ) + } + + printJSON(pendingSweepsResp) + + return nil +} diff --git a/cmd/lncli/walletrpc_default.go b/cmd/lncli/walletrpc_default.go new file mode 100644 index 000000000..f919a9933 --- /dev/null +++ b/cmd/lncli/walletrpc_default.go @@ -0,0 +1,10 @@ +// +build !walletrpc + +package main + +import "github.com/urfave/cli" + +// walletCommands will return nil for non-walletrpc builds. +func walletCommands() []cli.Command { + return nil +} diff --git a/cmd/lncli/walletrpc_types.go b/cmd/lncli/walletrpc_types.go new file mode 100644 index 000000000..f336e02e2 --- /dev/null +++ b/cmd/lncli/walletrpc_types.go @@ -0,0 +1,27 @@ +package main + +import "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + +// PendingSweep is a CLI-friendly type of the walletrpc.PendingSweep proto. We +// use this to show more useful string versions of byte slices and enums. +type PendingSweep struct { + OutPoint OutPoint `json:"outpoint"` + WitnessType string `json:"witness_type"` + AmountSat uint32 `json:"amount_sat"` + SatPerByte uint32 `json:"sat_per_byte"` + BroadcastAttempts uint32 `json:"broadcast_attempts"` + NextBroadcastHeight uint32 `json:"next_broadcast_height"` +} + +// NewPendingSweepFromProto converts the walletrpc.PendingSweep proto type into +// its corresponding CLI-friendly type. +func NewPendingSweepFromProto(pendingSweep *walletrpc.PendingSweep) *PendingSweep { + return &PendingSweep{ + OutPoint: NewOutPointFromProto(pendingSweep.Outpoint), + WitnessType: pendingSweep.WitnessType.String(), + AmountSat: pendingSweep.AmountSat, + SatPerByte: pendingSweep.SatPerByte, + BroadcastAttempts: pendingSweep.BroadcastAttempts, + NextBroadcastHeight: pendingSweep.NextBroadcastHeight, + } +}