mirror of
https://github.com/lightninglabs/faraday.git
synced 2026-08-13 12:33:35 +02:00
232 lines
7.1 KiB
Go
232 lines
7.1 KiB
Go
package resolutions
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcd/btcjson"
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/lightninglabs/faraday/utils"
|
|
"github.com/lightninglabs/lndclient"
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
var (
|
|
// ErrChannelNotClosed is returned when we get a request for a close
|
|
// channel report for a channel that is not present in lnd's list of
|
|
// closed channels.
|
|
ErrChannelNotClosed = errors.New("channel not closed, cannot create " +
|
|
"report")
|
|
|
|
// ErrCloseTypeNotSupported is returned when we do not yet support
|
|
// creation of close reports for the channel type provided.
|
|
ErrCloseTypeNotSupported = errors.New("close reports for type not " +
|
|
"supported")
|
|
|
|
// errBatchedTx is returned when we are trying to get the fees for a
|
|
// transaction but it is not of the format we expect (max 2 outputs,
|
|
// one change one output).
|
|
errBatchedTx = errors.New("cannot calculate fees for batched " +
|
|
"transaction")
|
|
)
|
|
|
|
// Config provides all the external functions and parameters required to produce
|
|
// reports on closed channels.
|
|
type Config struct {
|
|
// ClosedChannels returns a list of our currently closed channels.
|
|
ClosedChannels func() ([]lndclient.ClosedChannel, error)
|
|
|
|
// WalletTransactions returns a list of transactions that are relevant
|
|
// to our wallet.
|
|
WalletTransactions func() ([]lndclient.Transaction, error)
|
|
|
|
// GetTxDetail looks up an on chain transaction and returns the raw
|
|
// tx result which contains a detailed set of information about the
|
|
// transaction.
|
|
GetTxDetail func(txHash *chainhash.Hash) (*btcjson.TxRawResult, error)
|
|
|
|
// CalculateFees gets the total on chain fees for a transaction.
|
|
CalculateFees func(*chainhash.Hash) (btcutil.Amount, error)
|
|
}
|
|
|
|
// ChannelCloseReport returns a full report on a closed channel.
|
|
func ChannelCloseReport(cfg *Config, chanPoint string) (*CloseReport, error) {
|
|
// First, get our set of closed channels and make sure that the
|
|
closed, err := cfg.ClosedChannels()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
closedChannel lndclient.ClosedChannel
|
|
found bool
|
|
)
|
|
|
|
for _, channel := range closed {
|
|
if channel.ChannelPoint == chanPoint {
|
|
closedChannel = channel
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return nil, ErrChannelNotClosed
|
|
}
|
|
|
|
outpoint, err := utils.GetOutPointFromString(closedChannel.ChannelPoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Lookup our transaction and check that it has at most two outputs,
|
|
// allowing for a funding outpoint and change address. Our current
|
|
// fee calculations do not account for batched transactions.
|
|
tx, err := cfg.GetTxDetail(&outpoint.Hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO(carla): support batched transactions.
|
|
if len(tx.Vout) > 2 {
|
|
return nil, errBatchedTx
|
|
}
|
|
|
|
switch closedChannel.CloseType {
|
|
case lndclient.CloseTypeCooperative:
|
|
return coopCloseReport(cfg, outpoint, &closedChannel)
|
|
|
|
default:
|
|
return nil, ErrCloseTypeNotSupported
|
|
}
|
|
}
|
|
|
|
// CloseReport represents a closed channel.
|
|
type CloseReport struct {
|
|
// ChannelPoint is the outpoint of the funding transaction.
|
|
ChannelPoint *wire.OutPoint
|
|
|
|
// ChannelInitiator is true if we opened the channel.
|
|
ChannelInitiator bool
|
|
|
|
// CloseType reflects the type of close that occurred.
|
|
CloseType lndclient.CloseType
|
|
|
|
// CloseTxid is the transaction ID of the channel close.
|
|
CloseTxid string
|
|
|
|
// OpenFee is the amount of fees we paid to open the channel in
|
|
// satoshis. Note that this will be zero for the current protocol where
|
|
// the initiating party pays for the channel to be opened.
|
|
OpenFee decimal.Decimal
|
|
|
|
// CloseFee is the amount of fees we paid to close the channel in
|
|
// satoshis. Note that this will be zero for the current protocol where
|
|
// the initiating party pays for the channel to be closed.
|
|
CloseFee decimal.Decimal
|
|
}
|
|
|
|
// coopCloseReport creates a channel report for a cooperatively closed channel
|
|
// where we do not need to worry about on chain resolutions.
|
|
func coopCloseReport(cfg *Config, chanPoint *wire.OutPoint,
|
|
channel *lndclient.ClosedChannel) (*CloseReport, error) {
|
|
|
|
report := &CloseReport{
|
|
ChannelPoint: chanPoint,
|
|
ChannelInitiator: false,
|
|
CloseType: channel.CloseType,
|
|
CloseTxid: channel.ClosingTxHash,
|
|
OpenFee: decimal.Zero,
|
|
CloseFee: decimal.Zero,
|
|
}
|
|
|
|
// We pay fees based on whether we opened the channel or not, so we
|
|
// switch on our open initiator field (which may be unknown) to decide
|
|
// whether we need to get fee information.
|
|
switch channel.OpenInitiator {
|
|
// If the remote party opened the channel, we do not need to get any
|
|
// further information about the open and close fees, because we know
|
|
// the remote party paid them. We can just return our report as is.
|
|
case lndclient.InitiatorRemote:
|
|
return report, nil
|
|
|
|
// If we know we opened the channel, we fallthrough to get our open and
|
|
// close fees.
|
|
case lndclient.InitiatorLocal:
|
|
report.ChannelInitiator = true
|
|
|
|
// If we do not know whether we opened the channel or not, we lookup our
|
|
// funding outpoint with our wallet to determine whether is is ours or
|
|
// not. If it isn't ours, we just return the report as is. If it is, we
|
|
// fallthrough to get additional information about the close.
|
|
case lndclient.InitiatorUnrecorded:
|
|
var err error
|
|
report.ChannelInitiator, err = getCloseInitiatorFromWallet(
|
|
cfg, report.ChannelPoint.Hash.String(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If we did not open the channel, we can just return here
|
|
// because we do not need to record any fees (we did not pay
|
|
// them). If we did open the channel, we fallthrough to get
|
|
// our fee information.
|
|
if !report.ChannelInitiator {
|
|
return report, nil
|
|
}
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown inititor: %v",
|
|
channel.OpenInitiator)
|
|
}
|
|
|
|
// At this stage, we know that we opened the channel. We now lookup our
|
|
// open and close transactions to get the fees we paid for them.
|
|
var err error
|
|
openFee, err := cfg.CalculateFees(&chanPoint.Hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
report.OpenFee = decimal.NewFromInt(int64(openFee))
|
|
|
|
closeHash, err := chainhash.NewHashFromStr(channel.ClosingTxHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get the fees for our closing transaction. Since we will have to pay
|
|
// for the full close transaction (regardless of whether we have an
|
|
// output), we get our total fees for this transaction rather than for
|
|
// a specific outpoint.
|
|
closeFee, err := cfg.CalculateFees(closeHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
report.CloseFee = decimal.NewFromInt(int64(closeFee))
|
|
|
|
return report, nil
|
|
}
|
|
|
|
// getCloseInitiatorFromWallet figures out whether we initiated opening a
|
|
// channel by checking whether the opening transaction is in our set of wallet
|
|
// relevant transactions. If it is present, we contributed funds or published
|
|
// (in the case of psbt) the channel, so we were the opening party.
|
|
func getCloseInitiatorFromWallet(cfg *Config, openTx string) (bool,
|
|
error) {
|
|
|
|
txns, err := cfg.WalletTransactions()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for _, tx := range txns {
|
|
if tx.TxHash == openTx {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|