mirror of https://github.com/lightninglabs/loop
Merge c7d2160153
into 7ff3318185
commit
8ed875ffea
@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS deposits;
|
@ -0,0 +1,44 @@
|
||||
-- deposits stores historic and unspent static address outputs.
|
||||
CREATE TABLE IF NOT EXISTS deposits (
|
||||
-- id is the auto-incrementing primary key for a static address.
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
||||
-- deposit_id is the unique identifier for the deposit.
|
||||
deposit_id BLOB NOT NULL UNIQUE,
|
||||
|
||||
-- tx_hash is the transaction hash of the deposit.
|
||||
tx_hash BYTEA NOT NULL,
|
||||
|
||||
-- output_index is the index of the output in the transaction.
|
||||
out_index INT NOT NULL,
|
||||
|
||||
-- amount is the amount of the deposit.
|
||||
amount BIGINT NOT NULL,
|
||||
|
||||
-- confirmation_height is the absolute height at which the deposit was
|
||||
-- confirmed.
|
||||
confirmation_height BIGINT NOT NULL,
|
||||
|
||||
-- timeout_sweep_pk_script is the public key script that will be used to
|
||||
-- sweep the deposit after has expired.
|
||||
timeout_sweep_pk_script BYTEA NOT NULL,
|
||||
|
||||
-- withdrawal_sweep_pk_script is the address that will be used to sweep the
|
||||
-- deposit cooperatively with the server before it has expired.
|
||||
withdrawal_sweep_address TEXT
|
||||
);
|
||||
|
||||
-- deposit_updates contains all the updates to a deposit.
|
||||
CREATE TABLE IF NOT EXISTS deposit_updates (
|
||||
-- id is the auto incrementing primary key.
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
||||
-- deposit_id is the unique identifier for the deposit.
|
||||
deposit_id BLOB NOT NULL REFERENCES deposits(deposit_id),
|
||||
|
||||
-- update_state is the state of the deposit at the time of the update.
|
||||
update_state TEXT NOT NULL,
|
||||
|
||||
-- update_timestamp is the timestamp of the update.
|
||||
update_timestamp TIMESTAMP NOT NULL
|
||||
);
|
@ -0,0 +1,66 @@
|
||||
-- name: CreateDeposit :exec
|
||||
INSERT INTO deposits (
|
||||
deposit_id,
|
||||
tx_hash,
|
||||
out_index,
|
||||
amount,
|
||||
confirmation_height,
|
||||
timeout_sweep_pk_script,
|
||||
withdrawal_sweep_address
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7
|
||||
);
|
||||
|
||||
-- name: UpdateDeposit :exec
|
||||
UPDATE deposits
|
||||
SET
|
||||
tx_hash = $2,
|
||||
out_index = $3,
|
||||
confirmation_height = $4,
|
||||
withdrawal_sweep_address = $5
|
||||
WHERE
|
||||
deposits.deposit_id = $1;
|
||||
|
||||
-- name: InsertDepositUpdate :exec
|
||||
INSERT INTO deposit_updates (
|
||||
deposit_id,
|
||||
update_state,
|
||||
update_timestamp
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3
|
||||
);
|
||||
|
||||
-- name: GetDeposit :one
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
deposits
|
||||
WHERE
|
||||
deposit_id = $1;
|
||||
|
||||
-- name: AllDeposits :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
deposits
|
||||
ORDER BY
|
||||
id ASC;
|
||||
|
||||
-- name: GetLatestDepositUpdate :one
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
deposit_updates
|
||||
WHERE
|
||||
deposit_id = $1
|
||||
ORDER BY
|
||||
update_timestamp DESC
|
||||
LIMIT 1;
|
@ -0,0 +1,198 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.17.2
|
||||
// source: static_address_deposits.sql
|
||||
|
||||
package sqlc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
const allDeposits = `-- name: AllDeposits :many
|
||||
SELECT
|
||||
id, deposit_id, tx_hash, out_index, amount, confirmation_height, timeout_sweep_pk_script, withdrawal_sweep_address
|
||||
FROM
|
||||
deposits
|
||||
ORDER BY
|
||||
id ASC
|
||||
`
|
||||
|
||||
func (q *Queries) AllDeposits(ctx context.Context) ([]Deposit, error) {
|
||||
rows, err := q.db.QueryContext(ctx, allDeposits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Deposit
|
||||
for rows.Next() {
|
||||
var i Deposit
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.DepositID,
|
||||
&i.TxHash,
|
||||
&i.OutIndex,
|
||||
&i.Amount,
|
||||
&i.ConfirmationHeight,
|
||||
&i.TimeoutSweepPkScript,
|
||||
&i.WithdrawalSweepAddress,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const createDeposit = `-- name: CreateDeposit :exec
|
||||
INSERT INTO deposits (
|
||||
deposit_id,
|
||||
tx_hash,
|
||||
out_index,
|
||||
amount,
|
||||
confirmation_height,
|
||||
timeout_sweep_pk_script,
|
||||
withdrawal_sweep_address
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7
|
||||
)
|
||||
`
|
||||
|
||||
type CreateDepositParams struct {
|
||||
DepositID []byte
|
||||
TxHash []byte
|
||||
OutIndex int32
|
||||
Amount int64
|
||||
ConfirmationHeight int64
|
||||
TimeoutSweepPkScript []byte
|
||||
WithdrawalSweepAddress sql.NullString
|
||||
}
|
||||
|
||||
func (q *Queries) CreateDeposit(ctx context.Context, arg CreateDepositParams) error {
|
||||
_, err := q.db.ExecContext(ctx, createDeposit,
|
||||
arg.DepositID,
|
||||
arg.TxHash,
|
||||
arg.OutIndex,
|
||||
arg.Amount,
|
||||
arg.ConfirmationHeight,
|
||||
arg.TimeoutSweepPkScript,
|
||||
arg.WithdrawalSweepAddress,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const getDeposit = `-- name: GetDeposit :one
|
||||
SELECT
|
||||
id, deposit_id, tx_hash, out_index, amount, confirmation_height, timeout_sweep_pk_script, withdrawal_sweep_address
|
||||
FROM
|
||||
deposits
|
||||
WHERE
|
||||
deposit_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetDeposit(ctx context.Context, depositID []byte) (Deposit, error) {
|
||||
row := q.db.QueryRowContext(ctx, getDeposit, depositID)
|
||||
var i Deposit
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.DepositID,
|
||||
&i.TxHash,
|
||||
&i.OutIndex,
|
||||
&i.Amount,
|
||||
&i.ConfirmationHeight,
|
||||
&i.TimeoutSweepPkScript,
|
||||
&i.WithdrawalSweepAddress,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getLatestDepositUpdate = `-- name: GetLatestDepositUpdate :one
|
||||
SELECT
|
||||
id, deposit_id, update_state, update_timestamp
|
||||
FROM
|
||||
deposit_updates
|
||||
WHERE
|
||||
deposit_id = $1
|
||||
ORDER BY
|
||||
update_timestamp DESC
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetLatestDepositUpdate(ctx context.Context, depositID []byte) (DepositUpdate, error) {
|
||||
row := q.db.QueryRowContext(ctx, getLatestDepositUpdate, depositID)
|
||||
var i DepositUpdate
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.DepositID,
|
||||
&i.UpdateState,
|
||||
&i.UpdateTimestamp,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertDepositUpdate = `-- name: InsertDepositUpdate :exec
|
||||
INSERT INTO deposit_updates (
|
||||
deposit_id,
|
||||
update_state,
|
||||
update_timestamp
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3
|
||||
)
|
||||
`
|
||||
|
||||
type InsertDepositUpdateParams struct {
|
||||
DepositID []byte
|
||||
UpdateState string
|
||||
UpdateTimestamp time.Time
|
||||
}
|
||||
|
||||
func (q *Queries) InsertDepositUpdate(ctx context.Context, arg InsertDepositUpdateParams) error {
|
||||
_, err := q.db.ExecContext(ctx, insertDepositUpdate, arg.DepositID, arg.UpdateState, arg.UpdateTimestamp)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateDeposit = `-- name: UpdateDeposit :exec
|
||||
UPDATE deposits
|
||||
SET
|
||||
tx_hash = $2,
|
||||
out_index = $3,
|
||||
confirmation_height = $4,
|
||||
withdrawal_sweep_address = $5
|
||||
WHERE
|
||||
deposits.deposit_id = $1
|
||||
`
|
||||
|
||||
type UpdateDepositParams struct {
|
||||
DepositID []byte
|
||||
TxHash []byte
|
||||
OutIndex int32
|
||||
ConfirmationHeight int64
|
||||
WithdrawalSweepAddress sql.NullString
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateDeposit(ctx context.Context, arg UpdateDepositParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateDeposit,
|
||||
arg.DepositID,
|
||||
arg.TxHash,
|
||||
arg.OutIndex,
|
||||
arg.ConfirmationHeight,
|
||||
arg.WithdrawalSweepAddress,
|
||||
)
|
||||
return err
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
// Code generated by falafel 0.9.1. DO NOT EDIT.
|
||||
// source: client.proto
|
||||
|
||||
package looprpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
func RegisterStaticAddressClientJSONCallbacks(registry map[string]func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
|
||||
|
||||
marshaler := &gateway.JSONPb{
|
||||
MarshalOptions: protojson.MarshalOptions{
|
||||
UseProtoNames: true,
|
||||
EmitUnpopulated: true,
|
||||
},
|
||||
}
|
||||
|
||||
registry["looprpc.StaticAddressClient.NewAddress"] = func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
req := &NewAddressRequest{}
|
||||
err := marshaler.Unmarshal([]byte(reqJSON), req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
client := NewStaticAddressClientClient(conn)
|
||||
resp, err := client.NewAddress(ctx, req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
respBytes, err := marshaler.Marshal(resp)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
callback(string(respBytes), nil)
|
||||
}
|
||||
|
||||
registry["looprpc.StaticAddressClient.ListUnspent"] = func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
req := &ListUnspentRequest{}
|
||||
err := marshaler.Unmarshal([]byte(reqJSON), req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
client := NewStaticAddressClientClient(conn)
|
||||
resp, err := client.ListUnspent(ctx, req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
respBytes, err := marshaler.Marshal(resp)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
callback(string(respBytes), nil)
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package address
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
)
|
||||
|
||||
// Subsystem defines the sub system name of this package.
|
||||
const Subsystem = "SADDR"
|
||||
|
||||
// log is a logger that is initialized with no output filters. This means the
|
||||
// package will not perform any logging by default until the caller requests it.
|
||||
var log btclog.Logger
|
||||
|
||||
// The default amount of logging is none.
|
||||
func init() {
|
||||
UseLogger(build.NewSubLogger(Subsystem, nil))
|
||||
}
|
||||
|
||||
// UseLogger uses a specified Logger to output package logging info. This should
|
||||
// be used in preference to SetLogWriter if the caller is also using btclog.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
||||
|
||||
// GetLogger returns the logger for this package.
|
||||
func GetLogger() btclog.Logger {
|
||||
return log
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
package deposit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/fsm"
|
||||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultConfTarget = 3
|
||||
)
|
||||
|
||||
// PublishDepositExpirySweepAction creates and publishes the timeout transaction
|
||||
// that spends the deposit from the static address timeout leaf to the
|
||||
// predefined timeout sweep pkscript.
|
||||
func (f *FSM) PublishDepositExpirySweepAction(_ fsm.EventContext) fsm.EventType {
|
||||
msgTx := wire.NewMsgTx(2)
|
||||
|
||||
params, err := f.cfg.AddressManager.GetStaticAddressParameters(f.ctx)
|
||||
if err != nil {
|
||||
return fsm.OnError
|
||||
}
|
||||
|
||||
// Add the deposit outpoint as input to the transaction.
|
||||
msgTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: f.deposit.OutPoint,
|
||||
Sequence: params.Expiry,
|
||||
SignatureScript: nil,
|
||||
})
|
||||
|
||||
// Estimate the fee rate of an expiry spend transaction.
|
||||
feeRateEstimator, err := f.cfg.WalletKit.EstimateFeeRate(
|
||||
f.ctx, defaultConfTarget,
|
||||
)
|
||||
if err != nil {
|
||||
return f.HandleError(fmt.Errorf("timeout sweep fee "+
|
||||
"estimation failed: %v", err))
|
||||
}
|
||||
|
||||
weight := script.ExpirySpendWeight()
|
||||
|
||||
fee := feeRateEstimator.FeeForWeight(weight)
|
||||
|
||||
// We cap the fee at 20% of the deposit value.
|
||||
if fee > f.deposit.Value/5 {
|
||||
return f.HandleError(errors.New("fee is greater than 20% of " +
|
||||
"the deposit value"))
|
||||
}
|
||||
|
||||
output := &wire.TxOut{
|
||||
Value: int64(f.deposit.Value - fee),
|
||||
PkScript: f.deposit.TimeOutSweepPkScript,
|
||||
}
|
||||
msgTx.AddTxOut(output)
|
||||
|
||||
txOut := &wire.TxOut{
|
||||
Value: int64(f.deposit.Value),
|
||||
PkScript: params.PkScript,
|
||||
}
|
||||
|
||||
prevOut := []*wire.TxOut{txOut}
|
||||
|
||||
signDesc, err := f.SignDescriptor()
|
||||
if err != nil {
|
||||
return f.HandleError(err)
|
||||
}
|
||||
|
||||
rawSigs, err := f.cfg.Signer.SignOutputRaw(
|
||||
f.ctx, msgTx, []*lndclient.SignDescriptor{signDesc}, prevOut,
|
||||
)
|
||||
if err != nil {
|
||||
return f.HandleError(err)
|
||||
}
|
||||
|
||||
address, err := f.cfg.AddressManager.GetStaticAddress(f.ctx)
|
||||
if err != nil {
|
||||
return f.HandleError(err)
|
||||
}
|
||||
|
||||
sig := rawSigs[0]
|
||||
msgTx.TxIn[0].Witness, err = address.GenTimeoutWitness(sig)
|
||||
if err != nil {
|
||||
return f.HandleError(err)
|
||||
}
|
||||
|
||||
err = f.cfg.WalletKit.PublishTransaction(
|
||||
f.ctx, msgTx, f.deposit.OutPoint.Hash.String()+"-close-sweep",
|
||||
)
|
||||
if err != nil {
|
||||
return f.HandleError(err)
|
||||
}
|
||||
|
||||
f.Debugf("published timeout sweep with txid: %v", msgTx.TxHash())
|
||||
|
||||
return OnExpiryPublished
|
||||
}
|
||||
|
||||
// WaitForExpirySweepAction waits for a sufficient number of confirmations
|
||||
// before a timeout sweep is considered successful.
|
||||
func (f *FSM) WaitForExpirySweepAction(_ fsm.EventContext) fsm.EventType {
|
||||
spendChan, errSpendChan, err := f.cfg.ChainNotifier.RegisterConfirmationsNtfn( //nolint:lll
|
||||
f.ctx, nil, f.deposit.TimeOutSweepPkScript, defaultConfTarget,
|
||||
int32(f.deposit.ConfirmationHeight),
|
||||
)
|
||||
if err != nil {
|
||||
return f.HandleError(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case err := <-errSpendChan:
|
||||
log.Debugf("error while sweeping expired deposit: %v", err)
|
||||
return fsm.OnError
|
||||
|
||||
case <-spendChan:
|
||||
return OnExpirySwept
|
||||
|
||||
case <-f.ctx.Done():
|
||||
return fsm.OnError
|
||||
}
|
||||
}
|
||||
|
||||
// SweptExpiredDepositAction is the final action of the FSM. It signals to the
|
||||
// manager that the deposit has been swept and the FSM can be removed. It also
|
||||
// ends the state machine main loop by cancelling its context.
|
||||
func (f *FSM) SweptExpiredDepositAction(_ fsm.EventContext) fsm.EventType {
|
||||
select {
|
||||
case <-f.ctx.Done():
|
||||
return fsm.OnError
|
||||
|
||||
default:
|
||||
f.finalizedDepositChan <- f.deposit.OutPoint
|
||||
f.ctx.Done()
|
||||
}
|
||||
|
||||
return fsm.NoOp
|
||||
}
|
||||
|
||||
// WithdrawnDepositAction is the final action after a withdrawal. It signals to
|
||||
// the manager that the deposit has been swept and the FSM can be removed. It
|
||||
// also ends the state machine main loop by cancelling its context.
|
||||
func (f *FSM) WithdrawnDepositAction(_ fsm.EventContext) fsm.EventType {
|
||||
select {
|
||||
case <-f.ctx.Done():
|
||||
return fsm.OnError
|
||||
|
||||
default:
|
||||
f.finalizedDepositChan <- f.deposit.OutPoint
|
||||
f.ctx.Done()
|
||||
}
|
||||
|
||||
return fsm.NoOp
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package deposit
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/loop/fsm"
|
||||
)
|
||||
|
||||
// ID is a unique identifier for a deposit.
|
||||
type ID [IdLength]byte
|
||||
|
||||
// FromByteSlice creates a deposit id from a byte slice.
|
||||
func (r *ID) FromByteSlice(b []byte) error {
|
||||
if len(b) != IdLength {
|
||||
return fmt.Errorf("deposit id must be 32 bytes, got %d, %x",
|
||||
len(b), b)
|
||||
}
|
||||
|
||||
copy(r[:], b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deposit bundles an utxo at a static address together with manager-relevant
|
||||
// data.
|
||||
type Deposit struct {
|
||||
// ID is the unique identifier of the deposit.
|
||||
ID ID
|
||||
|
||||
// State is the current state of the deposit.
|
||||
State fsm.StateType
|
||||
|
||||
// Outpoint of the deposit.
|
||||
wire.OutPoint
|
||||
|
||||
// Value is the amount of the deposit.
|
||||
Value btcutil.Amount
|
||||
|
||||
// ConfirmationHeight is the absolute height at which the deposit was
|
||||
// first confirmed.
|
||||
ConfirmationHeight int64
|
||||
|
||||
// TimeOutSweepPkScript is the pk script that is used to sweep the
|
||||
// deposit to after it is expired.
|
||||
TimeOutSweepPkScript []byte
|
||||
|
||||
// WithdrawalSweepAddress is the address that is used to
|
||||
// cooperatively sweep the deposit to before it is expired.
|
||||
WithdrawalSweepAddress string
|
||||
}
|
||||
|
||||
// IsPending returns true if the deposit is pending.
|
||||
func (d *Deposit) IsPending() bool {
|
||||
return !d.IsFinal()
|
||||
}
|
||||
|
||||
// IsFinal returns true if the deposit is final.
|
||||
func (d *Deposit) IsFinal() bool {
|
||||
return d.State == Withdrawn || d.State == Expired ||
|
||||
d.State == Failed
|
||||
}
|
||||
|
||||
// GetRandomDepositID generates a random deposit ID.
|
||||
func GetRandomDepositID() (ID, error) {
|
||||
var id ID
|
||||
_, err := rand.Read(id[:])
|
||||
return id, err
|
||||
}
|
@ -0,0 +1,343 @@
|
||||
package deposit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/fsm"
|
||||
"github.com/lightninglabs/loop/staticaddr"
|
||||
"github.com/lightninglabs/loop/staticaddr/address"
|
||||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultObserverSize = 20
|
||||
)
|
||||
|
||||
var (
|
||||
ErrProtocolVersionNotSupported = errors.New("protocol version not " +
|
||||
"supported")
|
||||
)
|
||||
|
||||
// States.
|
||||
var (
|
||||
Deposited = fsm.StateType("Deposited")
|
||||
|
||||
Withdrawing = fsm.StateType("Withdrawing")
|
||||
|
||||
Withdrawn = fsm.StateType("Withdrawn")
|
||||
|
||||
PublishExpiredDeposit = fsm.StateType("PublishExpiredDeposit")
|
||||
|
||||
WaitForExpirySweep = fsm.StateType("WaitForExpirySweep")
|
||||
|
||||
Expired = fsm.StateType("Expired")
|
||||
|
||||
Failed = fsm.StateType("DepositFailed")
|
||||
)
|
||||
|
||||
// Events.
|
||||
var (
|
||||
OnStart = fsm.EventType("OnStart")
|
||||
OnWithdraw = fsm.EventType("OnWithdraw")
|
||||
OnWithdrawn = fsm.EventType("OnWithdrawn")
|
||||
OnExpiry = fsm.EventType("OnExpiry")
|
||||
OnExpiryPublished = fsm.EventType("OnExpiryPublished")
|
||||
OnExpirySwept = fsm.EventType("OnExpirySwept")
|
||||
OnRecover = fsm.EventType("OnRecover")
|
||||
)
|
||||
|
||||
// FSM is the state machine that handles the instant out.
|
||||
type FSM struct {
|
||||
*fsm.StateMachine
|
||||
|
||||
cfg *ManagerConfig
|
||||
|
||||
deposit *Deposit
|
||||
|
||||
params *address.Parameters
|
||||
|
||||
address *script.StaticAddress
|
||||
|
||||
ctx context.Context
|
||||
|
||||
blockNtfnChan chan uint32
|
||||
|
||||
finalizedDepositChan chan wire.OutPoint
|
||||
}
|
||||
|
||||
// NewFSM creates a new state machine that can action on all static address
|
||||
// feature requests.
|
||||
func NewFSM(ctx context.Context, deposit *Deposit, cfg *ManagerConfig,
|
||||
finalizedDepositChan chan wire.OutPoint,
|
||||
recoverStateMachine bool) (*FSM, error) {
|
||||
|
||||
params, err := cfg.AddressManager.GetStaticAddressParameters(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get static address "+
|
||||
"parameters: %v", err)
|
||||
}
|
||||
|
||||
address, err := cfg.AddressManager.GetStaticAddress(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get static address: %v", err)
|
||||
}
|
||||
|
||||
depoFsm := &FSM{
|
||||
cfg: cfg,
|
||||
deposit: deposit,
|
||||
params: params,
|
||||
address: address,
|
||||
ctx: ctx,
|
||||
blockNtfnChan: make(chan uint32),
|
||||
finalizedDepositChan: finalizedDepositChan,
|
||||
}
|
||||
|
||||
depositStates := depoFsm.DepositStatesV0()
|
||||
switch params.ProtocolVersion {
|
||||
case staticaddr.ProtocolVersion_V0:
|
||||
|
||||
default:
|
||||
return nil, ErrProtocolVersionNotSupported
|
||||
}
|
||||
|
||||
if recoverStateMachine {
|
||||
depoFsm.StateMachine = fsm.NewStateMachineWithState(
|
||||
depositStates, deposit.State,
|
||||
DefaultObserverSize,
|
||||
)
|
||||
} else {
|
||||
depoFsm.StateMachine = fsm.NewStateMachine(
|
||||
depositStates, DefaultObserverSize,
|
||||
)
|
||||
}
|
||||
|
||||
depoFsm.ActionEntryFunc = depoFsm.updateDeposit
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case currentHeight := <-depoFsm.blockNtfnChan:
|
||||
err := depoFsm.handleBlockNotification(
|
||||
currentHeight,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("error handling block "+
|
||||
"notification: %v", err)
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return depoFsm, nil
|
||||
}
|
||||
|
||||
func (f *FSM) handleBlockNotification(currentHeight uint32) error {
|
||||
params, err := f.cfg.AddressManager.GetStaticAddressParameters(f.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isExpired := func() bool {
|
||||
return currentHeight >= uint32(f.deposit.ConfirmationHeight)+
|
||||
params.Expiry
|
||||
}
|
||||
|
||||
if isExpired() && f.deposit.State != WaitForExpirySweep &&
|
||||
!f.deposit.IsFinal() {
|
||||
|
||||
go func() {
|
||||
err := f.SendEvent(OnExpiry, nil)
|
||||
if err != nil {
|
||||
log.Debugf("error sending OnExpiry event: %v",
|
||||
err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DepositStatesV0 returns the states a deposit can be in.
|
||||
func (f *FSM) DepositStatesV0() fsm.States {
|
||||
return fsm.States{
|
||||
fsm.EmptyState: fsm.State{
|
||||
Transitions: fsm.Transitions{
|
||||
OnStart: Deposited,
|
||||
},
|
||||
Action: fsm.NoOpAction,
|
||||
},
|
||||
Deposited: fsm.State{
|
||||
Transitions: fsm.Transitions{
|
||||
OnWithdraw: Withdrawing,
|
||||
OnExpiry: PublishExpiredDeposit,
|
||||
OnRecover: Deposited,
|
||||
},
|
||||
Action: fsm.NoOpAction,
|
||||
},
|
||||
Withdrawing: fsm.State{
|
||||
Transitions: fsm.Transitions{
|
||||
OnWithdrawn: Withdrawn,
|
||||
// Upon recovery, we go back to the Deposited
|
||||
// state. The deposit by then has a withdrawal
|
||||
// address stamped to it which will cause it to
|
||||
// transition into the Withdrawing state again.
|
||||
OnRecover: Deposited,
|
||||
|
||||
// A precondition for the Withdrawing state is
|
||||
// that the withdrawal transaction has been
|
||||
// broadcast. If the deposit expires while the
|
||||
// withdrawal isn't confirmed, we can ignore the
|
||||
// expiry.
|
||||
OnExpiry: Withdrawing,
|
||||
|
||||
// If the withdrawal failed we go back to
|
||||
// Deposited, hoping that another withdrawal
|
||||
// attempt will be successful. Alternatively,
|
||||
// the client can wait for the timeout sweep.
|
||||
fsm.OnError: Deposited,
|
||||
},
|
||||
Action: fsm.NoOpAction,
|
||||
},
|
||||
PublishExpiredDeposit: fsm.State{
|
||||
Transitions: fsm.Transitions{
|
||||
OnRecover: PublishExpiredDeposit,
|
||||
OnExpiryPublished: WaitForExpirySweep,
|
||||
// If the timeout sweep failed we go back to
|
||||
// Deposited, hoping that another timeout sweep
|
||||
// attempt will be successful. Alternatively,
|
||||
// the client can try to coop-spend the deposit.
|
||||
fsm.OnError: Deposited,
|
||||
},
|
||||
Action: f.PublishDepositExpirySweepAction,
|
||||
},
|
||||
WaitForExpirySweep: fsm.State{
|
||||
Transitions: fsm.Transitions{
|
||||
OnExpirySwept: Expired,
|
||||
OnRecover: PublishExpiredDeposit,
|
||||
// If the timeout sweep failed we go back to
|
||||
// Deposited, hoping that another timeout sweep
|
||||
// attempt will be successful. Alternatively,
|
||||
// the client can try to coop-spend the deposit.
|
||||
fsm.OnError: Deposited,
|
||||
},
|
||||
Action: f.WaitForExpirySweepAction,
|
||||
},
|
||||
Expired: fsm.State{
|
||||
Transitions: fsm.Transitions{
|
||||
OnExpiry: Expired,
|
||||
},
|
||||
Action: f.SweptExpiredDepositAction,
|
||||
},
|
||||
Withdrawn: fsm.State{
|
||||
Action: f.WithdrawnDepositAction,
|
||||
},
|
||||
Failed: fsm.State{
|
||||
Action: fsm.NoOpAction,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DepositEntryFunction is called after every action and updates the deposit in
|
||||
// the db.
|
||||
func (f *FSM) updateDeposit(notification fsm.Notification) {
|
||||
if f.deposit == nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.Debugf("NextState: %v, PreviousState: %v, Event: %v",
|
||||
notification.NextState, notification.PreviousState,
|
||||
notification.Event,
|
||||
)
|
||||
|
||||
f.deposit.State = notification.NextState
|
||||
|
||||
// Don't update the deposit if we are in an initial state or if we
|
||||
// are transitioning from an initial state to a failed state.
|
||||
state := f.deposit.State
|
||||
prevState := notification.PreviousState
|
||||
if state == fsm.EmptyState ||
|
||||
state == Deposited && !isRecoverable(prevState) ||
|
||||
(prevState == Deposited && state == Failed) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err := f.cfg.Store.UpdateDeposit(f.ctx, f.deposit)
|
||||
if err != nil {
|
||||
f.Errorf("unable to update deposit: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func isRecoverable(state fsm.StateType) bool {
|
||||
if state == Withdrawing || state == PublishExpiredDeposit ||
|
||||
state == WaitForExpirySweep {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Infof logs an info message with the deposit outpoint.
|
||||
func (f *FSM) Infof(format string, args ...interface{}) {
|
||||
log.Infof(
|
||||
"Deposit %v: "+format,
|
||||
append(
|
||||
[]interface{}{f.deposit.OutPoint},
|
||||
args...,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Debugf logs a debug message with the deposit outpoint.
|
||||
func (f *FSM) Debugf(format string, args ...interface{}) {
|
||||
log.Debugf(
|
||||
"Deposit %v: "+format,
|
||||
append(
|
||||
[]interface{}{f.deposit.OutPoint},
|
||||
args...,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Errorf logs an error message with the deposit outpoint.
|
||||
func (f *FSM) Errorf(format string, args ...interface{}) {
|
||||
log.Errorf(
|
||||
"Deposit %v: "+format,
|
||||
append(
|
||||
[]interface{}{f.deposit.OutPoint},
|
||||
args...,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
||||
// SignDescriptor returns the sign descriptor for the static address output.
|
||||
func (f *FSM) SignDescriptor() (*lndclient.SignDescriptor, error) {
|
||||
address, err := f.cfg.AddressManager.GetStaticAddress(f.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &lndclient.SignDescriptor{
|
||||
WitnessScript: address.TimeoutLeaf.Script,
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: f.params.ClientPubkey,
|
||||
},
|
||||
Output: wire.NewTxOut(
|
||||
int64(f.deposit.Value), f.params.PkScript,
|
||||
),
|
||||
HashType: txscript.SigHashDefault,
|
||||
InputIndex: 0,
|
||||
SignMethod: input.TaprootScriptSpendSignMethod,
|
||||
}, nil
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package deposit
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/lightninglabs/loop/staticaddr/address"
|
||||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
)
|
||||
|
||||
const (
|
||||
IdLength = 32
|
||||
)
|
||||
|
||||
// Store is the database interface that is used to store and retrieve
|
||||
// static address deposits.
|
||||
type Store interface {
|
||||
// CreateDeposit inserts a new deposit into the store.
|
||||
CreateDeposit(ctx context.Context, deposit *Deposit) error
|
||||
|
||||
// UpdateDeposit updates the deposit in the database.
|
||||
UpdateDeposit(ctx context.Context, deposit *Deposit) error
|
||||
|
||||
// GetDeposit retrieves a deposit with depositID from the database.
|
||||
GetDeposit(ctx context.Context, depositID ID) (*Deposit, error)
|
||||
|
||||
// AllDeposits retrieves all deposits from the store.
|
||||
AllDeposits(ctx context.Context) ([]*Deposit, error)
|
||||
}
|
||||
|
||||
// AddressManager handles fetching of address parameters.
|
||||
type AddressManager interface {
|
||||
// GetStaticAddressParameters returns the static address parameters.
|
||||
GetStaticAddressParameters(ctx context.Context) (*address.Parameters,
|
||||
error)
|
||||
|
||||
// GetStaticAddress returns the deposit address for the given
|
||||
// client and server public keys.
|
||||
GetStaticAddress(ctx context.Context) (*script.StaticAddress, error)
|
||||
|
||||
// ListUnspent returns a list of utxos at the static address.
|
||||
ListUnspent(ctx context.Context, minConfs,
|
||||
maxConfs int32) ([]*lnwallet.Utxo, error)
|
||||
}
|
@ -0,0 +1,503 @@
|
||||
package deposit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightninglabs/loop/fsm"
|
||||
staticaddressrpc "github.com/lightninglabs/loop/swapserverrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
)
|
||||
|
||||
const (
|
||||
// PollInterval is the interval in which we poll for new deposits to our
|
||||
// static address.
|
||||
PollInterval = 10 * time.Second
|
||||
|
||||
// MinConfs is the minimum number of confirmations we require for a
|
||||
// deposit to be considered available for loop-ins, coop-spends and
|
||||
// timeouts.
|
||||
MinConfs = 3
|
||||
|
||||
// MaxConfs is unset since we don't require a max number of
|
||||
// confirmations for deposits.
|
||||
MaxConfs = 0
|
||||
)
|
||||
|
||||
// ManagerConfig holds the configuration for the address manager.
|
||||
type ManagerConfig struct {
|
||||
// AddressClient is the client that communicates with the loop server
|
||||
// to manage static addresses.
|
||||
AddressClient staticaddressrpc.StaticAddressServerClient
|
||||
|
||||
AddressManager AddressManager
|
||||
|
||||
// SwapClient provides loop rpc functionality.
|
||||
SwapClient *loop.Client
|
||||
|
||||
// Store is the database store that is used to store static address
|
||||
// related records.
|
||||
Store Store
|
||||
|
||||
// WalletKit is the wallet client that is used to derive new keys from
|
||||
// lnd's wallet.
|
||||
WalletKit lndclient.WalletKitClient
|
||||
|
||||
// ChainParams is the chain configuration(mainnet, testnet...) this
|
||||
// manager uses.
|
||||
ChainParams *chaincfg.Params
|
||||
|
||||
// ChainNotifier is the chain notifier that is used to listen for new
|
||||
// blocks.
|
||||
ChainNotifier lndclient.ChainNotifierClient
|
||||
|
||||
// Signer is the signer client that is used to sign transactions.
|
||||
Signer lndclient.SignerClient
|
||||
}
|
||||
|
||||
// Manager manages the address state machines.
|
||||
type Manager struct {
|
||||
cfg *ManagerConfig
|
||||
|
||||
runCtx context.Context
|
||||
|
||||
sync.Mutex
|
||||
|
||||
// initChan signals the daemon that the address manager has completed
|
||||
// its initialization.
|
||||
initChan chan struct{}
|
||||
|
||||
// activeDeposits contains all the active static address outputs.
|
||||
activeDeposits map[wire.OutPoint]*FSM
|
||||
|
||||
// initiationHeight stores the currently best known block height.
|
||||
initiationHeight uint32
|
||||
|
||||
// currentHeight stores the currently best known block height.
|
||||
currentHeight uint32
|
||||
|
||||
// deposits contains all the deposits that have ever been made to the
|
||||
// static address. This field is used to store and recover deposits. It
|
||||
// also serves as basis for reconciliation of newly detected deposits by
|
||||
// matching them against deposits in this map that were already seen.
|
||||
deposits map[wire.OutPoint]*Deposit
|
||||
|
||||
// finalizedDepositChan is a channel that receives deposits that have
|
||||
// been finalized. The manager will adjust its internal state and flush
|
||||
// finalized deposits from its memory.
|
||||
finalizedDepositChan chan wire.OutPoint
|
||||
}
|
||||
|
||||
// NewManager creates a new deposit manager.
|
||||
func NewManager(cfg *ManagerConfig) *Manager {
|
||||
return &Manager{
|
||||
cfg: cfg,
|
||||
initChan: make(chan struct{}),
|
||||
activeDeposits: make(map[wire.OutPoint]*FSM),
|
||||
deposits: make(map[wire.OutPoint]*Deposit),
|
||||
finalizedDepositChan: make(chan wire.OutPoint),
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs the address manager.
|
||||
func (m *Manager) Run(ctx context.Context, currentHeight uint32) error {
|
||||
m.runCtx = ctx
|
||||
|
||||
m.Lock()
|
||||
m.currentHeight, m.initiationHeight = currentHeight, currentHeight
|
||||
m.Unlock()
|
||||
|
||||
newBlockChan, newBlockErrChan, err := m.cfg.ChainNotifier.RegisterBlockEpochNtfn(m.runCtx) //nolint:lll
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Recover previous deposits and static address parameters from the DB.
|
||||
err = m.recover(m.runCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start the deposit notifier.
|
||||
m.pollDeposits(ctx)
|
||||
|
||||
// Communicate to the caller that the address manager has completed its
|
||||
// initialization.
|
||||
close(m.initChan)
|
||||
|
||||
for {
|
||||
select {
|
||||
case height := <-newBlockChan:
|
||||
m.Lock()
|
||||
m.currentHeight = uint32(height)
|
||||
m.Unlock()
|
||||
|
||||
// Inform all active deposits about a new block arrival.
|
||||
for _, fsm := range m.activeDeposits {
|
||||
select {
|
||||
case fsm.blockNtfnChan <- uint32(height):
|
||||
|
||||
case <-m.runCtx.Done():
|
||||
return m.runCtx.Err()
|
||||
}
|
||||
}
|
||||
case outpoint := <-m.finalizedDepositChan:
|
||||
// If deposits notify us about their finalization, we
|
||||
// update the manager's internal state and flush the
|
||||
// finalized deposit from memory.
|
||||
m.finalizeDeposit(outpoint)
|
||||
|
||||
case err := <-newBlockErrChan:
|
||||
return err
|
||||
|
||||
case <-m.runCtx.Done():
|
||||
return m.runCtx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recover recovers static address parameters, previous deposits and state
|
||||
// machines from the database and starts the deposit notifier.
|
||||
func (m *Manager) recover(ctx context.Context) error {
|
||||
log.Infof("Recovering static address parameters and deposits...")
|
||||
|
||||
// Recover deposits.
|
||||
deposits, err := m.cfg.Store.AllDeposits(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, d := range deposits {
|
||||
m.deposits[d.OutPoint] = deposits[i]
|
||||
|
||||
// If the current deposit is final it wasn't active when we
|
||||
// shut down the client last. So we don't need to start a fsm
|
||||
// for it.
|
||||
if d.IsFinal() {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("Recovering deposit %x", d.ID)
|
||||
|
||||
// Create a state machine for a given deposit.
|
||||
fsm, err := NewFSM(
|
||||
m.runCtx, d, m.cfg,
|
||||
m.finalizedDepositChan, true,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send the OnRecover event to the state machine.
|
||||
go func() {
|
||||
err = fsm.SendEvent(OnRecover, nil)
|
||||
if err != nil {
|
||||
log.Errorf("Error sending OnStart event: %v",
|
||||
err)
|
||||
}
|
||||
}()
|
||||
|
||||
m.activeDeposits[d.OutPoint] = fsm
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitInitComplete waits until the address manager has completed its setup.
|
||||
func (m *Manager) WaitInitComplete() {
|
||||
defer log.Debugf("Static address deposit manager initiation complete.")
|
||||
<-m.initChan
|
||||
}
|
||||
|
||||
// pollDeposits polls new deposits to our static address and notifies the
|
||||
// manager's event loop about them.
|
||||
func (m *Manager) pollDeposits(ctx context.Context) {
|
||||
log.Debugf("waiting for new static address deposits...")
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(PollInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
err := m.reconcileDeposits(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("unable to reconcile "+
|
||||
"deposits: %v", err)
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// reconcileDeposits fetches all spends to our static address from our lnd
|
||||
// wallet and matches it against the deposits in our memory that we've seen so
|
||||
// far. It picks the newly identified deposits and starts a state machine per
|
||||
// deposit to track its progress.
|
||||
func (m *Manager) reconcileDeposits(ctx context.Context) error {
|
||||
log.Tracef("Reconciling new deposits...")
|
||||
|
||||
utxos, err := m.cfg.AddressManager.ListUnspent(
|
||||
ctx, MinConfs, MaxConfs,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to list new deposits: %v", err)
|
||||
}
|
||||
|
||||
newDeposits := m.filterNewDeposits(utxos)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to filter new deposits: %v", err)
|
||||
}
|
||||
|
||||
if len(newDeposits) == 0 {
|
||||
log.Tracef("No new deposits...")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, utxo := range newDeposits {
|
||||
deposit, err := m.createNewDeposit(ctx, utxo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to retain new deposit: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
log.Debugf("Received deposit: %v", deposit)
|
||||
err = m.startDepositFsm(deposit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start new deposit FSM: %v",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createNewDeposit transforms the wallet utxo into a deposit struct and stores
|
||||
// it in our database and manager memory.
|
||||
func (m *Manager) createNewDeposit(ctx context.Context,
|
||||
utxo *lnwallet.Utxo) (*Deposit, error) {
|
||||
|
||||
blockHeight, err := m.getBlockHeight(ctx, utxo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the sweep pk script.
|
||||
addr, err := m.cfg.WalletKit.NextAddr(
|
||||
ctx, lnwallet.DefaultAccountName,
|
||||
walletrpc.AddressType_TAPROOT_PUBKEY, false,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timeoutSweepPkScript, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := GetRandomDepositID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deposit := &Deposit{
|
||||
ID: id,
|
||||
State: Deposited,
|
||||
OutPoint: utxo.OutPoint,
|
||||
Value: utxo.Value,
|
||||
ConfirmationHeight: int64(blockHeight),
|
||||
TimeOutSweepPkScript: timeoutSweepPkScript,
|
||||
}
|
||||
|
||||
err = m.cfg.Store.CreateDeposit(ctx, deposit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
m.deposits[deposit.OutPoint] = deposit
|
||||
m.Unlock()
|
||||
|
||||
return deposit, nil
|
||||
}
|
||||
|
||||
// getBlockHeight retrieves the block height of a given utxo.
|
||||
func (m *Manager) getBlockHeight(ctx context.Context,
|
||||
utxo *lnwallet.Utxo) (uint32, error) {
|
||||
|
||||
addressParams, err := m.cfg.AddressManager.GetStaticAddressParameters(
|
||||
ctx,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("couldn't get confirmation height for "+
|
||||
"deposit, %v", err)
|
||||
}
|
||||
|
||||
notifChan, errChan, err := m.cfg.ChainNotifier.RegisterConfirmationsNtfn( //nolint:lll
|
||||
ctx, &utxo.OutPoint.Hash, addressParams.PkScript, MinConfs,
|
||||
int32(m.initiationHeight),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
select {
|
||||
case tx := <-notifChan:
|
||||
return tx.BlockHeight, nil
|
||||
|
||||
case err := <-errChan:
|
||||
return 0, err
|
||||
|
||||
case <-ctx.Done():
|
||||
return 0, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// filterNewDeposits filters the given utxos for new deposits that we haven't
|
||||
// seen before.
|
||||
func (m *Manager) filterNewDeposits(utxos []*lnwallet.Utxo) []*lnwallet.Utxo {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
var newDeposits []*lnwallet.Utxo
|
||||
for _, utxo := range utxos {
|
||||
_, ok := m.deposits[utxo.OutPoint]
|
||||
if !ok {
|
||||
newDeposits = append(newDeposits, utxo)
|
||||
}
|
||||
}
|
||||
|
||||
return newDeposits
|
||||
}
|
||||
|
||||
// startDepositFsm creates a new state machine flow from the latest deposit to
|
||||
// our static address.
|
||||
func (m *Manager) startDepositFsm(deposit *Deposit) error {
|
||||
// Create a state machine for a given deposit.
|
||||
fsm, err := NewFSM(
|
||||
m.runCtx, deposit, m.cfg, m.finalizedDepositChan, false,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send the start event to the state machine.
|
||||
go func() {
|
||||
err = fsm.SendEvent(OnStart, nil)
|
||||
if err != nil {
|
||||
log.Errorf("Error sending OnStart event: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = fsm.DefaultObserver.WaitForState(m.runCtx, time.Minute, Deposited)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the FSM to the active FSMs map.
|
||||
m.Lock()
|
||||
m.activeDeposits[deposit.OutPoint] = fsm
|
||||
m.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) finalizeDeposit(outpoint wire.OutPoint) {
|
||||
m.Lock()
|
||||
delete(m.activeDeposits, outpoint)
|
||||
delete(m.deposits, outpoint)
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
// GetActiveDepositsInState returns all active deposits.
|
||||
func (m *Manager) GetActiveDepositsInState(stateFilter fsm.StateType) (
|
||||
[]*Deposit, error) {
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
var deposits []*Deposit
|
||||
for _, fsm := range m.activeDeposits {
|
||||
if fsm.deposit.State != stateFilter {
|
||||
continue
|
||||
}
|
||||
deposits = append(deposits, fsm.deposit)
|
||||
}
|
||||
|
||||
return deposits, nil
|
||||
}
|
||||
|
||||
// GetAllDeposits returns all active deposits.
|
||||
func (m *Manager) GetAllDeposits() ([]*Deposit, error) {
|
||||
return m.cfg.Store.AllDeposits(m.runCtx)
|
||||
}
|
||||
|
||||
// AllOutpointsActiveDeposits checks if all deposits referenced by the outpoints
|
||||
// are active and in the specified state.
|
||||
func (m *Manager) AllOutpointsActiveDeposits(outpoints []wire.OutPoint,
|
||||
stateFilter fsm.StateType) (
|
||||
|
||||
[]*Deposit, bool) {
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
var deposits []*Deposit
|
||||
for _, o := range outpoints {
|
||||
if _, ok := m.activeDeposits[o]; !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
deposit := m.deposits[o]
|
||||
if deposit.State != stateFilter {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
deposits = append(deposits, m.deposits[o])
|
||||
}
|
||||
|
||||
return deposits, true
|
||||
}
|
||||
|
||||
// TransitionDeposits allows a caller to transition a set of deposits to a new
|
||||
// state.
|
||||
func (m *Manager) TransitionDeposits(deposits []*Deposit, event fsm.EventType,
|
||||
expectedFinalState fsm.StateType) error {
|
||||
|
||||
for _, d := range deposits {
|
||||
m.Lock()
|
||||
sm, ok := m.activeDeposits[d.OutPoint]
|
||||
m.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("deposit not found")
|
||||
}
|
||||
|
||||
err := sm.SendEvent(event, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = sm.DefaultObserver.WaitForState(
|
||||
m.runCtx, time.Minute, expectedFinalState,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) UpdateDeposit(d *Deposit) error {
|
||||
return m.cfg.Store.UpdateDeposit(m.runCtx, d)
|
||||
}
|
@ -0,0 +1,216 @@
|
||||
package deposit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/loop/fsm"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/loopdb/sqlc"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
)
|
||||
|
||||
// SqlStore is the backing store for static addresses.
|
||||
type SqlStore struct {
|
||||
baseDB *loopdb.BaseDB
|
||||
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
// NewSqlStore constructs a new SQLStore from a BaseDB. The BaseDB is agnostic
|
||||
// to the underlying driver which can be postgres or sqlite.
|
||||
func NewSqlStore(db *loopdb.BaseDB) *SqlStore {
|
||||
return &SqlStore{
|
||||
baseDB: db,
|
||||
|
||||
clock: clock.NewDefaultClock(),
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDeposit creates a static address record in the database.
|
||||
func (s *SqlStore) CreateDeposit(ctx context.Context, deposit *Deposit) error {
|
||||
createArgs := sqlc.CreateDepositParams{
|
||||
DepositID: deposit.ID[:],
|
||||
TxHash: deposit.Hash[:],
|
||||
OutIndex: int32(deposit.Index),
|
||||
Amount: int64(deposit.Value),
|
||||
ConfirmationHeight: deposit.ConfirmationHeight,
|
||||
TimeoutSweepPkScript: deposit.TimeOutSweepPkScript,
|
||||
WithdrawalSweepAddress: sql.NullString{
|
||||
String: deposit.WithdrawalSweepAddress,
|
||||
},
|
||||
}
|
||||
|
||||
updateArgs := sqlc.InsertDepositUpdateParams{
|
||||
DepositID: deposit.ID[:],
|
||||
UpdateTimestamp: s.clock.Now().UTC(),
|
||||
UpdateState: string(deposit.State),
|
||||
}
|
||||
|
||||
return s.baseDB.ExecTx(ctx, &loopdb.SqliteTxOptions{},
|
||||
func(q *sqlc.Queries) error {
|
||||
err := q.CreateDeposit(ctx, createArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return q.InsertDepositUpdate(ctx, updateArgs)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateDeposit updates the deposit in the database.
|
||||
func (s *SqlStore) UpdateDeposit(ctx context.Context, deposit *Deposit) error {
|
||||
insertUpdateArgs := sqlc.InsertDepositUpdateParams{
|
||||
DepositID: deposit.ID[:],
|
||||
UpdateTimestamp: s.clock.Now().UTC(),
|
||||
UpdateState: string(deposit.State),
|
||||
}
|
||||
|
||||
var (
|
||||
txHash = deposit.Hash[:]
|
||||
outIndex = sql.NullInt32{
|
||||
Int32: int32(deposit.Index),
|
||||
Valid: true,
|
||||
}
|
||||
)
|
||||
|
||||
updateArgs := sqlc.UpdateDepositParams{
|
||||
DepositID: deposit.ID[:],
|
||||
TxHash: txHash,
|
||||
OutIndex: outIndex.Int32,
|
||||
ConfirmationHeight: marshalSqlNullInt64(
|
||||
deposit.ConfirmationHeight,
|
||||
).Int64,
|
||||
WithdrawalSweepAddress: sql.NullString{
|
||||
String: deposit.WithdrawalSweepAddress,
|
||||
Valid: deposit.WithdrawalSweepAddress != "",
|
||||
},
|
||||
}
|
||||
|
||||
return s.baseDB.ExecTx(ctx, &loopdb.SqliteTxOptions{},
|
||||
func(q *sqlc.Queries) error {
|
||||
err := q.UpdateDeposit(ctx, updateArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return q.InsertDepositUpdate(ctx, insertUpdateArgs)
|
||||
})
|
||||
}
|
||||
|
||||
// GetDeposit retrieves the deposit from the database.
|
||||
func (s *SqlStore) GetDeposit(ctx context.Context, id ID) (*Deposit, error) {
|
||||
var deposit *Deposit
|
||||
err := s.baseDB.ExecTx(ctx, loopdb.NewSqlReadOpts(),
|
||||
func(q *sqlc.Queries) error {
|
||||
row, err := q.GetDeposit(ctx, id[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
latestUpdate, err := q.GetLatestDepositUpdate(
|
||||
ctx, id[:],
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deposit, err = s.toDeposit(row, latestUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return deposit, nil
|
||||
}
|
||||
|
||||
// AllDeposits retrieves all known deposits to our static address.
|
||||
func (s *SqlStore) AllDeposits(ctx context.Context) ([]*Deposit, error) {
|
||||
var allDeposits []*Deposit
|
||||
|
||||
err := s.baseDB.ExecTx(ctx, loopdb.NewSqlReadOpts(),
|
||||
func(q *sqlc.Queries) error {
|
||||
var err error
|
||||
|
||||
deposits, err := q.AllDeposits(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, deposit := range deposits {
|
||||
latestUpdate, err := q.GetLatestDepositUpdate(
|
||||
ctx, deposit.DepositID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d, err := s.toDeposit(deposit, latestUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allDeposits = append(allDeposits, d)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return allDeposits, nil
|
||||
}
|
||||
|
||||
// toDeposit converts an sql deposit to a deposit.
|
||||
func (s *SqlStore) toDeposit(row sqlc.Deposit,
|
||||
lastUpdate sqlc.DepositUpdate) (*Deposit, error) {
|
||||
|
||||
id := ID{}
|
||||
err := id.FromByteSlice(row.DepositID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var txHash *chainhash.Hash
|
||||
if row.TxHash != nil {
|
||||
txHash, err = chainhash.NewHash(row.TxHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &Deposit{
|
||||
ID: id,
|
||||
State: fsm.StateType(lastUpdate.UpdateState),
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: *txHash,
|
||||
Index: uint32(row.OutIndex),
|
||||
},
|
||||
Value: btcutil.Amount(row.Amount),
|
||||
ConfirmationHeight: row.ConfirmationHeight,
|
||||
TimeOutSweepPkScript: row.TimeoutSweepPkScript,
|
||||
WithdrawalSweepAddress: row.WithdrawalSweepAddress.String,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close closes the database connection.
|
||||
func (s *SqlStore) Close() {
|
||||
s.baseDB.DB.Close()
|
||||
}
|
||||
|
||||
// marshalSqlNullInt64 converts an int64 to a sql.NullInt64.
|
||||
func marshalSqlNullInt64(i int64) sql.NullInt64 {
|
||||
return sql.NullInt64{
|
||||
Int64: i,
|
||||
Valid: i != 0,
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package staticaddr
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/lightninglabs/loop/looprpc"
|
||||
staticaddressrpc "github.com/lightninglabs/loop/swapserverrpc"
|
||||
)
|
||||
|
||||
// AddressServer holds all fields for the address rpc server.
|
||||
type AddressServer struct {
|
||||
addressClient staticaddressrpc.StaticAddressServerClient
|
||||
manager *Manager
|
||||
looprpc.UnimplementedStaticAddressClientServer
|
||||
}
|
||||
|
||||
// NewAddressServer creates a new static address server.
|
||||
func NewAddressServer(addressClient staticaddressrpc.StaticAddressServerClient,
|
||||
manager *Manager) *AddressServer {
|
||||
|
||||
return &AddressServer{
|
||||
addressClient: addressClient,
|
||||
manager: manager,
|
||||
}
|
||||
}
|
||||
|
||||
// NewAddress is the rpc endpoint for loop clients to request a new static
|
||||
// address.
|
||||
func (s *AddressServer) NewAddress(ctx context.Context,
|
||||
_ *looprpc.NewAddressRequest) (*looprpc.NewAddressResponse, error) {
|
||||
|
||||
address, err := s.manager.NewAddress(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("New static loop-in address: %s\n", address.String())
|
||||
|
||||
return &looprpc.NewAddressResponse{
|
||||
Address: address.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListUnspent returns a list of utxos behind the static address.
|
||||
func (s *AddressServer) ListUnspent(ctx context.Context,
|
||||
req *looprpc.ListUnspentRequest) (*looprpc.ListUnspentResponse, error) {
|
||||
|
||||
// List all unspent utxos the wallet sees, regardless of the number of
|
||||
// confirmations.
|
||||
staticAddress, utxos, err := s.manager.ListUnspentRaw(
|
||||
ctx, req.MinConfs, req.MaxConfs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prepare the list response.
|
||||
var respUtxos []*looprpc.Utxo
|
||||
for _, u := range utxos {
|
||||
utxo := &looprpc.Utxo{
|
||||
StaticAddress: staticAddress.String(),
|
||||
AmountSat: int64(u.Value),
|
||||
Confirmations: u.Confirmations,
|
||||
Outpoint: u.OutPoint.String(),
|
||||
}
|
||||
respUtxos = append(respUtxos, utxo)
|
||||
}
|
||||
|
||||
return &looprpc.ListUnspentResponse{Utxos: respUtxos}, nil
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package withdraw
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/loop/fsm"
|
||||
"github.com/lightninglabs/loop/staticaddr/address"
|
||||
"github.com/lightninglabs/loop/staticaddr/deposit"
|
||||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
)
|
||||
|
||||
const (
|
||||
IdLength = 32
|
||||
)
|
||||
|
||||
// AddressManager handles fetching of address parameters.
|
||||
type AddressManager interface {
|
||||
// GetStaticAddressParameters returns the static address parameters.
|
||||
GetStaticAddressParameters(ctx context.Context) (*address.Parameters,
|
||||
error)
|
||||
|
||||
// GetStaticAddress returns the deposit address for the given
|
||||
// client and server public keys.
|
||||
GetStaticAddress(ctx context.Context) (*script.StaticAddress, error)
|
||||
|
||||
// ListUnspent returns a list of utxos at the static address.
|
||||
ListUnspent(ctx context.Context, minConfs,
|
||||
maxConfs int32) ([]*lnwallet.Utxo, error)
|
||||
}
|
||||
|
||||
type DepositManager interface {
|
||||
GetActiveDepositsInState(stateFilter fsm.StateType) ([]*deposit.Deposit,
|
||||
error)
|
||||
|
||||
AllOutpointsActiveDeposits(outpoints []wire.OutPoint,
|
||||
stateFilter fsm.StateType) ([]*deposit.Deposit, bool)
|
||||
|
||||
TransitionDeposits(deposits []*deposit.Deposit, event fsm.EventType,
|
||||
expectedFinalState fsm.StateType) error
|
||||
|
||||
UpdateDeposit(d *deposit.Deposit) error
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package withdraw
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
)
|
||||
|
||||
// Subsystem defines the sub system name of this package.
|
||||
const Subsystem = "CLOS"
|
||||
|
||||
// log is a logger that is initialized with no output filters. This means the
|
||||
// package will not perform any logging by default until the caller requests it.
|
||||
var log btclog.Logger
|
||||
|
||||
// The default amount of logging is none.
|
||||
func init() {
|
||||
UseLogger(build.NewSubLogger(Subsystem, nil))
|
||||
}
|
||||
|
||||
// UseLogger uses a specified Logger to output package logging info. This should
|
||||
// be used in preference to SetLogWriter if the caller is also using btclog.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
@ -0,0 +1,649 @@
|
||||
package withdraw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/staticaddr/deposit"
|
||||
staticaddressrpc "github.com/lightninglabs/loop/swapserverrpc"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrWithdrawingInactiveDeposits is returned when the user tries to
|
||||
// withdraw inactive deposits.
|
||||
ErrWithdrawingInactiveDeposits = errors.New("deposits to be withdrawn " +
|
||||
"are unknown or inactive")
|
||||
|
||||
// MinConfs is the minimum number of confirmations we require for a
|
||||
// deposit to be considered withdrawn.
|
||||
MinConfs int32 = 3
|
||||
|
||||
// Is the default confirmation target for the fee estimation of the
|
||||
// withdrawal transaction.
|
||||
defaultConfTarget int32 = 3
|
||||
)
|
||||
|
||||
// ManagerConfig holds the configuration for the address manager.
|
||||
type ManagerConfig struct {
|
||||
WithdrawalServerClient staticaddressrpc.WithdrawalServerClient
|
||||
|
||||
AddressManager AddressManager
|
||||
|
||||
DepositManager DepositManager
|
||||
|
||||
// WalletKit is the wallet client that is used to derive new keys from
|
||||
// lnd's wallet.
|
||||
WalletKit lndclient.WalletKitClient
|
||||
|
||||
// ChainParams is the chain configuration(mainnet, testnet...) this
|
||||
// manager uses.
|
||||
ChainParams *chaincfg.Params
|
||||
|
||||
// ChainNotifier is the chain notifier that is used to listen for new
|
||||
// blocks.
|
||||
ChainNotifier lndclient.ChainNotifierClient
|
||||
|
||||
// Signer is the signer client that is used to sign transactions.
|
||||
Signer lndclient.SignerClient
|
||||
}
|
||||
|
||||
// Manager manages the address state machines.
|
||||
type Manager struct {
|
||||
cfg *ManagerConfig
|
||||
|
||||
runCtx context.Context
|
||||
|
||||
sync.Mutex
|
||||
|
||||
// initChan signals the daemon that the address manager has completed
|
||||
// its initialization.
|
||||
initChan chan struct{}
|
||||
|
||||
// initiationHeight stores the currently best known block height.
|
||||
initiationHeight uint32
|
||||
|
||||
// currentHeight stores the currently best known block height.
|
||||
currentHeight uint32
|
||||
}
|
||||
|
||||
// NewManager creates a new deposit withdrawal manager.
|
||||
func NewManager(cfg *ManagerConfig) *Manager {
|
||||
return &Manager{
|
||||
cfg: cfg,
|
||||
initChan: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs the deposit withdrawal manager.
|
||||
func (m *Manager) Run(ctx context.Context, currentHeight uint32) error {
|
||||
m.runCtx = ctx
|
||||
|
||||
m.Lock()
|
||||
m.currentHeight, m.initiationHeight = currentHeight, currentHeight
|
||||
m.Unlock()
|
||||
|
||||
newBlockChan, newBlockErrChan, err := m.cfg.ChainNotifier.RegisterBlockEpochNtfn(m.runCtx) //nolint:lll
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.recover()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Communicate to the caller that the address manager has completed its
|
||||
// initialization.
|
||||
close(m.initChan)
|
||||
|
||||
for {
|
||||
select {
|
||||
case height := <-newBlockChan:
|
||||
m.Lock()
|
||||
m.currentHeight = uint32(height)
|
||||
m.Unlock()
|
||||
|
||||
case err := <-newBlockErrChan:
|
||||
return err
|
||||
|
||||
case <-m.runCtx.Done():
|
||||
return m.runCtx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) recover() error {
|
||||
// To recover withdrawals we skim through all active deposits and check
|
||||
// if they have a withdrawal address set. For the ones that do we
|
||||
// cluster those with equal withdrawal addresses and kick-off
|
||||
// their withdrawal. Each cluster represents a separate withdrawal by
|
||||
// the user.
|
||||
activeDeposits, err := m.cfg.DepositManager.GetActiveDepositsInState(
|
||||
deposit.Deposited,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Group the deposits by their withdrawal address.
|
||||
depositsByWithdrawalAddress := make(map[string][]*deposit.Deposit)
|
||||
for _, d := range activeDeposits {
|
||||
sweepAddress := d.WithdrawalSweepAddress
|
||||
if sweepAddress == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
depositsByWithdrawalAddress[sweepAddress] = append(
|
||||
depositsByWithdrawalAddress[sweepAddress], d,
|
||||
)
|
||||
}
|
||||
|
||||
// We can now reinstate each cluster of deposits for a withdrawal.
|
||||
for address, deposits := range depositsByWithdrawalAddress {
|
||||
sweepAddress, err := btcutil.DecodeAddress(
|
||||
address, m.cfg.ChainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.cfg.DepositManager.TransitionDeposits(
|
||||
deposits, deposit.OnWithdraw, deposit.Withdrawing,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.publishWithdrawal(m.runCtx, deposits, sweepAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitInitComplete waits until the address manager has completed its setup.
|
||||
func (m *Manager) WaitInitComplete() {
|
||||
defer log.Debugf("Static address withdrawal manager initiation " +
|
||||
"complete.")
|
||||
|
||||
<-m.initChan
|
||||
}
|
||||
|
||||
// WithdrawDeposits starts a deposits withdrawal flow.
|
||||
func (m *Manager) WithdrawDeposits(ctx context.Context,
|
||||
outpoints []wire.OutPoint) error {
|
||||
|
||||
if len(outpoints) == 0 {
|
||||
return fmt.Errorf("no outpoints selected to withdraw")
|
||||
}
|
||||
|
||||
// Ensure that the deposits are in a state in which they can be
|
||||
// withdrawn.
|
||||
deposits, allActive := m.cfg.DepositManager.AllOutpointsActiveDeposits(
|
||||
outpoints, deposit.Deposited)
|
||||
|
||||
if !allActive {
|
||||
return ErrWithdrawingInactiveDeposits
|
||||
}
|
||||
|
||||
// Generate the withdrawal address from our local lnd wallet.
|
||||
withdrawalAddress, err := m.cfg.WalletKit.NextAddr(
|
||||
ctx, lnwallet.DefaultAccountName,
|
||||
walletrpc.AddressType_TAPROOT_PUBKEY, false,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Attach the withdrawal address to the deposit. After a client restart
|
||||
// we can use this address as an indicator to continue the withdrawal.
|
||||
// If there are multiple deposits with the same withdrawal address, we
|
||||
// bundle them together in the same withdrawal transaction.
|
||||
for _, d := range deposits {
|
||||
d.WithdrawalSweepAddress = withdrawalAddress.String()
|
||||
/*err := m.cfg.DepositManager.UpdateDeposit(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}*/
|
||||
}
|
||||
|
||||
// Transition the deposits to the withdrawing state. This updates each
|
||||
// deposits withdrawal address. If a transition fails, we'll return an
|
||||
// error and abort the withdrawal. An error in transition is likely due
|
||||
// to an error in the state machine. The already transitioned deposits
|
||||
// should be reset to the deposit state after a restart.
|
||||
err = m.cfg.DepositManager.TransitionDeposits(
|
||||
deposits, deposit.OnWithdraw, deposit.Withdrawing,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.publishWithdrawal(ctx, deposits, withdrawalAddress)
|
||||
}
|
||||
|
||||
func (m *Manager) publishWithdrawal(ctx context.Context,
|
||||
deposits []*deposit.Deposit, withdrawalAddress btcutil.Address) error {
|
||||
|
||||
// Create a musig2 session for each deposit.
|
||||
withdrawalSessions, clientNonces, err := m.createMusig2Sessions(
|
||||
ctx, deposits,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the fee rate for the withdrawal sweep.
|
||||
withdrawalSweepFeeRate, err := m.cfg.WalletKit.EstimateFeeRate(
|
||||
m.runCtx, defaultConfTarget,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outpoints := toOutpoints(deposits)
|
||||
resp, err := m.cfg.WithdrawalServerClient.WithdrawDeposits(
|
||||
m.runCtx,
|
||||
&staticaddressrpc.ServerWithdrawRequest{
|
||||
Outpoints: toServerOutpoints(outpoints),
|
||||
ClientNonces: clientNonces,
|
||||
ClientSweepAddr: withdrawalAddress.String(),
|
||||
MusigTxFeeRate: uint64(withdrawalSweepFeeRate),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addressParams, err := m.cfg.AddressManager.GetStaticAddressParameters(
|
||||
ctx,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't get confirmation height for "+
|
||||
"deposit, %v", err)
|
||||
}
|
||||
|
||||
prevOuts := m.toPrevOuts(deposits, addressParams.PkScript)
|
||||
totalValue := withdrawalValue(prevOuts)
|
||||
withdrawalTx, err := m.createWithdrawalTx(
|
||||
prevOuts, totalValue, withdrawalAddress, withdrawalSweepFeeRate,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
coopServerNonces, err := toNonces(resp.ServerNonces)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Next we'll get our sweep tx signatures.
|
||||
prevOutFetcher := txscript.NewMultiPrevOutFetcher(prevOuts)
|
||||
_, err = m.signMusig2Tx(
|
||||
m.runCtx, prevOutFetcher, outpoints, m.cfg.Signer, withdrawalTx,
|
||||
withdrawalSessions, coopServerNonces,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now we'll finalize the sweepless sweep transaction.
|
||||
finalizedWithdrawalTx, err := m.finalizeMusig2Transaction(
|
||||
m.runCtx, outpoints, m.cfg.Signer, withdrawalSessions,
|
||||
withdrawalTx, resp.Musig2SweepSigs,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txLabel := fmt.Sprintf("deposit-withdrawal-%v",
|
||||
finalizedWithdrawalTx.TxHash())
|
||||
|
||||
// Publish the sweepless sweep transaction.
|
||||
err = m.cfg.WalletKit.PublishTransaction(
|
||||
m.runCtx, finalizedWithdrawalTx, txLabel,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txHash := finalizedWithdrawalTx.TxHash()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
withdrawalPkScript, err := txscript.PayToAddrScript(withdrawalAddress)
|
||||
|
||||
confChan, errChan, err := m.cfg.ChainNotifier.RegisterConfirmationsNtfn(
|
||||
m.runCtx, &txHash, withdrawalPkScript, MinConfs,
|
||||
int32(m.initiationHeight),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-confChan:
|
||||
err = m.cfg.DepositManager.TransitionDeposits(
|
||||
deposits, deposit.OnWithdrawn,
|
||||
deposit.Withdrawn,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Error transitioning deposits: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
case err := <-errChan:
|
||||
log.Errorf("Error waiting for confirmation: %v", err)
|
||||
|
||||
case <-m.runCtx.Done():
|
||||
log.Errorf("Withdrawal tx confirmation wait canceled")
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toOutpoints(deposits []*deposit.Deposit) []wire.OutPoint {
|
||||
outpoints := make([]wire.OutPoint, len(deposits))
|
||||
for i, d := range deposits {
|
||||
outpoints[i] = wire.OutPoint{
|
||||
Hash: d.Hash,
|
||||
Index: d.Index,
|
||||
}
|
||||
}
|
||||
|
||||
return outpoints
|
||||
}
|
||||
|
||||
// signMusig2Tx adds the server nonces to the musig2 sessions and signs the
|
||||
// transaction.
|
||||
func (m *Manager) signMusig2Tx(ctx context.Context,
|
||||
prevOutFetcher *txscript.MultiPrevOutFetcher, outpoints []wire.OutPoint,
|
||||
signer lndclient.SignerClient, tx *wire.MsgTx,
|
||||
musig2sessions []*input.MuSig2SessionInfo,
|
||||
counterPartyNonces [][musig2.PubNonceSize]byte) ([][]byte, error) {
|
||||
|
||||
sigHashes := txscript.NewTxSigHashes(tx, prevOutFetcher)
|
||||
sigs := make([][]byte, len(outpoints))
|
||||
|
||||
for idx, outpoint := range outpoints {
|
||||
if !reflect.DeepEqual(tx.TxIn[idx].PreviousOutPoint,
|
||||
outpoint) {
|
||||
|
||||
return nil, fmt.Errorf("tx input does not match " +
|
||||
"deposits")
|
||||
}
|
||||
|
||||
taprootSigHash, err := txscript.CalcTaprootSignatureHash(
|
||||
sigHashes, txscript.SigHashDefault, tx, idx,
|
||||
prevOutFetcher,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var digest [32]byte
|
||||
copy(digest[:], taprootSigHash)
|
||||
|
||||
// Register the server's nonce before attempting to create our
|
||||
// partial signature.
|
||||
haveAllNonces, err := signer.MuSig2RegisterNonces(
|
||||
ctx, musig2sessions[idx].SessionID,
|
||||
[][musig2.PubNonceSize]byte{counterPartyNonces[idx]},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sanity check that we have all the nonces.
|
||||
if !haveAllNonces {
|
||||
return nil, fmt.Errorf("invalid MuSig2 session: " +
|
||||
"nonces missing")
|
||||
}
|
||||
|
||||
// Since our MuSig2 session has all nonces, we can now create
|
||||
// the local partial signature by signing the sig hash.
|
||||
sig, err := signer.MuSig2Sign(
|
||||
ctx, musig2sessions[idx].SessionID, digest, false,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sigs[idx] = sig
|
||||
}
|
||||
|
||||
return sigs, nil
|
||||
}
|
||||
|
||||
func withdrawalValue(prevOuts map[wire.OutPoint]*wire.TxOut) btcutil.Amount {
|
||||
var totalValue btcutil.Amount
|
||||
for _, prevOut := range prevOuts {
|
||||
totalValue += btcutil.Amount(prevOut.Value)
|
||||
}
|
||||
return totalValue
|
||||
}
|
||||
|
||||
// toNonces converts a byte slice to a 66 byte slice.
|
||||
func toNonces(nonces [][]byte) ([][66]byte, error) {
|
||||
res := make([][66]byte, 0, len(nonces))
|
||||
for _, n := range nonces {
|
||||
n := n
|
||||
nonce, err := byteSliceTo66ByteSlice(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = append(res, nonce)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// byteSliceTo66ByteSlice converts a byte slice to a 66 byte slice.
|
||||
func byteSliceTo66ByteSlice(b []byte) ([66]byte, error) {
|
||||
if len(b) != 66 {
|
||||
return [66]byte{}, fmt.Errorf("invalid byte slice length")
|
||||
}
|
||||
|
||||
var res [66]byte
|
||||
copy(res[:], b)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (m *Manager) createWithdrawalTx(prevOuts map[wire.OutPoint]*wire.TxOut,
|
||||
withdrawlAmount btcutil.Amount, clientSweepAddress btcutil.Address,
|
||||
feeRate chainfee.SatPerKWeight) (*wire.MsgTx, error) {
|
||||
|
||||
// First Create the tx.
|
||||
msgTx := wire.NewMsgTx(2)
|
||||
|
||||
// Add the deposit inputs to the transaction.
|
||||
for o, _ := range prevOuts {
|
||||
msgTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: o,
|
||||
})
|
||||
}
|
||||
|
||||
// Estimate the fee
|
||||
weight, err := withdrawalFee(len(prevOuts), clientSweepAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkscript, err := txscript.PayToAddrScript(clientSweepAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fee := feeRate.FeeForWeight(weight)
|
||||
|
||||
// Create the sweep output
|
||||
sweepOutput := &wire.TxOut{
|
||||
Value: int64(withdrawlAmount) - int64(fee),
|
||||
PkScript: pkscript,
|
||||
}
|
||||
|
||||
msgTx.AddTxOut(sweepOutput)
|
||||
|
||||
return msgTx, nil
|
||||
}
|
||||
|
||||
// withdrawalFee returns the weight for the withdrawal transaction.
|
||||
func withdrawalFee(numInputs int, sweepAddress btcutil.Address) (int64,
|
||||
error) {
|
||||
|
||||
var weightEstimator input.TxWeightEstimator
|
||||
for i := 0; i < numInputs; i++ {
|
||||
weightEstimator.AddTaprootKeySpendInput(
|
||||
txscript.SigHashDefault,
|
||||
)
|
||||
}
|
||||
|
||||
// Get the weight of the sweep output.
|
||||
switch sweepAddress.(type) {
|
||||
case *btcutil.AddressWitnessPubKeyHash:
|
||||
weightEstimator.AddP2WKHOutput()
|
||||
|
||||
case *btcutil.AddressTaproot:
|
||||
weightEstimator.AddP2TROutput()
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid sweep address type %T",
|
||||
sweepAddress)
|
||||
}
|
||||
|
||||
return int64(weightEstimator.Weight()), nil
|
||||
}
|
||||
|
||||
// finalizeMusig2Transaction creates the finalized transactions for either
|
||||
// the htlc or the cooperative close.
|
||||
func (m *Manager) finalizeMusig2Transaction(ctx context.Context,
|
||||
outpoints []wire.OutPoint, signer lndclient.SignerClient,
|
||||
musig2Sessions []*input.MuSig2SessionInfo,
|
||||
tx *wire.MsgTx, serverSigs [][]byte) (*wire.MsgTx, error) {
|
||||
|
||||
for idx := range outpoints {
|
||||
haveAllSigs, finalSig, err := signer.MuSig2CombineSig(
|
||||
ctx, musig2Sessions[idx].SessionID,
|
||||
[][]byte{serverSigs[idx]},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !haveAllSigs {
|
||||
return nil, fmt.Errorf("missing sigs")
|
||||
}
|
||||
|
||||
tx.TxIn[idx].Witness = wire.TxWitness{finalSig}
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func toServerOutpoints(outpoints []wire.OutPoint) []*staticaddressrpc.ServerOutPoint { //nolint:lll
|
||||
var result []*staticaddressrpc.ServerOutPoint
|
||||
for _, o := range outpoints {
|
||||
outP := o
|
||||
outpoint := &staticaddressrpc.ServerOutPoint{
|
||||
TxidBytes: outP.Hash[:],
|
||||
OutputIndex: outP.Index,
|
||||
}
|
||||
result = append(result, outpoint)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// createMusig2Sessions creates a musig2 session for a number of deposits.
|
||||
func (m *Manager) createMusig2Sessions(ctx context.Context,
|
||||
deposits []*deposit.Deposit) ([]*input.MuSig2SessionInfo, [][]byte,
|
||||
error) {
|
||||
|
||||
musig2Sessions := make([]*input.MuSig2SessionInfo, len(deposits))
|
||||
clientNonces := make([][]byte, len(deposits))
|
||||
|
||||
// Create the sessions and nonces from the deposits.
|
||||
for i := 0; i < len(deposits); i++ {
|
||||
session, err := m.createMusig2Session(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
musig2Sessions[i] = session
|
||||
clientNonces[i] = session.PublicNonce[:]
|
||||
}
|
||||
|
||||
return musig2Sessions, clientNonces, nil
|
||||
}
|
||||
|
||||
// Musig2CreateSession creates a musig2 session for the deposit.
|
||||
func (m *Manager) createMusig2Session(ctx context.Context) (
|
||||
*input.MuSig2SessionInfo, error) {
|
||||
|
||||
addressParams, err := m.cfg.AddressManager.GetStaticAddressParameters(
|
||||
ctx,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't get confirmation height for "+
|
||||
"deposit, %v", err)
|
||||
}
|
||||
|
||||
signers := [][]byte{
|
||||
addressParams.ClientPubkey.SerializeCompressed(),
|
||||
addressParams.ServerPubkey.SerializeCompressed(),
|
||||
}
|
||||
|
||||
address, err := m.cfg.AddressManager.GetStaticAddress(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't get confirmation height for "+
|
||||
"deposit, %v", err)
|
||||
}
|
||||
|
||||
expiryLeaf := address.TimeoutLeaf
|
||||
|
||||
rootHash := expiryLeaf.TapHash()
|
||||
|
||||
return m.cfg.Signer.MuSig2CreateSession(
|
||||
ctx, input.MuSig2Version100RC2, &addressParams.KeyLocator,
|
||||
signers, lndclient.MuSig2TaprootTweakOpt(rootHash[:], false),
|
||||
)
|
||||
}
|
||||
|
||||
func (m *Manager) toPrevOuts(deposits []*deposit.Deposit,
|
||||
pkScript []byte) map[wire.OutPoint]*wire.TxOut {
|
||||
|
||||
prevOuts := make(map[wire.OutPoint]*wire.TxOut, len(deposits))
|
||||
for _, d := range deposits {
|
||||
outpoint := wire.OutPoint{
|
||||
Hash: d.Hash,
|
||||
Index: d.Index,
|
||||
}
|
||||
txOut := &wire.TxOut{
|
||||
Value: int64(d.Value),
|
||||
PkScript: pkScript,
|
||||
}
|
||||
prevOuts[outpoint] = txOut
|
||||
}
|
||||
|
||||
return prevOuts
|
||||
}
|
@ -0,0 +1,363 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.30.0
|
||||
// protoc v3.6.1
|
||||
// source: withdraw.proto
|
||||
|
||||
// We can't change this to swapserverrpc, it would be a breaking change because
|
||||
// the package name is also contained in the HTTP URIs and old clients would
|
||||
// call the wrong endpoints. Luckily with the go_package option we can have
|
||||
// different golang and RPC package names to fix protobuf namespace conflicts.
|
||||
|
||||
package swapserverrpc
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type ServerWithdrawRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The deposit outpoints the client whishes to close.
|
||||
Outpoints []*ServerOutPoint `protobuf:"bytes,1,rep,name=outpoints,proto3" json:"outpoints,omitempty"`
|
||||
// The nonces that the client used to generate the coop close tx sigs.
|
||||
ClientNonces [][]byte `protobuf:"bytes,2,rep,name=client_nonces,json=clientNonces,proto3" json:"client_nonces,omitempty"`
|
||||
// The address that the client wants to sweep the static address deposits
|
||||
// to.
|
||||
ClientSweepAddr string `protobuf:"bytes,3,opt,name=client_sweep_addr,json=clientSweepAddr,proto3" json:"client_sweep_addr,omitempty"`
|
||||
// The fee rate in sat/kw that the client wants to use for the sweep.
|
||||
MusigTxFeeRate uint64 `protobuf:"varint,4,opt,name=musig_tx_fee_rate,json=musigTxFeeRate,proto3" json:"musig_tx_fee_rate,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ServerWithdrawRequest) Reset() {
|
||||
*x = ServerWithdrawRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_withdraw_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ServerWithdrawRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ServerWithdrawRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ServerWithdrawRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_withdraw_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ServerWithdrawRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ServerWithdrawRequest) Descriptor() ([]byte, []int) {
|
||||
return file_withdraw_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ServerWithdrawRequest) GetOutpoints() []*ServerOutPoint {
|
||||
if x != nil {
|
||||
return x.Outpoints
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ServerWithdrawRequest) GetClientNonces() [][]byte {
|
||||
if x != nil {
|
||||
return x.ClientNonces
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ServerWithdrawRequest) GetClientSweepAddr() string {
|
||||
if x != nil {
|
||||
return x.ClientSweepAddr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ServerWithdrawRequest) GetMusigTxFeeRate() uint64 {
|
||||
if x != nil {
|
||||
return x.MusigTxFeeRate
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ServerWithdrawResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The sweep sigs that the server generated for the htlc.
|
||||
Musig2SweepSigs [][]byte `protobuf:"bytes,1,rep,name=musig2_sweep_sigs,json=musig2SweepSigs,proto3" json:"musig2_sweep_sigs,omitempty"`
|
||||
// The nonces that the server used to generate the sweepless sweep sigs.
|
||||
ServerNonces [][]byte `protobuf:"bytes,2,rep,name=server_nonces,json=serverNonces,proto3" json:"server_nonces,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ServerWithdrawResponse) Reset() {
|
||||
*x = ServerWithdrawResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_withdraw_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ServerWithdrawResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ServerWithdrawResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ServerWithdrawResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_withdraw_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ServerWithdrawResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ServerWithdrawResponse) Descriptor() ([]byte, []int) {
|
||||
return file_withdraw_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ServerWithdrawResponse) GetMusig2SweepSigs() [][]byte {
|
||||
if x != nil {
|
||||
return x.Musig2SweepSigs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ServerWithdrawResponse) GetServerNonces() [][]byte {
|
||||
if x != nil {
|
||||
return x.ServerNonces
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ServerOutPoint struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
//
|
||||
//Raw bytes representing the transaction id.
|
||||
TxidBytes []byte `protobuf:"bytes,1,opt,name=txid_bytes,json=txidBytes,proto3" json:"txid_bytes,omitempty"`
|
||||
//
|
||||
//Reversed, hex-encoded string representing the transaction id.
|
||||
TxidStr string `protobuf:"bytes,2,opt,name=txid_str,json=txidStr,proto3" json:"txid_str,omitempty"`
|
||||
//
|
||||
//The index of the output on the transaction.
|
||||
OutputIndex uint32 `protobuf:"varint,3,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ServerOutPoint) Reset() {
|
||||
*x = ServerOutPoint{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_withdraw_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ServerOutPoint) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ServerOutPoint) ProtoMessage() {}
|
||||
|
||||
func (x *ServerOutPoint) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_withdraw_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ServerOutPoint.ProtoReflect.Descriptor instead.
|
||||
func (*ServerOutPoint) Descriptor() ([]byte, []int) {
|
||||
return file_withdraw_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *ServerOutPoint) GetTxidBytes() []byte {
|
||||
if x != nil {
|
||||
return x.TxidBytes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ServerOutPoint) GetTxidStr() string {
|
||||
if x != nil {
|
||||
return x.TxidStr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ServerOutPoint) GetOutputIndex() uint32 {
|
||||
if x != nil {
|
||||
return x.OutputIndex
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_withdraw_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_withdraw_proto_rawDesc = []byte{
|
||||
0x0a, 0x0e, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x12, 0x07, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x22, 0xca, 0x01, 0x0a, 0x15, 0x53, 0x65,
|
||||
0x72, 0x76, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x09, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73,
|
||||
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52,
|
||||
0x09, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c,
|
||||
0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
|
||||
0x0c, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12,
|
||||
0x2a, 0x0a, 0x11, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f,
|
||||
0x61, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65,
|
||||
0x6e, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x41, 0x64, 0x64, 0x72, 0x12, 0x29, 0x0a, 0x11, 0x6d,
|
||||
0x75, 0x73, 0x69, 0x67, 0x5f, 0x74, 0x78, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6d, 0x75, 0x73, 0x69, 0x67, 0x54, 0x78, 0x46,
|
||||
0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x22, 0x69, 0x0a, 0x16, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x75, 0x73, 0x69, 0x67, 0x32, 0x5f, 0x73, 0x77, 0x65, 0x65, 0x70,
|
||||
0x5f, 0x73, 0x69, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0f, 0x6d, 0x75, 0x73,
|
||||
0x69, 0x67, 0x32, 0x53, 0x77, 0x65, 0x65, 0x70, 0x53, 0x69, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d,
|
||||
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20,
|
||||
0x03, 0x28, 0x0c, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65,
|
||||
0x73, 0x22, 0x6d, 0x0a, 0x0e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x50, 0x6f,
|
||||
0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65,
|
||||
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x78, 0x69, 0x64, 0x42, 0x79, 0x74,
|
||||
0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x78, 0x69, 0x64, 0x53, 0x74, 0x72, 0x12, 0x21, 0x0a,
|
||||
0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78,
|
||||
0x32, 0x67, 0x0a, 0x10, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x53, 0x65,
|
||||
0x72, 0x76, 0x65, 0x72, 0x12, 0x53, 0x0a, 0x10, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77,
|
||||
0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61,
|
||||
0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61,
|
||||
0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74,
|
||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e,
|
||||
0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x73, 0x77, 0x61, 0x70, 0x73,
|
||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_withdraw_proto_rawDescOnce sync.Once
|
||||
file_withdraw_proto_rawDescData = file_withdraw_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_withdraw_proto_rawDescGZIP() []byte {
|
||||
file_withdraw_proto_rawDescOnce.Do(func() {
|
||||
file_withdraw_proto_rawDescData = protoimpl.X.CompressGZIP(file_withdraw_proto_rawDescData)
|
||||
})
|
||||
return file_withdraw_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_withdraw_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_withdraw_proto_goTypes = []interface{}{
|
||||
(*ServerWithdrawRequest)(nil), // 0: looprpc.ServerWithdrawRequest
|
||||
(*ServerWithdrawResponse)(nil), // 1: looprpc.ServerWithdrawResponse
|
||||
(*ServerOutPoint)(nil), // 2: looprpc.ServerOutPoint
|
||||
}
|
||||
var file_withdraw_proto_depIdxs = []int32{
|
||||
2, // 0: looprpc.ServerWithdrawRequest.outpoints:type_name -> looprpc.ServerOutPoint
|
||||
0, // 1: looprpc.WithdrawalServer.WithdrawDeposits:input_type -> looprpc.ServerWithdrawRequest
|
||||
1, // 2: looprpc.WithdrawalServer.WithdrawDeposits:output_type -> looprpc.ServerWithdrawResponse
|
||||
2, // [2:3] is the sub-list for method output_type
|
||||
1, // [1:2] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_withdraw_proto_init() }
|
||||
func file_withdraw_proto_init() {
|
||||
if File_withdraw_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_withdraw_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ServerWithdrawRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_withdraw_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ServerWithdrawResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_withdraw_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ServerOutPoint); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_withdraw_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_withdraw_proto_goTypes,
|
||||
DependencyIndexes: file_withdraw_proto_depIdxs,
|
||||
MessageInfos: file_withdraw_proto_msgTypes,
|
||||
}.Build()
|
||||
File_withdraw_proto = out.File
|
||||
file_withdraw_proto_rawDesc = nil
|
||||
file_withdraw_proto_goTypes = nil
|
||||
file_withdraw_proto_depIdxs = nil
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
syntax = "proto3";
|
||||
|
||||
// We can't change this to swapserverrpc, it would be a breaking change because
|
||||
// the package name is also contained in the HTTP URIs and old clients would
|
||||
// call the wrong endpoints. Luckily with the go_package option we can have
|
||||
// different golang and RPC package names to fix protobuf namespace conflicts.
|
||||
package looprpc;
|
||||
|
||||
option go_package = "github.com/lightninglabs/loop/swapserverrpc";
|
||||
|
||||
service WithdrawalServer {
|
||||
// WithdrawDeposits allows to cooperatively sweep deposits that haven't
|
||||
// timed out yet to the client's wallet. The server will generate the
|
||||
// partial sigs for the client's selected deposits.
|
||||
rpc WithdrawDeposits (ServerWithdrawRequest)
|
||||
returns (ServerWithdrawResponse);
|
||||
}
|
||||
|
||||
message ServerWithdrawRequest {
|
||||
// The deposit outpoints the client whishes to close.
|
||||
repeated ServerOutPoint outpoints = 1;
|
||||
|
||||
// The nonces that the client used to generate the coop close tx sigs.
|
||||
repeated bytes client_nonces = 2;
|
||||
|
||||
// The address that the client wants to sweep the static address deposits
|
||||
// to.
|
||||
string client_sweep_addr = 3;
|
||||
|
||||
// The fee rate in sat/kw that the client wants to use for the sweep.
|
||||
uint64 musig_tx_fee_rate = 4;
|
||||
}
|
||||
|
||||
message ServerWithdrawResponse {
|
||||
// The sweep sigs that the server generated for the htlc.
|
||||
repeated bytes musig2_sweep_sigs = 1;
|
||||
|
||||
// The nonces that the server used to generate the sweepless sweep sigs.
|
||||
repeated bytes server_nonces = 2;
|
||||
}
|
||||
|
||||
message ServerOutPoint {
|
||||
/*
|
||||
Raw bytes representing the transaction id.
|
||||
*/
|
||||
bytes txid_bytes = 1;
|
||||
|
||||
/*
|
||||
Reversed, hex-encoded string representing the transaction id.
|
||||
*/
|
||||
string txid_str = 2;
|
||||
|
||||
/*
|
||||
The index of the output on the transaction.
|
||||
*/
|
||||
uint32 output_index = 3;
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package swapserverrpc
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// WithdrawalServerClient is the client API for WithdrawalServer service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type WithdrawalServerClient interface {
|
||||
// WithdrawDeposits allows to cooperatively sweep deposits that haven't
|
||||
// timed out yet to the client's wallet. The server will generate the
|
||||
// partial sigs for the client's selected deposits.
|
||||
WithdrawDeposits(ctx context.Context, in *ServerWithdrawRequest, opts ...grpc.CallOption) (*ServerWithdrawResponse, error)
|
||||
}
|
||||
|
||||
type withdrawalServerClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewWithdrawalServerClient(cc grpc.ClientConnInterface) WithdrawalServerClient {
|
||||
return &withdrawalServerClient{cc}
|
||||
}
|
||||
|
||||
func (c *withdrawalServerClient) WithdrawDeposits(ctx context.Context, in *ServerWithdrawRequest, opts ...grpc.CallOption) (*ServerWithdrawResponse, error) {
|
||||
out := new(ServerWithdrawResponse)
|
||||
err := c.cc.Invoke(ctx, "/looprpc.WithdrawalServer/WithdrawDeposits", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// WithdrawalServerServer is the server API for WithdrawalServer service.
|
||||
// All implementations must embed UnimplementedWithdrawalServerServer
|
||||
// for forward compatibility
|
||||
type WithdrawalServerServer interface {
|
||||
// WithdrawDeposits allows to cooperatively sweep deposits that haven't
|
||||
// timed out yet to the client's wallet. The server will generate the
|
||||
// partial sigs for the client's selected deposits.
|
||||
WithdrawDeposits(context.Context, *ServerWithdrawRequest) (*ServerWithdrawResponse, error)
|
||||
mustEmbedUnimplementedWithdrawalServerServer()
|
||||
}
|
||||
|
||||
// UnimplementedWithdrawalServerServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedWithdrawalServerServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedWithdrawalServerServer) WithdrawDeposits(context.Context, *ServerWithdrawRequest) (*ServerWithdrawResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method WithdrawDeposits not implemented")
|
||||
}
|
||||
func (UnimplementedWithdrawalServerServer) mustEmbedUnimplementedWithdrawalServerServer() {}
|
||||
|
||||
// UnsafeWithdrawalServerServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to WithdrawalServerServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeWithdrawalServerServer interface {
|
||||
mustEmbedUnimplementedWithdrawalServerServer()
|
||||
}
|
||||
|
||||
func RegisterWithdrawalServerServer(s grpc.ServiceRegistrar, srv WithdrawalServerServer) {
|
||||
s.RegisterService(&WithdrawalServer_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _WithdrawalServer_WithdrawDeposits_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ServerWithdrawRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(WithdrawalServerServer).WithdrawDeposits(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/looprpc.WithdrawalServer/WithdrawDeposits",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WithdrawalServerServer).WithdrawDeposits(ctx, req.(*ServerWithdrawRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// WithdrawalServer_ServiceDesc is the grpc.ServiceDesc for WithdrawalServer service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var WithdrawalServer_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "looprpc.WithdrawalServer",
|
||||
HandlerType: (*WithdrawalServerServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "WithdrawDeposits",
|
||||
Handler: _WithdrawalServer_WithdrawDeposits_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "withdraw.proto",
|
||||
}
|
Loading…
Reference in New Issue