mirror of
https://github.com/muun/recovery.git
synced 2025-11-10 22:10:14 -05:00
Release v0.3.0
This commit is contained in:
137
vendor/github.com/lightningnetwork/lnd/lnwallet/chanfunding/assembler.go
generated
vendored
Normal file
137
vendor/github.com/lightningnetwork/lnd/lnwallet/chanfunding/assembler.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
package chanfunding
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
|
||||
// CoinSource is an interface that allows a caller to access a source of UTXOs
|
||||
// to use when attempting to fund a new channel.
|
||||
type CoinSource interface {
|
||||
// ListCoins returns all UTXOs from the source that have between
|
||||
// minConfs and maxConfs number of confirmations.
|
||||
ListCoins(minConfs, maxConfs int32) ([]Coin, error)
|
||||
|
||||
// CoinFromOutPoint attempts to locate details pertaining to a coin
|
||||
// based on its outpoint. If the coin isn't under the control of the
|
||||
// backing CoinSource, then an error should be returned.
|
||||
CoinFromOutPoint(wire.OutPoint) (*Coin, error)
|
||||
}
|
||||
|
||||
// CoinSelectionLocker is an interface that allows the caller to perform an
|
||||
// operation, which is synchronized with all coin selection attempts. This can
|
||||
// be used when an operation requires that all coin selection operations cease
|
||||
// forward progress. Think of this as an exclusive lock on coin selection
|
||||
// operations.
|
||||
type CoinSelectionLocker interface {
|
||||
// WithCoinSelectLock will execute the passed function closure in a
|
||||
// synchronized manner preventing any coin selection operations from
|
||||
// proceeding while the closure is executing. This can be seen as the
|
||||
// ability to execute a function closure under an exclusive coin
|
||||
// selection lock.
|
||||
WithCoinSelectLock(func() error) error
|
||||
}
|
||||
|
||||
// OutpointLocker allows a caller to lock/unlock an outpoint. When locked, the
|
||||
// outpoints shouldn't be used for any sort of channel funding of coin
|
||||
// selection. Locked outpoints are not expected to be persisted between
|
||||
// restarts.
|
||||
type OutpointLocker interface {
|
||||
// LockOutpoint locks a target outpoint, rendering it unusable for coin
|
||||
// selection.
|
||||
LockOutpoint(o wire.OutPoint)
|
||||
|
||||
// UnlockOutpoint unlocks a target outpoint, allowing it to be used for
|
||||
// coin selection once again.
|
||||
UnlockOutpoint(o wire.OutPoint)
|
||||
}
|
||||
|
||||
// Request is a new request for funding a channel. The items in the struct
|
||||
// governs how the final channel point will be provisioned by the target
|
||||
// Assembler.
|
||||
type Request struct {
|
||||
// LocalAmt is the amount of coins we're placing into the funding
|
||||
// output.
|
||||
LocalAmt btcutil.Amount
|
||||
|
||||
// RemoteAmt is the amount of coins the remote party is contributing to
|
||||
// the funding output.
|
||||
RemoteAmt btcutil.Amount
|
||||
|
||||
// MinConfs controls how many confirmations a coin need to be eligible
|
||||
// to be used as an input to the funding transaction. If this value is
|
||||
// set to zero, then zero conf outputs may be spent.
|
||||
MinConfs int32
|
||||
|
||||
// SubtractFees should be set if we intend to spend exactly LocalAmt
|
||||
// when opening the channel, subtracting the fees from the funding
|
||||
// output. This can be used for instance to use all our remaining funds
|
||||
// to open the channel, since it will take fees into
|
||||
// account.
|
||||
SubtractFees bool
|
||||
|
||||
// FeeRate is the fee rate in sat/kw that the funding transaction
|
||||
// should carry.
|
||||
FeeRate chainfee.SatPerKWeight
|
||||
|
||||
// ChangeAddr is a closure that will provide the Assembler with a
|
||||
// change address for the funding transaction if needed.
|
||||
ChangeAddr func() (btcutil.Address, error)
|
||||
}
|
||||
|
||||
// Intent is returned by an Assembler and represents the base functionality the
|
||||
// caller needs to proceed with channel funding on a higher level. If the
|
||||
// Cancel method is called, then all resources assembled to fund the channel
|
||||
// will be released back to the eligible pool.
|
||||
type Intent interface {
|
||||
// FundingOutput returns the witness script, and the output that
|
||||
// creates the funding output.
|
||||
FundingOutput() ([]byte, *wire.TxOut, error)
|
||||
|
||||
// ChanPoint returns the final outpoint that will create the funding
|
||||
// output described above.
|
||||
ChanPoint() (*wire.OutPoint, error)
|
||||
|
||||
// RemoteFundingAmt is the amount the remote party put into the
|
||||
// channel.
|
||||
RemoteFundingAmt() btcutil.Amount
|
||||
|
||||
// LocalFundingAmt is the amount we put into the channel. This may
|
||||
// differ from the local amount requested, as depending on coin
|
||||
// selection, we may bleed from of that LocalAmt into fees to minimize
|
||||
// change.
|
||||
LocalFundingAmt() btcutil.Amount
|
||||
|
||||
// Cancel allows the caller to cancel a funding Intent at any time.
|
||||
// This will return any resources such as coins back to the eligible
|
||||
// pool to be used in order channel fundings.
|
||||
Cancel()
|
||||
}
|
||||
|
||||
// Assembler is an abstract object that is capable of assembling everything
|
||||
// needed to create a new funding output. As an example, this assembler may be
|
||||
// our core backing wallet, an interactive PSBT based assembler, an assembler
|
||||
// than can aggregate multiple intents into a single funding transaction, or an
|
||||
// external protocol that creates a funding output out-of-band such as channel
|
||||
// factories.
|
||||
type Assembler interface {
|
||||
// ProvisionChannel returns a populated Intent that can be used to
|
||||
// further the channel funding workflow. Depending on the
|
||||
// implementation of Assembler, additional state machine (Intent)
|
||||
// actions may be required before the FundingOutput and ChanPoint are
|
||||
// made available to the caller.
|
||||
ProvisionChannel(*Request) (Intent, error)
|
||||
}
|
||||
|
||||
// FundingTxAssembler is a super-set of the regular Assembler interface that's
|
||||
// also able to provide a fully populated funding transaction via the intents
|
||||
// that it produces.
|
||||
type FundingTxAssembler interface {
|
||||
Assembler
|
||||
|
||||
// FundingTxAvailable is an empty method that an assembler can
|
||||
// implement to signal to callers that its able to provide the funding
|
||||
// transaction for the channel via the intent it returns.
|
||||
FundingTxAvailable()
|
||||
}
|
||||
207
vendor/github.com/lightningnetwork/lnd/lnwallet/chanfunding/canned_assembler.go
generated
vendored
Normal file
207
vendor/github.com/lightningnetwork/lnd/lnwallet/chanfunding/canned_assembler.go
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
package chanfunding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
||||
// ShimIntent is an intent created by the CannedAssembler which represents a
|
||||
// funding output to be created that was constructed outside the wallet. This
|
||||
// might be used when a hardware wallet, or a channel factory is the entity
|
||||
// crafting the funding transaction, and not lnd.
|
||||
type ShimIntent struct {
|
||||
// localFundingAmt is the final amount we put into the funding output.
|
||||
localFundingAmt btcutil.Amount
|
||||
|
||||
// remoteFundingAmt is the final amount the remote party put into the
|
||||
// funding output.
|
||||
remoteFundingAmt btcutil.Amount
|
||||
|
||||
// localKey is our multi-sig key.
|
||||
localKey *keychain.KeyDescriptor
|
||||
|
||||
// remoteKey is the remote party's multi-sig key.
|
||||
remoteKey *btcec.PublicKey
|
||||
|
||||
// chanPoint is the final channel point for the to be created channel.
|
||||
chanPoint *wire.OutPoint
|
||||
|
||||
// thawHeight, if non-zero is the height where this channel will become
|
||||
// a normal channel. Until this height, it's considered frozen, so it
|
||||
// can only be cooperatively closed by the responding party.
|
||||
thawHeight uint32
|
||||
}
|
||||
|
||||
// FundingOutput returns the witness script, and the output that creates the
|
||||
// funding output.
|
||||
//
|
||||
// NOTE: This method satisfies the chanfunding.Intent interface.
|
||||
func (s *ShimIntent) FundingOutput() ([]byte, *wire.TxOut, error) {
|
||||
if s.localKey == nil || s.remoteKey == nil {
|
||||
return nil, nil, fmt.Errorf("unable to create witness " +
|
||||
"script, no funding keys")
|
||||
}
|
||||
|
||||
totalAmt := s.localFundingAmt + s.remoteFundingAmt
|
||||
return input.GenFundingPkScript(
|
||||
s.localKey.PubKey.SerializeCompressed(),
|
||||
s.remoteKey.SerializeCompressed(),
|
||||
int64(totalAmt),
|
||||
)
|
||||
}
|
||||
|
||||
// Cancel allows the caller to cancel a funding Intent at any time. This will
|
||||
// return any resources such as coins back to the eligible pool to be used in
|
||||
// order channel fundings.
|
||||
//
|
||||
// NOTE: This method satisfies the chanfunding.Intent interface.
|
||||
func (s *ShimIntent) Cancel() {
|
||||
}
|
||||
|
||||
// RemoteFundingAmt is the amount the remote party put into the channel.
|
||||
//
|
||||
// NOTE: This method satisfies the chanfunding.Intent interface.
|
||||
func (s *ShimIntent) LocalFundingAmt() btcutil.Amount {
|
||||
return s.localFundingAmt
|
||||
}
|
||||
|
||||
// LocalFundingAmt is the amount we put into the channel. This may differ from
|
||||
// the local amount requested, as depending on coin selection, we may bleed
|
||||
// from of that LocalAmt into fees to minimize change.
|
||||
//
|
||||
// NOTE: This method satisfies the chanfunding.Intent interface.
|
||||
func (s *ShimIntent) RemoteFundingAmt() btcutil.Amount {
|
||||
return s.remoteFundingAmt
|
||||
}
|
||||
|
||||
// ChanPoint returns the final outpoint that will create the funding output
|
||||
// described above.
|
||||
//
|
||||
// NOTE: This method satisfies the chanfunding.Intent interface.
|
||||
func (s *ShimIntent) ChanPoint() (*wire.OutPoint, error) {
|
||||
if s.chanPoint == nil {
|
||||
return nil, fmt.Errorf("chan point unknown, funding output " +
|
||||
"not constructed")
|
||||
}
|
||||
|
||||
return s.chanPoint, nil
|
||||
}
|
||||
|
||||
// ThawHeight returns the height where this channel goes back to being a normal
|
||||
// channel.
|
||||
func (s *ShimIntent) ThawHeight() uint32 {
|
||||
return s.thawHeight
|
||||
}
|
||||
|
||||
// FundingKeys couples our multi-sig key along with the remote party's key.
|
||||
type FundingKeys struct {
|
||||
// LocalKey is our multi-sig key.
|
||||
LocalKey *keychain.KeyDescriptor
|
||||
|
||||
// RemoteKey is the multi-sig key of the remote party.
|
||||
RemoteKey *btcec.PublicKey
|
||||
}
|
||||
|
||||
// MultiSigKeys returns the committed multi-sig keys, but only if they've been
|
||||
// specified/provided.
|
||||
func (s *ShimIntent) MultiSigKeys() (*FundingKeys, error) {
|
||||
if s.localKey == nil || s.remoteKey == nil {
|
||||
return nil, fmt.Errorf("unknown funding keys")
|
||||
}
|
||||
|
||||
return &FundingKeys{
|
||||
LocalKey: s.localKey,
|
||||
RemoteKey: s.remoteKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// A compile-time check to ensure ShimIntent adheres to the Intent interface.
|
||||
var _ Intent = (*ShimIntent)(nil)
|
||||
|
||||
// CannedAssembler is a type of chanfunding.Assembler wherein the funding
|
||||
// transaction is constructed outside of lnd, and may already exist. This
|
||||
// Assembler serves as a shim which gives the funding flow the only thing it
|
||||
// actually needs to proceed: the channel point.
|
||||
type CannedAssembler struct {
|
||||
// fundingAmt is the total amount of coins in the funding output.
|
||||
fundingAmt btcutil.Amount
|
||||
|
||||
// localKey is our multi-sig key.
|
||||
localKey *keychain.KeyDescriptor
|
||||
|
||||
// remoteKey is the remote party's multi-sig key.
|
||||
remoteKey *btcec.PublicKey
|
||||
|
||||
// chanPoint is the final channel point for the to be created channel.
|
||||
chanPoint wire.OutPoint
|
||||
|
||||
// initiator indicates if we're the initiator or the channel or not.
|
||||
initiator bool
|
||||
|
||||
// thawHeight, if non-zero is the height where this channel will become
|
||||
// a normal channel. Until this height, it's considered frozen, so it
|
||||
// can only be cooperatively closed by the responding party.
|
||||
thawHeight uint32
|
||||
}
|
||||
|
||||
// NewCannedAssembler creates a new CannedAssembler from the material required
|
||||
// to construct a funding output and channel point.
|
||||
func NewCannedAssembler(thawHeight uint32, chanPoint wire.OutPoint,
|
||||
fundingAmt btcutil.Amount, localKey *keychain.KeyDescriptor,
|
||||
remoteKey *btcec.PublicKey, initiator bool) *CannedAssembler {
|
||||
|
||||
return &CannedAssembler{
|
||||
initiator: initiator,
|
||||
localKey: localKey,
|
||||
remoteKey: remoteKey,
|
||||
fundingAmt: fundingAmt,
|
||||
chanPoint: chanPoint,
|
||||
thawHeight: thawHeight,
|
||||
}
|
||||
}
|
||||
|
||||
// ProvisionChannel creates a new ShimIntent given the passed funding Request.
|
||||
// The returned intent is immediately able to provide the channel point and
|
||||
// funding output as they've already been created outside lnd.
|
||||
//
|
||||
// NOTE: This method satisfies the chanfunding.Assembler interface.
|
||||
func (c *CannedAssembler) ProvisionChannel(req *Request) (Intent, error) {
|
||||
// We'll exit out if this field is set as the funding transaction has
|
||||
// already been assembled, so we don't influence coin selection..
|
||||
if req.SubtractFees {
|
||||
return nil, fmt.Errorf("SubtractFees ignored, funding " +
|
||||
"transaction is frozen")
|
||||
}
|
||||
|
||||
intent := &ShimIntent{
|
||||
localKey: c.localKey,
|
||||
remoteKey: c.remoteKey,
|
||||
chanPoint: &c.chanPoint,
|
||||
thawHeight: c.thawHeight,
|
||||
}
|
||||
|
||||
if c.initiator {
|
||||
intent.localFundingAmt = c.fundingAmt
|
||||
} else {
|
||||
intent.remoteFundingAmt = c.fundingAmt
|
||||
}
|
||||
|
||||
// A simple sanity check to ensure the provisioned request matches the
|
||||
// re-made shim intent.
|
||||
if req.LocalAmt+req.RemoteAmt != c.fundingAmt {
|
||||
return nil, fmt.Errorf("intent doesn't match canned "+
|
||||
"assembler: local_amt=%v, remote_amt=%v, funding_amt=%v",
|
||||
req.LocalAmt, req.RemoteAmt, c.fundingAmt)
|
||||
}
|
||||
|
||||
return intent, nil
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure CannedAssembler meets the Assembler
|
||||
// interface.
|
||||
var _ Assembler = (*CannedAssembler)(nil)
|
||||
216
vendor/github.com/lightningnetwork/lnd/lnwallet/chanfunding/coin_select.go
generated
vendored
Normal file
216
vendor/github.com/lightningnetwork/lnd/lnwallet/chanfunding/coin_select.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
package chanfunding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
|
||||
// ErrInsufficientFunds is a type matching the error interface which is
|
||||
// returned when coin selection for a new funding transaction fails to due
|
||||
// having an insufficient amount of confirmed funds.
|
||||
type ErrInsufficientFunds struct {
|
||||
amountAvailable btcutil.Amount
|
||||
amountSelected btcutil.Amount
|
||||
}
|
||||
|
||||
// Error returns a human readable string describing the error.
|
||||
func (e *ErrInsufficientFunds) Error() string {
|
||||
return fmt.Sprintf("not enough witness outputs to create funding "+
|
||||
"transaction, need %v only have %v available",
|
||||
e.amountAvailable, e.amountSelected)
|
||||
}
|
||||
|
||||
// Coin represents a spendable UTXO which is available for channel funding.
|
||||
// This UTXO need not reside in our internal wallet as an example, and instead
|
||||
// may be derived from an existing watch-only wallet. It wraps both the output
|
||||
// present within the UTXO set, and also the outpoint that generates this coin.
|
||||
type Coin struct {
|
||||
wire.TxOut
|
||||
|
||||
wire.OutPoint
|
||||
}
|
||||
|
||||
// selectInputs selects a slice of inputs necessary to meet the specified
|
||||
// selection amount. If input selection is unable to succeed due to insufficient
|
||||
// funds, a non-nil error is returned. Additionally, the total amount of the
|
||||
// selected coins are returned in order for the caller to properly handle
|
||||
// change+fees.
|
||||
func selectInputs(amt btcutil.Amount, coins []Coin) (btcutil.Amount, []Coin, error) {
|
||||
satSelected := btcutil.Amount(0)
|
||||
for i, coin := range coins {
|
||||
satSelected += btcutil.Amount(coin.Value)
|
||||
if satSelected >= amt {
|
||||
return satSelected, coins[:i+1], nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, nil, &ErrInsufficientFunds{amt, satSelected}
|
||||
}
|
||||
|
||||
// CoinSelect attempts to select a sufficient amount of coins, including a
|
||||
// change output to fund amt satoshis, adhering to the specified fee rate. The
|
||||
// specified fee rate should be expressed in sat/kw for coin selection to
|
||||
// function properly.
|
||||
func CoinSelect(feeRate chainfee.SatPerKWeight, amt btcutil.Amount,
|
||||
coins []Coin) ([]Coin, btcutil.Amount, error) {
|
||||
|
||||
amtNeeded := amt
|
||||
for {
|
||||
// First perform an initial round of coin selection to estimate
|
||||
// the required fee.
|
||||
totalSat, selectedUtxos, err := selectInputs(amtNeeded, coins)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var weightEstimate input.TxWeightEstimator
|
||||
|
||||
for _, utxo := range selectedUtxos {
|
||||
switch {
|
||||
|
||||
case txscript.IsPayToWitnessPubKeyHash(utxo.PkScript):
|
||||
weightEstimate.AddP2WKHInput()
|
||||
|
||||
case txscript.IsPayToScriptHash(utxo.PkScript):
|
||||
weightEstimate.AddNestedP2WKHInput()
|
||||
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("unsupported address type: %x",
|
||||
utxo.PkScript)
|
||||
}
|
||||
}
|
||||
|
||||
// Channel funding multisig output is P2WSH.
|
||||
weightEstimate.AddP2WSHOutput()
|
||||
|
||||
// Assume that change output is a P2WKH output.
|
||||
//
|
||||
// TODO: Handle wallets that generate non-witness change
|
||||
// addresses.
|
||||
// TODO(halseth): make coinSelect not estimate change output
|
||||
// for dust change.
|
||||
weightEstimate.AddP2WKHOutput()
|
||||
|
||||
// The difference between the selected amount and the amount
|
||||
// requested will be used to pay fees, and generate a change
|
||||
// output with the remaining.
|
||||
overShootAmt := totalSat - amt
|
||||
|
||||
// Based on the estimated size and fee rate, if the excess
|
||||
// amount isn't enough to pay fees, then increase the requested
|
||||
// coin amount by the estimate required fee, performing another
|
||||
// round of coin selection.
|
||||
totalWeight := int64(weightEstimate.Weight())
|
||||
requiredFee := feeRate.FeeForWeight(totalWeight)
|
||||
if overShootAmt < requiredFee {
|
||||
amtNeeded = amt + requiredFee
|
||||
continue
|
||||
}
|
||||
|
||||
// If the fee is sufficient, then calculate the size of the
|
||||
// change output.
|
||||
changeAmt := overShootAmt - requiredFee
|
||||
|
||||
return selectedUtxos, changeAmt, nil
|
||||
}
|
||||
}
|
||||
|
||||
// CoinSelectSubtractFees attempts to select coins such that we'll spend up to
|
||||
// amt in total after fees, adhering to the specified fee rate. The selected
|
||||
// coins, the final output and change values are returned.
|
||||
func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt,
|
||||
dustLimit btcutil.Amount, coins []Coin) ([]Coin, btcutil.Amount,
|
||||
btcutil.Amount, error) {
|
||||
|
||||
// First perform an initial round of coin selection to estimate
|
||||
// the required fee.
|
||||
totalSat, selectedUtxos, err := selectInputs(amt, coins)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
var weightEstimate input.TxWeightEstimator
|
||||
for _, utxo := range selectedUtxos {
|
||||
switch {
|
||||
|
||||
case txscript.IsPayToWitnessPubKeyHash(utxo.PkScript):
|
||||
weightEstimate.AddP2WKHInput()
|
||||
|
||||
case txscript.IsPayToScriptHash(utxo.PkScript):
|
||||
weightEstimate.AddNestedP2WKHInput()
|
||||
|
||||
default:
|
||||
return nil, 0, 0, fmt.Errorf("unsupported address "+
|
||||
"type: %x", utxo.PkScript)
|
||||
}
|
||||
}
|
||||
|
||||
// Channel funding multisig output is P2WSH.
|
||||
weightEstimate.AddP2WSHOutput()
|
||||
|
||||
// At this point we've got two possibilities, either create a
|
||||
// change output, or not. We'll first try without creating a
|
||||
// change output.
|
||||
//
|
||||
// Estimate the fee required for a transaction without a change
|
||||
// output.
|
||||
totalWeight := int64(weightEstimate.Weight())
|
||||
requiredFee := feeRate.FeeForWeight(totalWeight)
|
||||
|
||||
// For a transaction without a change output, we'll let everything go
|
||||
// to our multi-sig output after subtracting fees.
|
||||
outputAmt := totalSat - requiredFee
|
||||
changeAmt := btcutil.Amount(0)
|
||||
|
||||
// If the the output is too small after subtracting the fee, the coin
|
||||
// selection cannot be performed with an amount this small.
|
||||
if outputAmt <= dustLimit {
|
||||
return nil, 0, 0, fmt.Errorf("output amount(%v) after "+
|
||||
"subtracting fees(%v) below dust limit(%v)", outputAmt,
|
||||
requiredFee, dustLimit)
|
||||
}
|
||||
|
||||
// We were able to create a transaction with no change from the
|
||||
// selected inputs. We'll remember the resulting values for
|
||||
// now, while we try to add a change output. Assume that change output
|
||||
// is a P2WKH output.
|
||||
weightEstimate.AddP2WKHOutput()
|
||||
|
||||
// Now that we have added the change output, redo the fee
|
||||
// estimate.
|
||||
totalWeight = int64(weightEstimate.Weight())
|
||||
requiredFee = feeRate.FeeForWeight(totalWeight)
|
||||
|
||||
// For a transaction with a change output, everything we don't spend
|
||||
// will go to change.
|
||||
newChange := totalSat - amt
|
||||
newOutput := amt - requiredFee
|
||||
|
||||
// If adding a change output leads to both outputs being above
|
||||
// the dust limit, we'll add the change output. Otherwise we'll
|
||||
// go with the no change tx we originally found.
|
||||
if newChange > dustLimit && newOutput > dustLimit {
|
||||
outputAmt = newOutput
|
||||
changeAmt = newChange
|
||||
}
|
||||
|
||||
// Sanity check the resulting output values to make sure we
|
||||
// don't burn a great part to fees.
|
||||
totalOut := outputAmt + changeAmt
|
||||
fee := totalSat - totalOut
|
||||
|
||||
// Fail if more than 20% goes to fees.
|
||||
// TODO(halseth): smarter fee limit. Make configurable or dynamic wrt
|
||||
// total funding size?
|
||||
if fee > totalOut/5 {
|
||||
return nil, 0, 0, fmt.Errorf("fee %v on total output"+
|
||||
"value %v", fee, totalOut)
|
||||
}
|
||||
|
||||
return selectedUtxos, outputAmt, changeAmt, nil
|
||||
}
|
||||
29
vendor/github.com/lightningnetwork/lnd/lnwallet/chanfunding/log.go
generated
vendored
Normal file
29
vendor/github.com/lightningnetwork/lnd/lnwallet/chanfunding/log.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package chanfunding
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
)
|
||||
|
||||
// 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("CHFD", nil))
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output. Logging output is disabled
|
||||
// by default until UseLogger is called.
|
||||
func DisableLog() {
|
||||
UseLogger(btclog.Disabled)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
524
vendor/github.com/lightningnetwork/lnd/lnwallet/chanfunding/psbt_assembler.go
generated
vendored
Normal file
524
vendor/github.com/lightningnetwork/lnd/lnwallet/chanfunding/psbt_assembler.go
generated
vendored
Normal file
@@ -0,0 +1,524 @@
|
||||
package chanfunding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcutil/psbt"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
||||
// PsbtState is a type for the state of the PSBT intent state machine.
|
||||
type PsbtState uint8
|
||||
|
||||
const (
|
||||
// PsbtShimRegistered denotes a channel funding process has started with
|
||||
// a PSBT shim attached. This is the default state for a PsbtIntent. We
|
||||
// don't use iota here because the values have to be in sync with the
|
||||
// RPC constants.
|
||||
PsbtShimRegistered PsbtState = 1
|
||||
|
||||
// PsbtOutputKnown denotes that the local and remote peer have
|
||||
// negotiated the multisig keys to be used as the channel funding output
|
||||
// and therefore the PSBT funding process can now start.
|
||||
PsbtOutputKnown PsbtState = 2
|
||||
|
||||
// PsbtVerified denotes that a potential PSBT has been presented to the
|
||||
// intent and passed all checks. The verified PSBT can be given to a/the
|
||||
// signer(s).
|
||||
PsbtVerified PsbtState = 3
|
||||
|
||||
// PsbtFinalized denotes that a fully signed PSBT has been given to the
|
||||
// intent that looks identical to the previously verified transaction
|
||||
// but has all witness data added and is therefore completely signed.
|
||||
PsbtFinalized PsbtState = 4
|
||||
|
||||
// PsbtFundingTxCompiled denotes that the PSBT processed by this intent
|
||||
// has been successfully converted into a protocol transaction. It is
|
||||
// not yet completely certain that the resulting transaction will be
|
||||
// published because the commitment transactions between the channel
|
||||
// peers first need to be counter signed. But the job of the intent is
|
||||
// hereby completed.
|
||||
PsbtFundingTxCompiled PsbtState = 5
|
||||
|
||||
// PsbtInitiatorCanceled denotes that the user has canceled the intent.
|
||||
PsbtInitiatorCanceled PsbtState = 6
|
||||
|
||||
// PsbtResponderCanceled denotes that the remote peer has canceled the
|
||||
// funding, likely due to a timeout.
|
||||
PsbtResponderCanceled PsbtState = 7
|
||||
)
|
||||
|
||||
// String returns a string representation of the PsbtState.
|
||||
func (s PsbtState) String() string {
|
||||
switch s {
|
||||
case PsbtShimRegistered:
|
||||
return "shim_registered"
|
||||
|
||||
case PsbtOutputKnown:
|
||||
return "output_known"
|
||||
|
||||
case PsbtVerified:
|
||||
return "verified"
|
||||
|
||||
case PsbtFinalized:
|
||||
return "finalized"
|
||||
|
||||
case PsbtFundingTxCompiled:
|
||||
return "funding_tx_compiled"
|
||||
|
||||
case PsbtInitiatorCanceled:
|
||||
return "user_canceled"
|
||||
|
||||
case PsbtResponderCanceled:
|
||||
return "remote_canceled"
|
||||
|
||||
default:
|
||||
return fmt.Sprintf("<unknown(%d)>", s)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrRemoteCanceled is the error that is returned to the user if the
|
||||
// funding flow was canceled by the remote peer.
|
||||
ErrRemoteCanceled = errors.New("remote canceled funding, possibly " +
|
||||
"timed out")
|
||||
|
||||
// ErrUserCanceled is the error that is returned through the PsbtReady
|
||||
// channel if the user canceled the funding flow.
|
||||
ErrUserCanceled = errors.New("user canceled funding")
|
||||
)
|
||||
|
||||
// PsbtIntent is an intent created by the PsbtAssembler which represents a
|
||||
// funding output to be created by a PSBT. This might be used when a hardware
|
||||
// wallet, or a channel factory is the entity crafting the funding transaction,
|
||||
// and not lnd.
|
||||
type PsbtIntent struct {
|
||||
// ShimIntent is the wrapped basic intent that contains common fields
|
||||
// we also use in the PSBT funding case.
|
||||
ShimIntent
|
||||
|
||||
// State is the current state the intent state machine is in.
|
||||
State PsbtState
|
||||
|
||||
// BasePsbt is the user-supplied base PSBT the channel output should be
|
||||
// added to. If this is nil we will create a new, empty PSBT as the base
|
||||
// for the funding transaction.
|
||||
BasePsbt *psbt.Packet
|
||||
|
||||
// PendingPsbt is the parsed version of the current PSBT. This can be
|
||||
// in two stages: If the user has not yet provided any PSBT, this is
|
||||
// nil. Once the user sends us an unsigned funded PSBT, we verify that
|
||||
// we have a valid transaction that sends to the channel output PK
|
||||
// script and has an input large enough to pay for it. We keep this
|
||||
// verified but not yet signed version around until the fully signed
|
||||
// transaction is submitted by the user. At that point we make sure the
|
||||
// inputs and outputs haven't changed to what was previously verified.
|
||||
// Only witness data should be added after the verification process.
|
||||
PendingPsbt *psbt.Packet
|
||||
|
||||
// PsbtReady is an error channel the funding manager will listen for
|
||||
// a signal about the PSBT being ready to continue the funding flow. In
|
||||
// the normal, happy flow, this channel is only ever closed. If a
|
||||
// non-nil error is sent through the channel, the funding flow will be
|
||||
// canceled.
|
||||
//
|
||||
// NOTE: This channel must always be buffered.
|
||||
PsbtReady chan error
|
||||
|
||||
// signalPsbtReady is a Once guard to make sure the PsbtReady channel is
|
||||
// only closed exactly once.
|
||||
signalPsbtReady sync.Once
|
||||
|
||||
// netParams are the network parameters used to encode the P2WSH funding
|
||||
// address.
|
||||
netParams *chaincfg.Params
|
||||
}
|
||||
|
||||
// BindKeys sets both the remote and local node's keys that will be used for the
|
||||
// channel funding multisig output.
|
||||
func (i *PsbtIntent) BindKeys(localKey *keychain.KeyDescriptor,
|
||||
remoteKey *btcec.PublicKey) {
|
||||
|
||||
i.localKey = localKey
|
||||
i.remoteKey = remoteKey
|
||||
i.State = PsbtOutputKnown
|
||||
}
|
||||
|
||||
// FundingParams returns the parameters that are necessary to start funding the
|
||||
// channel output this intent was created for. It returns the P2WSH funding
|
||||
// address, the exact funding amount and a PSBT packet that contains exactly one
|
||||
// output that encodes the previous two parameters.
|
||||
func (i *PsbtIntent) FundingParams() (btcutil.Address, int64, *psbt.Packet,
|
||||
error) {
|
||||
|
||||
if i.State != PsbtOutputKnown {
|
||||
return nil, 0, nil, fmt.Errorf("invalid state, got %v "+
|
||||
"expected %v", i.State, PsbtOutputKnown)
|
||||
}
|
||||
|
||||
// The funding output needs to be known already at this point, which
|
||||
// means we need to have the local and remote multisig keys bound
|
||||
// already.
|
||||
witnessScript, out, err := i.FundingOutput()
|
||||
if err != nil {
|
||||
return nil, 0, nil, fmt.Errorf("unable to create funding "+
|
||||
"output: %v", err)
|
||||
}
|
||||
witnessScriptHash := sha256.Sum256(witnessScript)
|
||||
|
||||
// Encode the address in the human readable bech32 format.
|
||||
addr, err := btcutil.NewAddressWitnessScriptHash(
|
||||
witnessScriptHash[:], i.netParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, nil, fmt.Errorf("unable to encode address: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
// We'll also encode the address/amount in a machine readable raw PSBT
|
||||
// format. If the user supplied a base PSBT, we'll add the output to
|
||||
// that one, otherwise we'll create a new one.
|
||||
packet := i.BasePsbt
|
||||
if packet == nil {
|
||||
packet, err = psbt.New(nil, nil, 2, 0, nil)
|
||||
if err != nil {
|
||||
return nil, 0, nil, fmt.Errorf("unable to create "+
|
||||
"PSBT: %v", err)
|
||||
}
|
||||
}
|
||||
packet.UnsignedTx.TxOut = append(packet.UnsignedTx.TxOut, out)
|
||||
packet.Outputs = append(packet.Outputs, psbt.POutput{})
|
||||
return addr, out.Value, packet, nil
|
||||
}
|
||||
|
||||
// Verify makes sure the PSBT that is given to the intent has an output that
|
||||
// sends to the channel funding multisig address with the correct amount. A
|
||||
// simple check that at least a single input has been specified is performed.
|
||||
func (i *PsbtIntent) Verify(packet *psbt.Packet) error {
|
||||
if packet == nil {
|
||||
return fmt.Errorf("PSBT is nil")
|
||||
}
|
||||
if i.State != PsbtOutputKnown {
|
||||
return fmt.Errorf("invalid state. got %v expected %v", i.State,
|
||||
PsbtOutputKnown)
|
||||
}
|
||||
|
||||
// Try to locate the channel funding multisig output.
|
||||
_, expectedOutput, err := i.FundingOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("funding output cannot be created: %v", err)
|
||||
}
|
||||
outputFound := false
|
||||
outputSum := int64(0)
|
||||
for _, out := range packet.UnsignedTx.TxOut {
|
||||
outputSum += out.Value
|
||||
if txOutsEqual(out, expectedOutput) {
|
||||
outputFound = true
|
||||
}
|
||||
}
|
||||
if !outputFound {
|
||||
return fmt.Errorf("funding output not found in PSBT")
|
||||
}
|
||||
|
||||
// At least one input needs to be specified and it must be large enough
|
||||
// to pay for all outputs. We don't want to dive into fee estimation
|
||||
// here so we just assume that if the input amount exceeds the output
|
||||
// amount, the chosen fee is sufficient.
|
||||
if len(packet.UnsignedTx.TxIn) == 0 {
|
||||
return fmt.Errorf("PSBT has no inputs")
|
||||
}
|
||||
sum, err := sumUtxoInputValues(packet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error determining input sum: %v", err)
|
||||
}
|
||||
if sum <= outputSum {
|
||||
return fmt.Errorf("input amount sum must be larger than " +
|
||||
"output amount sum")
|
||||
}
|
||||
|
||||
i.PendingPsbt = packet
|
||||
i.State = PsbtVerified
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finalize makes sure the final PSBT that is given to the intent is fully valid
|
||||
// and signed but still contains the same UTXOs and outputs as the pending
|
||||
// transaction we previously verified. If everything checks out, the funding
|
||||
// manager is informed that the channel can now be opened and the funding
|
||||
// transaction be broadcast.
|
||||
func (i *PsbtIntent) Finalize(packet *psbt.Packet) error {
|
||||
if packet == nil {
|
||||
return fmt.Errorf("PSBT is nil")
|
||||
}
|
||||
if i.State != PsbtVerified {
|
||||
return fmt.Errorf("invalid state. got %v expected %v", i.State,
|
||||
PsbtVerified)
|
||||
}
|
||||
|
||||
// Make sure the PSBT itself thinks it's finalized and ready to be
|
||||
// broadcast.
|
||||
err := psbt.MaybeFinalizeAll(packet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finalizing PSBT: %v", err)
|
||||
}
|
||||
_, err = psbt.Extract(packet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to extract funding TX: %v", err)
|
||||
}
|
||||
|
||||
// Do a basic check that this is still the same PSBT that we verified in
|
||||
// the previous step. This is to protect the user from unwanted
|
||||
// modifications. We only check the outputs and previous outpoints of
|
||||
// the inputs of the wire transaction because the fields in the PSBT
|
||||
// part are allowed to change.
|
||||
if i.PendingPsbt == nil {
|
||||
return fmt.Errorf("PSBT was not verified first")
|
||||
}
|
||||
err = verifyOutputsEqual(
|
||||
packet.UnsignedTx.TxOut, i.PendingPsbt.UnsignedTx.TxOut,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("outputs differ from verified PSBT: %v", err)
|
||||
}
|
||||
err = verifyInputPrevOutpointsEqual(
|
||||
packet.UnsignedTx.TxIn, i.PendingPsbt.UnsignedTx.TxIn,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inputs differ from verified PSBT: %v", err)
|
||||
}
|
||||
|
||||
// As far as we can tell, this PSBT is ok to be used as a funding
|
||||
// transaction.
|
||||
i.PendingPsbt = packet
|
||||
i.State = PsbtFinalized
|
||||
|
||||
// Signal the funding manager that it can now finally continue with its
|
||||
// funding flow as the PSBT is now ready to be converted into a real
|
||||
// transaction and be published.
|
||||
i.signalPsbtReady.Do(func() {
|
||||
close(i.PsbtReady)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompileFundingTx finalizes the previously verified PSBT and returns the
|
||||
// extracted binary serialized transaction from it. It also prepares the channel
|
||||
// point for which this funding intent was initiated for.
|
||||
func (i *PsbtIntent) CompileFundingTx() (*wire.MsgTx, error) {
|
||||
if i.State != PsbtFinalized {
|
||||
return nil, fmt.Errorf("invalid state. got %v expected %v",
|
||||
i.State, PsbtFinalized)
|
||||
}
|
||||
|
||||
// Make sure the PSBT can be finalized and extracted.
|
||||
err := psbt.MaybeFinalizeAll(i.PendingPsbt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error finalizing PSBT: %v", err)
|
||||
}
|
||||
fundingTx, err := psbt.Extract(i.PendingPsbt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract funding TX: %v", err)
|
||||
}
|
||||
|
||||
// Identify our funding outpoint now that we know everything's ready.
|
||||
_, txOut, err := i.FundingOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get funding output: %v", err)
|
||||
}
|
||||
ok, idx := input.FindScriptOutputIndex(fundingTx, txOut.PkScript)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("funding output not found in PSBT")
|
||||
}
|
||||
i.chanPoint = &wire.OutPoint{
|
||||
Hash: fundingTx.TxHash(),
|
||||
Index: idx,
|
||||
}
|
||||
i.State = PsbtFundingTxCompiled
|
||||
|
||||
return fundingTx, nil
|
||||
}
|
||||
|
||||
// RemoteCanceled informs the listener of the PSBT ready channel that the
|
||||
// funding has been canceled by the remote peer and that we can no longer
|
||||
// continue with it.
|
||||
func (i *PsbtIntent) RemoteCanceled() {
|
||||
log.Debugf("PSBT funding intent canceled by remote, state=%v", i.State)
|
||||
i.signalPsbtReady.Do(func() {
|
||||
i.PsbtReady <- ErrRemoteCanceled
|
||||
i.State = PsbtResponderCanceled
|
||||
})
|
||||
i.ShimIntent.Cancel()
|
||||
}
|
||||
|
||||
// Cancel allows the caller to cancel a funding Intent at any time. This will
|
||||
// return make sure the channel funding flow with the remote peer is failed and
|
||||
// any reservations are canceled.
|
||||
//
|
||||
// NOTE: Part of the chanfunding.Intent interface.
|
||||
func (i *PsbtIntent) Cancel() {
|
||||
log.Debugf("PSBT funding intent canceled, state=%v", i.State)
|
||||
i.signalPsbtReady.Do(func() {
|
||||
i.PsbtReady <- ErrUserCanceled
|
||||
i.State = PsbtInitiatorCanceled
|
||||
})
|
||||
i.ShimIntent.Cancel()
|
||||
}
|
||||
|
||||
// PsbtAssembler is a type of chanfunding.Assembler wherein the funding
|
||||
// transaction is constructed outside of lnd by using partially signed bitcoin
|
||||
// transactions (PSBT).
|
||||
type PsbtAssembler struct {
|
||||
// fundingAmt is the total amount of coins in the funding output.
|
||||
fundingAmt btcutil.Amount
|
||||
|
||||
// basePsbt is the user-supplied base PSBT the channel output should be
|
||||
// added to.
|
||||
basePsbt *psbt.Packet
|
||||
|
||||
// netParams are the network parameters used to encode the P2WSH funding
|
||||
// address.
|
||||
netParams *chaincfg.Params
|
||||
}
|
||||
|
||||
// NewPsbtAssembler creates a new CannedAssembler from the material required
|
||||
// to construct a funding output and channel point. An optional base PSBT can
|
||||
// be supplied which will be used to add the channel output to instead of
|
||||
// creating a new one.
|
||||
func NewPsbtAssembler(fundingAmt btcutil.Amount, basePsbt *psbt.Packet,
|
||||
netParams *chaincfg.Params) *PsbtAssembler {
|
||||
|
||||
return &PsbtAssembler{
|
||||
fundingAmt: fundingAmt,
|
||||
basePsbt: basePsbt,
|
||||
netParams: netParams,
|
||||
}
|
||||
}
|
||||
|
||||
// ProvisionChannel creates a new ShimIntent given the passed funding Request.
|
||||
// The returned intent is immediately able to provide the channel point and
|
||||
// funding output as they've already been created outside lnd.
|
||||
//
|
||||
// NOTE: This method satisfies the chanfunding.Assembler interface.
|
||||
func (p *PsbtAssembler) ProvisionChannel(req *Request) (Intent, error) {
|
||||
// We'll exit out if this field is set as the funding transaction will
|
||||
// be assembled externally, so we don't influence coin selection.
|
||||
if req.SubtractFees {
|
||||
return nil, fmt.Errorf("SubtractFees not supported for PSBT")
|
||||
}
|
||||
|
||||
intent := &PsbtIntent{
|
||||
ShimIntent: ShimIntent{
|
||||
localFundingAmt: p.fundingAmt,
|
||||
},
|
||||
State: PsbtShimRegistered,
|
||||
BasePsbt: p.basePsbt,
|
||||
PsbtReady: make(chan error, 1),
|
||||
netParams: p.netParams,
|
||||
}
|
||||
|
||||
// A simple sanity check to ensure the provisioned request matches the
|
||||
// re-made shim intent.
|
||||
if req.LocalAmt+req.RemoteAmt != p.fundingAmt {
|
||||
return nil, fmt.Errorf("intent doesn't match PSBT "+
|
||||
"assembler: local_amt=%v, remote_amt=%v, funding_amt=%v",
|
||||
req.LocalAmt, req.RemoteAmt, p.fundingAmt)
|
||||
}
|
||||
|
||||
return intent, nil
|
||||
}
|
||||
|
||||
// FundingTxAvailable is an empty method that an assembler can implement to
|
||||
// signal to callers that its able to provide the funding transaction for the
|
||||
// channel via the intent it returns.
|
||||
//
|
||||
// NOTE: This method is a part of the FundingTxAssembler interface.
|
||||
func (p *PsbtAssembler) FundingTxAvailable() {}
|
||||
|
||||
// A compile-time assertion to ensure PsbtAssembler meets the Assembler
|
||||
// interface.
|
||||
var _ Assembler = (*PsbtAssembler)(nil)
|
||||
|
||||
// sumUtxoInputValues tries to extract the sum of all inputs specified in the
|
||||
// UTXO fields of the PSBT. An error is returned if an input is specified that
|
||||
// does not contain any UTXO information.
|
||||
func sumUtxoInputValues(packet *psbt.Packet) (int64, error) {
|
||||
// We take the TX ins of the unsigned TX as the truth for how many
|
||||
// inputs there should be, as the fields in the extra data part of the
|
||||
// PSBT can be empty.
|
||||
if len(packet.UnsignedTx.TxIn) != len(packet.Inputs) {
|
||||
return 0, fmt.Errorf("TX input length doesn't match PSBT " +
|
||||
"input length")
|
||||
}
|
||||
inputSum := int64(0)
|
||||
for idx, in := range packet.Inputs {
|
||||
switch {
|
||||
case in.WitnessUtxo != nil:
|
||||
// Witness UTXOs only need to reference the TxOut.
|
||||
inputSum += in.WitnessUtxo.Value
|
||||
|
||||
case in.NonWitnessUtxo != nil:
|
||||
// Non-witness UTXOs reference to the whole transaction
|
||||
// the UTXO resides in.
|
||||
utxOuts := in.NonWitnessUtxo.TxOut
|
||||
txIn := packet.UnsignedTx.TxIn[idx]
|
||||
inputSum += utxOuts[txIn.PreviousOutPoint.Index].Value
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf("input %d has no UTXO information",
|
||||
idx)
|
||||
}
|
||||
}
|
||||
return inputSum, nil
|
||||
}
|
||||
|
||||
// txOutsEqual returns true if two transaction outputs are equal.
|
||||
func txOutsEqual(out1, out2 *wire.TxOut) bool {
|
||||
if out1 == nil || out2 == nil {
|
||||
return out1 == out2
|
||||
}
|
||||
return out1.Value == out2.Value &&
|
||||
bytes.Equal(out1.PkScript, out2.PkScript)
|
||||
}
|
||||
|
||||
// verifyOutputsEqual verifies that the two slices of transaction outputs are
|
||||
// deep equal to each other. We do the length check and manual loop to provide
|
||||
// better error messages to the user than just returning "not equal".
|
||||
func verifyOutputsEqual(outs1, outs2 []*wire.TxOut) error {
|
||||
if len(outs1) != len(outs2) {
|
||||
return fmt.Errorf("number of outputs are different")
|
||||
}
|
||||
for idx, out := range outs1 {
|
||||
// There is a byte slice in the output so we can't use the
|
||||
// equality operator.
|
||||
if !txOutsEqual(out, outs2[idx]) {
|
||||
return fmt.Errorf("output %d is different", idx)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyInputPrevOutpointsEqual verifies that the previous outpoints of the
|
||||
// two slices of transaction inputs are deep equal to each other. We do the
|
||||
// length check and manual loop to provide better error messages to the user
|
||||
// than just returning "not equal".
|
||||
func verifyInputPrevOutpointsEqual(ins1, ins2 []*wire.TxIn) error {
|
||||
if len(ins1) != len(ins2) {
|
||||
return fmt.Errorf("number of inputs are different")
|
||||
}
|
||||
for idx, in := range ins1 {
|
||||
if in.PreviousOutPoint != ins2[idx].PreviousOutPoint {
|
||||
return fmt.Errorf("previous outpoint of input %d is "+
|
||||
"different", idx)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
343
vendor/github.com/lightningnetwork/lnd/lnwallet/chanfunding/wallet_assembler.go
generated
vendored
Normal file
343
vendor/github.com/lightningnetwork/lnd/lnwallet/chanfunding/wallet_assembler.go
generated
vendored
Normal file
@@ -0,0 +1,343 @@
|
||||
package chanfunding
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcutil/txsort"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
||||
// FullIntent is an intent that is fully backed by the internal wallet. This
|
||||
// intent differs from the ShimIntent, in that the funding transaction will be
|
||||
// constructed internally, and will consist of only inputs we wholly control.
|
||||
// This Intent implements a basic state machine that must be executed in order
|
||||
// before CompileFundingTx can be called.
|
||||
//
|
||||
// Steps to final channel provisioning:
|
||||
// 1. Call BindKeys to notify the intent which keys to use when constructing
|
||||
// the multi-sig output.
|
||||
// 2. Call CompileFundingTx afterwards to obtain the funding transaction.
|
||||
//
|
||||
// If either of these steps fail, then the Cancel method MUST be called.
|
||||
type FullIntent struct {
|
||||
ShimIntent
|
||||
|
||||
// InputCoins are the set of coins selected as inputs to this funding
|
||||
// transaction.
|
||||
InputCoins []Coin
|
||||
|
||||
// ChangeOutputs are the set of outputs that the Assembler will use as
|
||||
// change from the main funding transaction.
|
||||
ChangeOutputs []*wire.TxOut
|
||||
|
||||
// coinLocker is the Assembler's instance of the OutpointLocker
|
||||
// interface.
|
||||
coinLocker OutpointLocker
|
||||
|
||||
// coinSource is the Assembler's instance of the CoinSource interface.
|
||||
coinSource CoinSource
|
||||
|
||||
// signer is the Assembler's instance of the Singer interface.
|
||||
signer input.Signer
|
||||
}
|
||||
|
||||
// BindKeys is a method unique to the FullIntent variant. This allows the
|
||||
// caller to decide precisely which keys are used in the final funding
|
||||
// transaction. This is kept out of the main Assembler as these may may not
|
||||
// necessarily be under full control of the wallet. Only after this method has
|
||||
// been executed will CompileFundingTx succeed.
|
||||
func (f *FullIntent) BindKeys(localKey *keychain.KeyDescriptor,
|
||||
remoteKey *btcec.PublicKey) {
|
||||
|
||||
f.localKey = localKey
|
||||
f.remoteKey = remoteKey
|
||||
}
|
||||
|
||||
// CompileFundingTx is to be called after BindKeys on the sub-intent has been
|
||||
// called. This method will construct the final funding transaction, and fully
|
||||
// sign all inputs that are known by the backing CoinSource. After this method
|
||||
// returns, the Intent is assumed to be complete, as the output can be created
|
||||
// at any point.
|
||||
func (f *FullIntent) CompileFundingTx(extraInputs []*wire.TxIn,
|
||||
extraOutputs []*wire.TxOut) (*wire.MsgTx, error) {
|
||||
|
||||
// Create a blank, fresh transaction. Soon to be a complete funding
|
||||
// transaction which will allow opening a lightning channel.
|
||||
fundingTx := wire.NewMsgTx(2)
|
||||
|
||||
// Add all multi-party inputs and outputs to the transaction.
|
||||
for _, coin := range f.InputCoins {
|
||||
fundingTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: coin.OutPoint,
|
||||
})
|
||||
}
|
||||
for _, theirInput := range extraInputs {
|
||||
fundingTx.AddTxIn(theirInput)
|
||||
}
|
||||
for _, ourChangeOutput := range f.ChangeOutputs {
|
||||
fundingTx.AddTxOut(ourChangeOutput)
|
||||
}
|
||||
for _, theirChangeOutput := range extraOutputs {
|
||||
fundingTx.AddTxOut(theirChangeOutput)
|
||||
}
|
||||
|
||||
_, fundingOutput, err := f.FundingOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sort the transaction. Since both side agree to a canonical ordering,
|
||||
// by sorting we no longer need to send the entire transaction. Only
|
||||
// signatures will be exchanged.
|
||||
fundingTx.AddTxOut(fundingOutput)
|
||||
txsort.InPlaceSort(fundingTx)
|
||||
|
||||
// Now that the funding tx has been fully assembled, we'll locate the
|
||||
// index of the funding output so we can create our final channel
|
||||
// point.
|
||||
_, multiSigIndex := input.FindScriptOutputIndex(
|
||||
fundingTx, fundingOutput.PkScript,
|
||||
)
|
||||
|
||||
// Next, sign all inputs that are ours, collecting the signatures in
|
||||
// order of the inputs.
|
||||
signDesc := input.SignDescriptor{
|
||||
HashType: txscript.SigHashAll,
|
||||
SigHashes: txscript.NewTxSigHashes(fundingTx),
|
||||
}
|
||||
for i, txIn := range fundingTx.TxIn {
|
||||
// We can only sign this input if it's ours, so we'll ask the
|
||||
// coin source if it can map this outpoint into a coin we own.
|
||||
// If not, then we'll continue as it isn't our input.
|
||||
info, err := f.coinSource.CoinFromOutPoint(
|
||||
txIn.PreviousOutPoint,
|
||||
)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Now that we know the input is ours, we'll populate the
|
||||
// signDesc with the per input unique information.
|
||||
signDesc.Output = &wire.TxOut{
|
||||
Value: info.Value,
|
||||
PkScript: info.PkScript,
|
||||
}
|
||||
signDesc.InputIndex = i
|
||||
|
||||
// Finally, we'll sign the input as is, and populate the input
|
||||
// with the witness and sigScript (if needed).
|
||||
inputScript, err := f.signer.ComputeInputScript(
|
||||
fundingTx, &signDesc,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txIn.SignatureScript = inputScript.SigScript
|
||||
txIn.Witness = inputScript.Witness
|
||||
}
|
||||
|
||||
// Finally, we'll populate the chanPoint now that we've fully
|
||||
// constructed the funding transaction.
|
||||
f.chanPoint = &wire.OutPoint{
|
||||
Hash: fundingTx.TxHash(),
|
||||
Index: multiSigIndex,
|
||||
}
|
||||
|
||||
return fundingTx, nil
|
||||
}
|
||||
|
||||
// Cancel allows the caller to cancel a funding Intent at any time. This will
|
||||
// return any resources such as coins back to the eligible pool to be used in
|
||||
// order channel fundings.
|
||||
//
|
||||
// NOTE: Part of the chanfunding.Intent interface.
|
||||
func (f *FullIntent) Cancel() {
|
||||
for _, coin := range f.InputCoins {
|
||||
f.coinLocker.UnlockOutpoint(coin.OutPoint)
|
||||
}
|
||||
|
||||
f.ShimIntent.Cancel()
|
||||
}
|
||||
|
||||
// A compile-time check to ensure FullIntent meets the Intent interface.
|
||||
var _ Intent = (*FullIntent)(nil)
|
||||
|
||||
// WalletConfig is the main config of the WalletAssembler.
|
||||
type WalletConfig struct {
|
||||
// CoinSource is what the WalletAssembler uses to list/locate coins.
|
||||
CoinSource CoinSource
|
||||
|
||||
// CoinSelectionLocker allows the WalletAssembler to gain exclusive
|
||||
// access to the current set of coins returned by the CoinSource.
|
||||
CoinSelectLocker CoinSelectionLocker
|
||||
|
||||
// CoinLocker is what the WalletAssembler uses to lock coins that may
|
||||
// be used as inputs for a new funding transaction.
|
||||
CoinLocker OutpointLocker
|
||||
|
||||
// Signer allows the WalletAssembler to sign inputs on any potential
|
||||
// funding transactions.
|
||||
Signer input.Signer
|
||||
|
||||
// DustLimit is the current dust limit. We'll use this to ensure that
|
||||
// we don't make dust outputs on the funding transaction.
|
||||
DustLimit btcutil.Amount
|
||||
}
|
||||
|
||||
// WalletAssembler is an instance of the Assembler interface that is backed by
|
||||
// a full wallet. This variant of the Assembler interface will produce the
|
||||
// entirety of the funding transaction within the wallet. This implements the
|
||||
// typical funding flow that is initiated either on the p2p level or using the
|
||||
// CLi.
|
||||
type WalletAssembler struct {
|
||||
cfg WalletConfig
|
||||
}
|
||||
|
||||
// NewWalletAssembler creates a new instance of the WalletAssembler from a
|
||||
// fully populated wallet config.
|
||||
func NewWalletAssembler(cfg WalletConfig) *WalletAssembler {
|
||||
return &WalletAssembler{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// ProvisionChannel is the main entry point to begin a funding workflow given a
|
||||
// fully populated request. The internal WalletAssembler will perform coin
|
||||
// selection in a goroutine safe manner, returning an Intent that will allow
|
||||
// the caller to finalize the funding process.
|
||||
//
|
||||
// NOTE: To cancel the funding flow the Cancel() method on the returned Intent,
|
||||
// MUST be called.
|
||||
//
|
||||
// NOTE: This is a part of the chanfunding.Assembler interface.
|
||||
func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
|
||||
var intent Intent
|
||||
|
||||
// We hold the coin select mutex while querying for outputs, and
|
||||
// performing coin selection in order to avoid inadvertent double
|
||||
// spends across funding transactions.
|
||||
err := w.cfg.CoinSelectLocker.WithCoinSelectLock(func() error {
|
||||
log.Infof("Performing funding tx coin selection using %v "+
|
||||
"sat/kw as fee rate", int64(r.FeeRate))
|
||||
|
||||
// Find all unlocked unspent witness outputs that satisfy the
|
||||
// minimum number of confirmations required.
|
||||
coins, err := w.cfg.CoinSource.ListCoins(
|
||||
r.MinConfs, math.MaxInt32,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
selectedCoins []Coin
|
||||
localContributionAmt btcutil.Amount
|
||||
changeAmt btcutil.Amount
|
||||
)
|
||||
|
||||
// Perform coin selection over our available, unlocked unspent
|
||||
// outputs in order to find enough coins to meet the funding
|
||||
// amount requirements.
|
||||
switch {
|
||||
// If there's no funding amount at all (receiving an inbound
|
||||
// single funder request), then we don't need to perform any
|
||||
// coin selection at all.
|
||||
case r.LocalAmt == 0:
|
||||
break
|
||||
|
||||
// In case this request want the fees subtracted from the local
|
||||
// amount, we'll call the specialized method for that. This
|
||||
// ensures that we won't deduct more that the specified balance
|
||||
// from our wallet.
|
||||
case r.SubtractFees:
|
||||
dustLimit := w.cfg.DustLimit
|
||||
selectedCoins, localContributionAmt, changeAmt, err = CoinSelectSubtractFees(
|
||||
r.FeeRate, r.LocalAmt, dustLimit, coins,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Otherwise do a normal coin selection where we target a given
|
||||
// funding amount.
|
||||
default:
|
||||
localContributionAmt = r.LocalAmt
|
||||
selectedCoins, changeAmt, err = CoinSelect(
|
||||
r.FeeRate, r.LocalAmt, coins,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Record any change output(s) generated as a result of the
|
||||
// coin selection, but only if the addition of the output won't
|
||||
// lead to the creation of dust.
|
||||
var changeOutput *wire.TxOut
|
||||
if changeAmt != 0 && changeAmt > w.cfg.DustLimit {
|
||||
changeAddr, err := r.ChangeAddr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changeScript, err := txscript.PayToAddrScript(changeAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changeOutput = &wire.TxOut{
|
||||
Value: int64(changeAmt),
|
||||
PkScript: changeScript,
|
||||
}
|
||||
}
|
||||
|
||||
// Lock the selected coins. These coins are now "reserved",
|
||||
// this prevents concurrent funding requests from referring to
|
||||
// and this double-spending the same set of coins.
|
||||
for _, coin := range selectedCoins {
|
||||
outpoint := coin.OutPoint
|
||||
|
||||
w.cfg.CoinLocker.LockOutpoint(outpoint)
|
||||
}
|
||||
|
||||
newIntent := &FullIntent{
|
||||
ShimIntent: ShimIntent{
|
||||
localFundingAmt: localContributionAmt,
|
||||
remoteFundingAmt: r.RemoteAmt,
|
||||
},
|
||||
InputCoins: selectedCoins,
|
||||
coinLocker: w.cfg.CoinLocker,
|
||||
coinSource: w.cfg.CoinSource,
|
||||
signer: w.cfg.Signer,
|
||||
}
|
||||
|
||||
if changeOutput != nil {
|
||||
newIntent.ChangeOutputs = []*wire.TxOut{changeOutput}
|
||||
}
|
||||
|
||||
intent = newIntent
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return intent, nil
|
||||
}
|
||||
|
||||
// FundingTxAvailable is an empty method that an assembler can implement to
|
||||
// signal to callers that its able to provide the funding transaction for the
|
||||
// channel via the intent it returns.
|
||||
//
|
||||
// NOTE: This method is a part of the FundingTxAssembler interface.
|
||||
func (w *WalletAssembler) FundingTxAvailable() {}
|
||||
|
||||
// A compile-time assertion to ensure the WalletAssembler meets the
|
||||
// FundingTxAssembler interface.
|
||||
var _ FundingTxAssembler = (*WalletAssembler)(nil)
|
||||
Reference in New Issue
Block a user