mirror of
https://github.com/lightninglabs/faraday.git
synced 2026-08-13 12:33:35 +02:00
resolutions: add resolution report for cooperatively closed channel
This commit is contained in:
parent
9f85678ac5
commit
9a58df81c9
5 changed files with 669 additions and 0 deletions
17
resolutions/docs.md
Normal file
17
resolutions/docs.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Channel Reports
|
||||
Channel reports provide details summaries of channels that we have closed on chain, including fees and on chain resolutions required to fully finalize a channel. Note that channels that are still pending close will not be considered for channel reports; you must wait until the channel is fully resolved on chain. Since these channels are opened, closed and resolved on chain, all units will be expressed in satoshis.
|
||||
|
||||
## Common Fields
|
||||
- Channel Point: The funding txid: output index of of the output which created the channel.
|
||||
- Channel Initiator: True if our node opened the channel.
|
||||
- Close Type: The type of channel close - cooperative, local force, remote force, breach or justice.
|
||||
- Open Fee: The fees we paid to open the channel in satoshis, note that this amount will be 0 if we did not open the channel.
|
||||
- Close Fee: The fees we paid to close the channel in satoshis, not that this amount will be 0 if we did not open the channel.
|
||||
|
||||
### Cooperative Close
|
||||
A cooperative close occurs when one party decides that they want to close the channel, and the other is online to cooperatively sign a close transaction. When this kind of close occurs, there are no on chain resolutions because the parties agree to wait for all htlcs to clear, and sign a close transaction which pays out each party without encumbering their funds behind a time lock.
|
||||
|
||||
Since this close type has no on chain resolutions, there are no fields in the report aside from the common fields listed above.
|
||||
|
||||
Known Omissions:
|
||||
- The current implementation does not support generation of reports for channels that were created with batched funding transactions.
|
||||
77
resolutions/fees.go
Normal file
77
resolutions/fees.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package resolutions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// getTxDetails is a function which looks up transactions by hash.
|
||||
type getTxDetails func(hash *chainhash.Hash) (*btcjson.TxRawResult, error)
|
||||
|
||||
// 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).
|
||||
var errBatchedTx = errors.New("cannot calculate fees for batched " +
|
||||
"transaction")
|
||||
|
||||
// totalFees returns the total fees for the transaction provided. Note that this
|
||||
// function assumes that we have a maximum of two outputs (one regular and one
|
||||
// change address, or two outputs from a cooperative close) and does not
|
||||
// calculate fees for batched transactions (where we cannot split fees between
|
||||
// outputs).
|
||||
func totalFees(details getTxDetails, txid *chainhash.Hash) (decimal.Decimal,
|
||||
error) {
|
||||
|
||||
var fees decimal.Decimal
|
||||
|
||||
tx, err := details(txid)
|
||||
if err != nil {
|
||||
return decimal.Zero, err
|
||||
}
|
||||
|
||||
// Do a quick sanity check that our transaction does not have more than
|
||||
// two outputs.
|
||||
// TODO(carla): identify change address and split fees between outputs.
|
||||
if len(tx.Vout) > 2 {
|
||||
return decimal.Zero, errBatchedTx
|
||||
}
|
||||
|
||||
// First, we minus total outputs from our fees.
|
||||
for _, out := range tx.Vout {
|
||||
amt, err := btcutil.NewAmount(out.Value)
|
||||
if err != nil {
|
||||
return decimal.Zero, err
|
||||
}
|
||||
|
||||
fees = fees.Sub(decimal.NewFromInt(int64(amt)))
|
||||
}
|
||||
|
||||
// Next, we lookup each of our inputs to figure out their values and
|
||||
// minus them from our fees
|
||||
for _, in := range tx.Vin {
|
||||
prevOutHash, err := chainhash.NewHashFromStr(in.Txid)
|
||||
if err != nil {
|
||||
return decimal.Zero, err
|
||||
}
|
||||
|
||||
tx, err := details(prevOutHash)
|
||||
if err != nil {
|
||||
return decimal.Zero, err
|
||||
}
|
||||
|
||||
prevOut := tx.Vout[in.Vout]
|
||||
amt, err := btcutil.NewAmount(prevOut.Value)
|
||||
if err != nil {
|
||||
return decimal.Zero, err
|
||||
}
|
||||
|
||||
fees = fees.Add(decimal.NewFromInt(int64(amt)))
|
||||
}
|
||||
|
||||
// Our fees are simply the difference between our input and output
|
||||
// total.
|
||||
return fees, nil
|
||||
}
|
||||
51
resolutions/fees_test.go
Normal file
51
resolutions/fees_test.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package resolutions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestTotalFees tests fee calculation for a transaction that we assume has a
|
||||
// single output, and perhaps a change address.
|
||||
func TestTotalFees(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
txid *chainhash.Hash
|
||||
fee decimal.Decimal
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "can calculate fees",
|
||||
txid: txid1,
|
||||
fee: tx1TotalFee,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "tx2",
|
||||
txid: txid2,
|
||||
fee: tx2TotalFee,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "too many outputs",
|
||||
txid: txid0,
|
||||
fee: decimal.Zero,
|
||||
err: errBatchedTx,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fee, err := totalFees(getDetails, test.txid)
|
||||
require.Equal(t, test.err, err)
|
||||
require.Equal(t, test.fee, fee)
|
||||
})
|
||||
}
|
||||
}
|
||||
205
resolutions/resolutions.go
Normal file
205
resolutions/resolutions.go
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
package resolutions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"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")
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
switch closedChannel.CloseType {
|
||||
case lndclient.CloseTypeCooperative:
|
||||
return coopCloseReport(cfg, &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,
|
||||
channel *lndclient.ClosedChannel) (*CloseReport, error) {
|
||||
|
||||
chanPoint, err := utils.GetOutPointFromString(channel.ChannelPoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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:
|
||||
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.
|
||||
report.OpenFee, err = totalFees(cfg.GetTxDetail, &chanPoint.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.
|
||||
report.CloseFee, err = totalFees(cfg.GetTxDetail, closeHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
319
resolutions/resolutions_test.go
Normal file
319
resolutions/resolutions_test.go
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
package resolutions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
hash0 = "286ddb794170fafb73450db66b911f65823567ca3f9b88adc1c67b769951d7c2"
|
||||
hash1 = "b3ee48d811b07dd4c6c089e49587ed45d313cf2333d3588468848c4d98e5940d"
|
||||
hash2 = "ffa0c0191f491ac5193c6626db13d78e44d8b841530058a9eeb89d8fcea26c0d"
|
||||
|
||||
// Create three transaction ids, which we will setup in the sequence
|
||||
// txid2 --spends from--> txid1 --spends from--> txid0.
|
||||
txid0, _ = chainhash.NewHashFromStr(hash0)
|
||||
txid1, _ = chainhash.NewHashFromStr(hash1)
|
||||
txid2, _ = chainhash.NewHashFromStr(hash2)
|
||||
|
||||
tx0VOutValue = 3.1
|
||||
tx0OtherOutputs = 1.1
|
||||
|
||||
// Create our first transaction, we do not need to set inputs here
|
||||
// because we just spend from this tx in tests. We give this transaction
|
||||
// three outputs so that it checks that we appropriately only use the
|
||||
// value of a single output in our calculation, and so that it can be
|
||||
// used to trigger error conditions (where we require >2 output).
|
||||
tx0 = &btcjson.TxRawResult{
|
||||
Hash: hash0,
|
||||
Vout: []btcjson.Vout{
|
||||
{
|
||||
Value: tx0VOutValue,
|
||||
},
|
||||
{
|
||||
Value: tx0OtherOutputs,
|
||||
},
|
||||
{
|
||||
Value: tx0OtherOutputs,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Set the amount that our next tx will have as an output. The fee for
|
||||
// our first tx is therefore the original tx0VOutValue less this amount.
|
||||
tx1VoutValue float64 = 3
|
||||
tx1TotalFeeSat, _ = btcutil.NewAmount(
|
||||
tx0VOutValue - tx1VoutValue,
|
||||
)
|
||||
tx1TotalFee = decimal.NewFromInt(int64(tx1TotalFeeSat))
|
||||
|
||||
// Create tx1 which spends all of the outputs from tx0, and will be
|
||||
// spent by tx2.
|
||||
tx1 = &btcjson.TxRawResult{
|
||||
Hash: hash1,
|
||||
Vin: []btcjson.Vin{
|
||||
{
|
||||
Txid: hash0,
|
||||
Vout: 0,
|
||||
},
|
||||
},
|
||||
Vout: []btcjson.Vout{
|
||||
{
|
||||
Value: tx1VoutValue,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Set the output amounts that our final tx will create.
|
||||
output1Value float64 = 2
|
||||
output2Value = 0.5
|
||||
|
||||
// Our fee for our second transaction is therefore the output of tx1
|
||||
// less our two outputs.
|
||||
tx2TotalFeeSat, _ = btcutil.NewAmount(
|
||||
tx1VoutValue - output1Value - output2Value,
|
||||
)
|
||||
tx2TotalFee = decimal.NewFromInt(int64(tx2TotalFeeSat))
|
||||
|
||||
// tx2 is a transaction that spends from only tx1 (value =3) and creates
|
||||
// two new outpoints, with a total value of 2.5
|
||||
tx2 = &btcjson.TxRawResult{
|
||||
Hash: hash2,
|
||||
Vin: []btcjson.Vin{
|
||||
{
|
||||
Txid: hash1,
|
||||
Vout: 0,
|
||||
},
|
||||
},
|
||||
Vout: []btcjson.Vout{
|
||||
{
|
||||
Value: output1Value,
|
||||
},
|
||||
{
|
||||
Value: output2Value,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Finally, we create channel point
|
||||
tx1ChanPoint = &wire.OutPoint{
|
||||
Hash: *txid1,
|
||||
Index: 0,
|
||||
}
|
||||
|
||||
tx2ChanPoint = &wire.OutPoint{
|
||||
Hash: *txid2,
|
||||
Index: 0,
|
||||
}
|
||||
)
|
||||
|
||||
// TestGetClosedReport tests creation of a a closed channel report.
|
||||
func TestGetClosedReport(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
chanPoint string
|
||||
closedChannels []lndclient.ClosedChannel
|
||||
error error
|
||||
}{
|
||||
{
|
||||
name: "channel found, wrong type",
|
||||
chanPoint: tx2ChanPoint.String(),
|
||||
closedChannels: []lndclient.ClosedChannel{
|
||||
{
|
||||
ChannelPoint: tx1ChanPoint.String(),
|
||||
},
|
||||
{
|
||||
ChannelPoint: tx2ChanPoint.String(),
|
||||
CloseType: lndclient.CloseTypeAbandoned,
|
||||
},
|
||||
},
|
||||
error: ErrCloseTypeNotSupported,
|
||||
},
|
||||
{
|
||||
name: "channel not found",
|
||||
chanPoint: tx1ChanPoint.String(),
|
||||
closedChannels: []lndclient.ClosedChannel{
|
||||
{ChannelPoint: tx2ChanPoint.String()},
|
||||
},
|
||||
error: ErrChannelNotClosed,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
closedChannels := func() ([]lndclient.ClosedChannel,
|
||||
error) {
|
||||
|
||||
return test.closedChannels, nil
|
||||
}
|
||||
|
||||
_, err := ChannelCloseReport(
|
||||
&Config{ClosedChannels: closedChannels},
|
||||
test.chanPoint,
|
||||
)
|
||||
require.Equal(t, test.error, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// walletTransactions is a helper function which provides a WalletTransactions
|
||||
// function that will return the txids provided.
|
||||
func walletTransactions(txids []*chainhash.Hash) func() (
|
||||
[]lndclient.Transaction, error) {
|
||||
|
||||
txns := make([]lndclient.Transaction, len(txids))
|
||||
for i, tx := range txids {
|
||||
txns[i] = lndclient.Transaction{
|
||||
TxHash: tx.String(),
|
||||
}
|
||||
}
|
||||
|
||||
return func() ([]lndclient.Transaction, error) {
|
||||
return txns, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TestCoopCloseReport tests creation of a cooperative close report.
|
||||
func TestCoopCloseReport(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
openInitiator lndclient.Initiator
|
||||
initiator bool
|
||||
chanPoint *wire.OutPoint
|
||||
openFee decimal.Decimal
|
||||
closeFee decimal.Decimal
|
||||
// walletTxns is a list of transactions that belong to our
|
||||
// wallet, our channel point should be in this list if it is
|
||||
// expected to be locally initiated.
|
||||
walletTxns []*chainhash.Hash
|
||||
error error
|
||||
}{
|
||||
{
|
||||
name: "remote opened",
|
||||
openInitiator: lndclient.InitiatorRemote,
|
||||
chanPoint: tx1ChanPoint,
|
||||
initiator: false,
|
||||
openFee: decimal.Zero,
|
||||
closeFee: decimal.Zero,
|
||||
error: nil,
|
||||
},
|
||||
{
|
||||
name: "local initiator",
|
||||
openInitiator: lndclient.InitiatorLocal,
|
||||
chanPoint: tx1ChanPoint,
|
||||
initiator: true,
|
||||
openFee: tx1TotalFee,
|
||||
closeFee: tx2TotalFee,
|
||||
error: nil,
|
||||
},
|
||||
{
|
||||
// Refer to tx0 (which has 3 outputs) as our open tx,
|
||||
// this indicates that our close tx was batched, which
|
||||
// we do not currently support.
|
||||
name: "local initiator, batched open tx",
|
||||
openInitiator: lndclient.InitiatorLocal,
|
||||
chanPoint: wire.NewOutPoint(txid0, 0),
|
||||
initiator: true,
|
||||
openFee: decimal.Zero,
|
||||
closeFee: decimal.Zero,
|
||||
error: errBatchedTx,
|
||||
},
|
||||
{
|
||||
name: "unknown initiator - lookup is remote",
|
||||
openInitiator: lndclient.InitiatorUnrecorded,
|
||||
chanPoint: tx1ChanPoint,
|
||||
initiator: false,
|
||||
openFee: decimal.Zero,
|
||||
closeFee: decimal.Zero,
|
||||
walletTxns: nil,
|
||||
error: nil,
|
||||
},
|
||||
{
|
||||
name: "unknown initiator - lookup is local",
|
||||
openInitiator: lndclient.InitiatorUnrecorded,
|
||||
chanPoint: tx1ChanPoint,
|
||||
initiator: true,
|
||||
openFee: tx1TotalFee,
|
||||
closeFee: tx2TotalFee,
|
||||
walletTxns: []*chainhash.Hash{txid1},
|
||||
error: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a coop-close with out outpoint and close
|
||||
// initiator of choice.
|
||||
chanClose := lndclient.ClosedChannel{
|
||||
ChannelPoint: test.chanPoint.String(),
|
||||
ClosingTxHash: hash2,
|
||||
CloseType: lndclient.CloseTypeCooperative,
|
||||
OpenInitiator: test.openInitiator,
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
WalletTransactions: walletTransactions(
|
||||
test.walletTxns,
|
||||
),
|
||||
GetTxDetail: getDetails,
|
||||
}
|
||||
|
||||
report, err := coopCloseReport(cfg, &chanClose)
|
||||
require.Equal(t, test.error, err)
|
||||
|
||||
// If we expect an error, we do not proceed to check our
|
||||
// report against our expected output.
|
||||
if err != nil {
|
||||
require.Nil(t, report)
|
||||
return
|
||||
}
|
||||
|
||||
expected := &CloseReport{
|
||||
ChannelPoint: &wire.OutPoint{
|
||||
Hash: *txid1,
|
||||
Index: 0,
|
||||
},
|
||||
ChannelInitiator: test.initiator,
|
||||
CloseType: lndclient.CloseTypeCooperative,
|
||||
CloseTxid: hash2,
|
||||
OpenFee: test.openFee,
|
||||
CloseFee: test.closeFee,
|
||||
}
|
||||
|
||||
require.Equal(t, expected, report)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// getDetails mocks lookup for a node that has knowledge of tx1 and tx2.
|
||||
func getDetails(txHash *chainhash.Hash) (*btcjson.TxRawResult, error) {
|
||||
switch *txHash {
|
||||
case *txid0:
|
||||
return tx0, nil
|
||||
|
||||
case *txid1:
|
||||
return tx1, nil
|
||||
|
||||
case *txid2:
|
||||
return tx2, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("transaction not found")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue