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:
26
vendor/github.com/lightningnetwork/lnd/lnwallet/README.md
generated
vendored
Normal file
26
vendor/github.com/lightningnetwork/lnd/lnwallet/README.md
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
lnwallet
|
||||
=========
|
||||
|
||||
[](https://travis-ci.org/lightningnetwork/lnd)
|
||||
[](https://github.com/lightningnetwork/lnd/blob/master/LICENSE)
|
||||
[](http://godoc.org/github.com/lightningnetwork/lnd/lnwallet)
|
||||
|
||||
The lnwallet package implements an abstracted wallet controller that is able to
|
||||
drive channel funding workflows, a number of script utilities, witness
|
||||
generation functions for the various Lightning scripts, revocation key
|
||||
derivation, and the commitment update state machine.
|
||||
|
||||
The package is used within `lnd` as the core wallet of the daemon. The wallet
|
||||
itself is composed of several distinct interfaces that decouple the
|
||||
implementation of things like signing and blockchain access. This separation
|
||||
allows new `WalletController` implementations to be easily dropped into
|
||||
`lnd` without disrupting the code base. A series of integration tests at the
|
||||
interface level are also in place to ensure conformance of the implementation
|
||||
with the interface.
|
||||
|
||||
|
||||
## Installation and Updating
|
||||
|
||||
```bash
|
||||
$ go get -u github.com/lightningnetwork/lnd/lnwallet
|
||||
```
|
||||
731
vendor/github.com/lightningnetwork/lnd/lnwallet/chainfee/estimator.go
generated
vendored
Normal file
731
vendor/github.com/lightningnetwork/lnd/lnwallet/chainfee/estimator.go
generated
vendored
Normal file
@@ -0,0 +1,731 @@
|
||||
package chainfee
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
prand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxBlockTarget is the highest number of blocks confirmations that
|
||||
// a WebAPIEstimator will cache fees for. This number is chosen
|
||||
// because it's the highest number of confs bitcoind will return a fee
|
||||
// estimate for.
|
||||
maxBlockTarget uint32 = 1009
|
||||
|
||||
// minBlockTarget is the lowest number of blocks confirmations that
|
||||
// a WebAPIEstimator will cache fees for. Requesting an estimate for
|
||||
// less than this will result in an error.
|
||||
minBlockTarget uint32 = 2
|
||||
|
||||
// minFeeUpdateTimeout represents the minimum interval in which a
|
||||
// WebAPIEstimator will request fresh fees from its API.
|
||||
minFeeUpdateTimeout = 5 * time.Minute
|
||||
|
||||
// maxFeeUpdateTimeout represents the maximum interval in which a
|
||||
// WebAPIEstimator will request fresh fees from its API.
|
||||
maxFeeUpdateTimeout = 20 * time.Minute
|
||||
)
|
||||
|
||||
// Estimator provides the ability to estimate on-chain transaction fees for
|
||||
// various combinations of transaction sizes and desired confirmation time
|
||||
// (measured by number of blocks).
|
||||
type Estimator interface {
|
||||
// EstimateFeePerKW takes in a target for the number of blocks until an
|
||||
// initial confirmation and returns the estimated fee expressed in
|
||||
// sat/kw.
|
||||
EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error)
|
||||
|
||||
// Start signals the Estimator to start any processes or goroutines
|
||||
// it needs to perform its duty.
|
||||
Start() error
|
||||
|
||||
// Stop stops any spawned goroutines and cleans up the resources used
|
||||
// by the fee estimator.
|
||||
Stop() error
|
||||
|
||||
// RelayFeePerKW returns the minimum fee rate required for transactions
|
||||
// to be relayed. This is also the basis for calculation of the dust
|
||||
// limit.
|
||||
RelayFeePerKW() SatPerKWeight
|
||||
}
|
||||
|
||||
// StaticEstimator will return a static value for all fee calculation requests.
|
||||
// It is designed to be replaced by a proper fee calculation implementation.
|
||||
// The fees are not accessible directly, because changing them would not be
|
||||
// thread safe.
|
||||
type StaticEstimator struct {
|
||||
// feePerKW is the static fee rate in satoshis-per-vbyte that will be
|
||||
// returned by this fee estimator.
|
||||
feePerKW SatPerKWeight
|
||||
|
||||
// relayFee is the minimum fee rate required for transactions to be
|
||||
// relayed.
|
||||
relayFee SatPerKWeight
|
||||
}
|
||||
|
||||
// NewStaticEstimator returns a new static fee estimator instance.
|
||||
func NewStaticEstimator(feePerKW, relayFee SatPerKWeight) *StaticEstimator {
|
||||
|
||||
return &StaticEstimator{
|
||||
feePerKW: feePerKW,
|
||||
relayFee: relayFee,
|
||||
}
|
||||
}
|
||||
|
||||
// EstimateFeePerKW will return a static value for fee calculations.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (e StaticEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) {
|
||||
return e.feePerKW, nil
|
||||
}
|
||||
|
||||
// RelayFeePerKW returns the minimum fee rate required for transactions to be
|
||||
// relayed.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (e StaticEstimator) RelayFeePerKW() SatPerKWeight {
|
||||
return e.relayFee
|
||||
}
|
||||
|
||||
// Start signals the Estimator to start any processes or goroutines
|
||||
// it needs to perform its duty.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (e StaticEstimator) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops any spawned goroutines and cleans up the resources used
|
||||
// by the fee estimator.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (e StaticEstimator) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure that StaticFeeEstimator implements the
|
||||
// Estimator interface.
|
||||
var _ Estimator = (*StaticEstimator)(nil)
|
||||
|
||||
// BtcdEstimator is an implementation of the Estimator interface backed
|
||||
// by the RPC interface of an active btcd node. This implementation will proxy
|
||||
// any fee estimation requests to btcd's RPC interface.
|
||||
type BtcdEstimator struct {
|
||||
// fallbackFeePerKW is the fall back fee rate in sat/kw that is returned
|
||||
// if the fee estimator does not yet have enough data to actually
|
||||
// produce fee estimates.
|
||||
fallbackFeePerKW SatPerKWeight
|
||||
|
||||
// minFeePerKW is the minimum fee, in sat/kw, that we should enforce.
|
||||
// This will be used as the default fee rate for a transaction when the
|
||||
// estimated fee rate is too low to allow the transaction to propagate
|
||||
// through the network.
|
||||
minFeePerKW SatPerKWeight
|
||||
|
||||
btcdConn *rpcclient.Client
|
||||
}
|
||||
|
||||
// NewBtcdEstimator creates a new BtcdEstimator given a fully populated
|
||||
// rpc config that is able to successfully connect and authenticate with the
|
||||
// btcd node, and also a fall back fee rate. The fallback fee rate is used in
|
||||
// the occasion that the estimator has insufficient data, or returns zero for a
|
||||
// fee estimate.
|
||||
func NewBtcdEstimator(rpcConfig rpcclient.ConnConfig,
|
||||
fallBackFeeRate SatPerKWeight) (*BtcdEstimator, error) {
|
||||
|
||||
rpcConfig.DisableConnectOnNew = true
|
||||
rpcConfig.DisableAutoReconnect = false
|
||||
chainConn, err := rpcclient.New(&rpcConfig, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BtcdEstimator{
|
||||
fallbackFeePerKW: fallBackFeeRate,
|
||||
btcdConn: chainConn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start signals the Estimator to start any processes or goroutines
|
||||
// it needs to perform its duty.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (b *BtcdEstimator) Start() error {
|
||||
if err := b.btcdConn.Connect(20); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Once the connection to the backend node has been established, we'll
|
||||
// query it for its minimum relay fee.
|
||||
info, err := b.btcdConn.GetInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relayFee, err := btcutil.NewAmount(info.RelayFee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The fee rate is expressed in sat/kb, so we'll manually convert it to
|
||||
// our desired sat/kw rate.
|
||||
minRelayFeePerKw := SatPerKVByte(relayFee).FeePerKWeight()
|
||||
|
||||
// By default, we'll use the backend node's minimum relay fee as the
|
||||
// minimum fee rate we'll propose for transacations. However, if this
|
||||
// happens to be lower than our fee floor, we'll enforce that instead.
|
||||
b.minFeePerKW = minRelayFeePerKw
|
||||
if b.minFeePerKW < FeePerKwFloor {
|
||||
b.minFeePerKW = FeePerKwFloor
|
||||
}
|
||||
|
||||
log.Debugf("Using minimum fee rate of %v sat/kw",
|
||||
int64(b.minFeePerKW))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops any spawned goroutines and cleans up the resources used
|
||||
// by the fee estimator.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (b *BtcdEstimator) Stop() error {
|
||||
b.btcdConn.Shutdown()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EstimateFeePerKW takes in a target for the number of blocks until an initial
|
||||
// confirmation and returns the estimated fee expressed in sat/kw.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (b *BtcdEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) {
|
||||
feeEstimate, err := b.fetchEstimate(numBlocks)
|
||||
switch {
|
||||
// If the estimator doesn't have enough data, or returns an error, then
|
||||
// to return a proper value, then we'll return the default fall back
|
||||
// fee rate.
|
||||
case err != nil:
|
||||
log.Errorf("unable to query estimator: %v", err)
|
||||
fallthrough
|
||||
|
||||
case feeEstimate == 0:
|
||||
return b.fallbackFeePerKW, nil
|
||||
}
|
||||
|
||||
return feeEstimate, nil
|
||||
}
|
||||
|
||||
// RelayFeePerKW returns the minimum fee rate required for transactions to be
|
||||
// relayed.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (b *BtcdEstimator) RelayFeePerKW() SatPerKWeight {
|
||||
return b.minFeePerKW
|
||||
}
|
||||
|
||||
// fetchEstimate returns a fee estimate for a transaction to be confirmed in
|
||||
// confTarget blocks. The estimate is returned in sat/kw.
|
||||
func (b *BtcdEstimator) fetchEstimate(confTarget uint32) (SatPerKWeight, error) {
|
||||
// First, we'll fetch the estimate for our confirmation target.
|
||||
btcPerKB, err := b.btcdConn.EstimateFee(int64(confTarget))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Next, we'll convert the returned value to satoshis, as it's
|
||||
// currently returned in BTC.
|
||||
satPerKB, err := btcutil.NewAmount(btcPerKB)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Since we use fee rates in sat/kw internally, we'll convert the
|
||||
// estimated fee rate from its sat/kb representation to sat/kw.
|
||||
satPerKw := SatPerKVByte(satPerKB).FeePerKWeight()
|
||||
|
||||
// Finally, we'll enforce our fee floor.
|
||||
if satPerKw < b.minFeePerKW {
|
||||
log.Debugf("Estimated fee rate of %v sat/kw is too low, "+
|
||||
"using fee floor of %v sat/kw instead", satPerKw,
|
||||
b.minFeePerKW)
|
||||
satPerKw = b.minFeePerKW
|
||||
}
|
||||
|
||||
log.Debugf("Returning %v sat/kw for conf target of %v",
|
||||
int64(satPerKw), confTarget)
|
||||
|
||||
return satPerKw, nil
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure that BtcdEstimator implements the
|
||||
// Estimator interface.
|
||||
var _ Estimator = (*BtcdEstimator)(nil)
|
||||
|
||||
// BitcoindEstimator is an implementation of the Estimator interface backed by
|
||||
// the RPC interface of an active bitcoind node. This implementation will proxy
|
||||
// any fee estimation requests to bitcoind's RPC interface.
|
||||
type BitcoindEstimator struct {
|
||||
// fallbackFeePerKW is the fallback fee rate in sat/kw that is returned
|
||||
// if the fee estimator does not yet have enough data to actually
|
||||
// produce fee estimates.
|
||||
fallbackFeePerKW SatPerKWeight
|
||||
|
||||
// minFeePerKW is the minimum fee, in sat/kw, that we should enforce.
|
||||
// This will be used as the default fee rate for a transaction when the
|
||||
// estimated fee rate is too low to allow the transaction to propagate
|
||||
// through the network.
|
||||
minFeePerKW SatPerKWeight
|
||||
|
||||
// feeMode is the estimate_mode to use when calling "estimatesmartfee".
|
||||
// It can be either "ECONOMICAL" or "CONSERVATIVE", and it's default
|
||||
// to "CONSERVATIVE".
|
||||
feeMode string
|
||||
|
||||
bitcoindConn *rpcclient.Client
|
||||
}
|
||||
|
||||
// NewBitcoindEstimator creates a new BitcoindEstimator given a fully populated
|
||||
// rpc config that is able to successfully connect and authenticate with the
|
||||
// bitcoind node, and also a fall back fee rate. The fallback fee rate is used
|
||||
// in the occasion that the estimator has insufficient data, or returns zero
|
||||
// for a fee estimate.
|
||||
func NewBitcoindEstimator(rpcConfig rpcclient.ConnConfig, feeMode string,
|
||||
fallBackFeeRate SatPerKWeight) (*BitcoindEstimator, error) {
|
||||
|
||||
rpcConfig.DisableConnectOnNew = true
|
||||
rpcConfig.DisableAutoReconnect = false
|
||||
rpcConfig.DisableTLS = true
|
||||
rpcConfig.HTTPPostMode = true
|
||||
chainConn, err := rpcclient.New(&rpcConfig, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BitcoindEstimator{
|
||||
fallbackFeePerKW: fallBackFeeRate,
|
||||
bitcoindConn: chainConn,
|
||||
feeMode: feeMode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start signals the Estimator to start any processes or goroutines
|
||||
// it needs to perform its duty.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (b *BitcoindEstimator) Start() error {
|
||||
// Once the connection to the backend node has been established, we'll
|
||||
// query it for its minimum relay fee. Since the `getinfo` RPC has been
|
||||
// deprecated for `bitcoind`, we'll need to send a `getnetworkinfo`
|
||||
// command as a raw request.
|
||||
resp, err := b.bitcoindConn.RawRequest("getnetworkinfo", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse the response to retrieve the relay fee in sat/KB.
|
||||
info := struct {
|
||||
RelayFee float64 `json:"relayfee"`
|
||||
}{}
|
||||
if err := json.Unmarshal(resp, &info); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relayFee, err := btcutil.NewAmount(info.RelayFee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The fee rate is expressed in sat/kb, so we'll manually convert it to
|
||||
// our desired sat/kw rate.
|
||||
minRelayFeePerKw := SatPerKVByte(relayFee).FeePerKWeight()
|
||||
|
||||
// By default, we'll use the backend node's minimum relay fee as the
|
||||
// minimum fee rate we'll propose for transacations. However, if this
|
||||
// happens to be lower than our fee floor, we'll enforce that instead.
|
||||
b.minFeePerKW = minRelayFeePerKw
|
||||
if b.minFeePerKW < FeePerKwFloor {
|
||||
b.minFeePerKW = FeePerKwFloor
|
||||
}
|
||||
|
||||
log.Debugf("Using minimum fee rate of %v sat/kw",
|
||||
int64(b.minFeePerKW))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops any spawned goroutines and cleans up the resources used
|
||||
// by the fee estimator.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (b *BitcoindEstimator) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EstimateFeePerKW takes in a target for the number of blocks until an initial
|
||||
// confirmation and returns the estimated fee expressed in sat/kw.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (b *BitcoindEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) {
|
||||
feeEstimate, err := b.fetchEstimate(numBlocks)
|
||||
switch {
|
||||
// If the estimator doesn't have enough data, or returns an error, then
|
||||
// to return a proper value, then we'll return the default fall back
|
||||
// fee rate.
|
||||
case err != nil:
|
||||
log.Errorf("unable to query estimator: %v", err)
|
||||
fallthrough
|
||||
|
||||
case feeEstimate == 0:
|
||||
return b.fallbackFeePerKW, nil
|
||||
}
|
||||
|
||||
return feeEstimate, nil
|
||||
}
|
||||
|
||||
// RelayFeePerKW returns the minimum fee rate required for transactions to be
|
||||
// relayed.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (b *BitcoindEstimator) RelayFeePerKW() SatPerKWeight {
|
||||
return b.minFeePerKW
|
||||
}
|
||||
|
||||
// fetchEstimate returns a fee estimate for a transaction to be confirmed in
|
||||
// confTarget blocks. The estimate is returned in sat/kw.
|
||||
func (b *BitcoindEstimator) fetchEstimate(confTarget uint32) (SatPerKWeight, error) {
|
||||
// First, we'll send an "estimatesmartfee" command as a raw request,
|
||||
// since it isn't supported by btcd but is available in bitcoind.
|
||||
target, err := json.Marshal(uint64(confTarget))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// The mode must be either ECONOMICAL or CONSERVATIVE.
|
||||
mode, err := json.Marshal(b.feeMode)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
resp, err := b.bitcoindConn.RawRequest(
|
||||
"estimatesmartfee", []json.RawMessage{target, mode},
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Next, we'll parse the response to get the BTC per KB.
|
||||
feeEstimate := struct {
|
||||
FeeRate float64 `json:"feerate"`
|
||||
}{}
|
||||
err = json.Unmarshal(resp, &feeEstimate)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Next, we'll convert the returned value to satoshis, as it's currently
|
||||
// returned in BTC.
|
||||
satPerKB, err := btcutil.NewAmount(feeEstimate.FeeRate)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Since we use fee rates in sat/kw internally, we'll convert the
|
||||
// estimated fee rate from its sat/kb representation to sat/kw.
|
||||
satPerKw := SatPerKVByte(satPerKB).FeePerKWeight()
|
||||
|
||||
// Finally, we'll enforce our fee floor.
|
||||
if satPerKw < b.minFeePerKW {
|
||||
log.Debugf("Estimated fee rate of %v sat/kw is too low, "+
|
||||
"using fee floor of %v sat/kw instead", satPerKw,
|
||||
b.minFeePerKW)
|
||||
|
||||
satPerKw = b.minFeePerKW
|
||||
}
|
||||
|
||||
log.Debugf("Returning %v sat/kw for conf target of %v",
|
||||
int64(satPerKw), confTarget)
|
||||
|
||||
return satPerKw, nil
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure that BitcoindEstimator implements the
|
||||
// Estimator interface.
|
||||
var _ Estimator = (*BitcoindEstimator)(nil)
|
||||
|
||||
// WebAPIFeeSource is an interface allows the WebAPIEstimator to query an
|
||||
// arbitrary HTTP-based fee estimator. Each new set/network will gain an
|
||||
// implementation of this interface in order to allow the WebAPIEstimator to
|
||||
// be fully generic in its logic.
|
||||
type WebAPIFeeSource interface {
|
||||
// GenQueryURL generates the full query URL. The value returned by this
|
||||
// method should be able to be used directly as a path for an HTTP GET
|
||||
// request.
|
||||
GenQueryURL() string
|
||||
|
||||
// ParseResponse attempts to parse the body of the response generated
|
||||
// by the above query URL. Typically this will be JSON, but the
|
||||
// specifics are left to the WebAPIFeeSource implementation.
|
||||
ParseResponse(r io.Reader) (map[uint32]uint32, error)
|
||||
}
|
||||
|
||||
// SparseConfFeeSource is an implementation of the WebAPIFeeSource that utilizes
|
||||
// a user-specified fee estimation API for Bitcoin. It expects the response
|
||||
// to be in the JSON format: `fee_by_block_target: { ... }` where the value maps
|
||||
// block targets to fee estimates (in sat per kilovbyte).
|
||||
type SparseConfFeeSource struct {
|
||||
// URL is the fee estimation API specified by the user.
|
||||
URL string
|
||||
}
|
||||
|
||||
// GenQueryURL generates the full query URL. The value returned by this
|
||||
// method should be able to be used directly as a path for an HTTP GET
|
||||
// request.
|
||||
//
|
||||
// NOTE: Part of the WebAPIFeeSource interface.
|
||||
func (s SparseConfFeeSource) GenQueryURL() string {
|
||||
return s.URL
|
||||
}
|
||||
|
||||
// ParseResponse attempts to parse the body of the response generated by the
|
||||
// above query URL. Typically this will be JSON, but the specifics are left to
|
||||
// the WebAPIFeeSource implementation.
|
||||
//
|
||||
// NOTE: Part of the WebAPIFeeSource interface.
|
||||
func (s SparseConfFeeSource) ParseResponse(r io.Reader) (map[uint32]uint32, error) {
|
||||
type jsonResp struct {
|
||||
FeeByBlockTarget map[uint32]uint32 `json:"fee_by_block_target"`
|
||||
}
|
||||
|
||||
resp := jsonResp{
|
||||
FeeByBlockTarget: make(map[uint32]uint32),
|
||||
}
|
||||
jsonReader := json.NewDecoder(r)
|
||||
if err := jsonReader.Decode(&resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp.FeeByBlockTarget, nil
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure that SparseConfFeeSource implements the
|
||||
// WebAPIFeeSource interface.
|
||||
var _ WebAPIFeeSource = (*SparseConfFeeSource)(nil)
|
||||
|
||||
// WebAPIEstimator is an implementation of the Estimator interface that
|
||||
// queries an HTTP-based fee estimation from an existing web API.
|
||||
type WebAPIEstimator struct {
|
||||
started sync.Once
|
||||
stopped sync.Once
|
||||
|
||||
// apiSource is the backing web API source we'll use for our queries.
|
||||
apiSource WebAPIFeeSource
|
||||
|
||||
// updateFeeTicker is the ticker responsible for updating the Estimator's
|
||||
// fee estimates every time it fires.
|
||||
updateFeeTicker *time.Ticker
|
||||
|
||||
// feeByBlockTarget is our cache for fees pulled from the API. When a
|
||||
// fee estimate request comes in, we pull the estimate from this array
|
||||
// rather than re-querying the API, to prevent an inadvertent DoS attack.
|
||||
feesMtx sync.Mutex
|
||||
feeByBlockTarget map[uint32]uint32
|
||||
|
||||
// defaultFeePerKw is a fallback value that we'll use if we're unable
|
||||
// to query the API for any reason.
|
||||
defaultFeePerKw SatPerKWeight
|
||||
|
||||
quit chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewWebAPIEstimator creates a new WebAPIEstimator from a given URL and a
|
||||
// fallback default fee. The fees are updated whenever a new block is mined.
|
||||
func NewWebAPIEstimator(
|
||||
api WebAPIFeeSource, defaultFee SatPerKWeight) *WebAPIEstimator {
|
||||
|
||||
return &WebAPIEstimator{
|
||||
apiSource: api,
|
||||
feeByBlockTarget: make(map[uint32]uint32),
|
||||
defaultFeePerKw: defaultFee,
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// EstimateFeePerKW takes in a target for the number of blocks until an initial
|
||||
// confirmation and returns the estimated fee expressed in sat/kw.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (w *WebAPIEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) {
|
||||
if numBlocks > maxBlockTarget {
|
||||
numBlocks = maxBlockTarget
|
||||
} else if numBlocks < minBlockTarget {
|
||||
return 0, fmt.Errorf("conf target of %v is too low, minimum "+
|
||||
"accepted is %v", numBlocks, minBlockTarget)
|
||||
}
|
||||
|
||||
feePerKb, err := w.getCachedFee(numBlocks)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// If the result is too low, then we'll clamp it to our current fee
|
||||
// floor.
|
||||
satPerKw := SatPerKVByte(feePerKb).FeePerKWeight()
|
||||
if satPerKw < FeePerKwFloor {
|
||||
satPerKw = FeePerKwFloor
|
||||
}
|
||||
|
||||
log.Debugf("Web API returning %v sat/kw for conf target of %v",
|
||||
int64(satPerKw), numBlocks)
|
||||
|
||||
return satPerKw, nil
|
||||
}
|
||||
|
||||
// Start signals the Estimator to start any processes or goroutines it needs
|
||||
// to perform its duty.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (w *WebAPIEstimator) Start() error {
|
||||
var err error
|
||||
w.started.Do(func() {
|
||||
log.Infof("Starting web API fee estimator")
|
||||
|
||||
w.updateFeeTicker = time.NewTicker(w.randomFeeUpdateTimeout())
|
||||
w.updateFeeEstimates()
|
||||
|
||||
w.wg.Add(1)
|
||||
go w.feeUpdateManager()
|
||||
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop stops any spawned goroutines and cleans up the resources used by the
|
||||
// fee estimator.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (w *WebAPIEstimator) Stop() error {
|
||||
w.stopped.Do(func() {
|
||||
log.Infof("Stopping web API fee estimator")
|
||||
|
||||
w.updateFeeTicker.Stop()
|
||||
|
||||
close(w.quit)
|
||||
w.wg.Wait()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelayFeePerKW returns the minimum fee rate required for transactions to be
|
||||
// relayed.
|
||||
//
|
||||
// NOTE: This method is part of the Estimator interface.
|
||||
func (w *WebAPIEstimator) RelayFeePerKW() SatPerKWeight {
|
||||
return FeePerKwFloor
|
||||
}
|
||||
|
||||
// randomFeeUpdateTimeout returns a random timeout between minFeeUpdateTimeout
|
||||
// and maxFeeUpdateTimeout that will be used to determine how often the Estimator
|
||||
// should retrieve fresh fees from its API.
|
||||
func (w *WebAPIEstimator) randomFeeUpdateTimeout() time.Duration {
|
||||
lower := int64(minFeeUpdateTimeout)
|
||||
upper := int64(maxFeeUpdateTimeout)
|
||||
return time.Duration(prand.Int63n(upper-lower) + lower)
|
||||
}
|
||||
|
||||
// getCachedFee takes in a target for the number of blocks until an initial
|
||||
// confirmation and returns an estimated fee (if one was returned by the API). If
|
||||
// the fee was not previously cached, we cache it here.
|
||||
func (w *WebAPIEstimator) getCachedFee(numBlocks uint32) (uint32, error) {
|
||||
w.feesMtx.Lock()
|
||||
defer w.feesMtx.Unlock()
|
||||
|
||||
// Search our cached fees for the desired block target. If the target is
|
||||
// not cached, then attempt to extrapolate it from the next lowest target
|
||||
// that *is* cached. If we successfully extrapolate, then cache the
|
||||
// target's fee.
|
||||
for target := numBlocks; target >= minBlockTarget; target-- {
|
||||
fee, ok := w.feeByBlockTarget[target]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
_, ok = w.feeByBlockTarget[numBlocks]
|
||||
if !ok {
|
||||
w.feeByBlockTarget[numBlocks] = fee
|
||||
}
|
||||
return fee, nil
|
||||
}
|
||||
return 0, fmt.Errorf("web API does not include a fee estimation for "+
|
||||
"block target of %v", numBlocks)
|
||||
}
|
||||
|
||||
// updateFeeEstimates re-queries the API for fresh fees and caches them.
|
||||
func (w *WebAPIEstimator) updateFeeEstimates() {
|
||||
// Rather than use the default http.Client, we'll make a custom one
|
||||
// which will allow us to control how long we'll wait to read the
|
||||
// response from the service. This way, if the service is down or
|
||||
// overloaded, we can exit early and use our default fee.
|
||||
netTransport := &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 5 * time.Second,
|
||||
}
|
||||
netClient := &http.Client{
|
||||
Timeout: time.Second * 10,
|
||||
Transport: netTransport,
|
||||
}
|
||||
|
||||
// With the client created, we'll query the API source to fetch the URL
|
||||
// that we should use to query for the fee estimation.
|
||||
targetURL := w.apiSource.GenQueryURL()
|
||||
resp, err := netClient.Get(targetURL)
|
||||
if err != nil {
|
||||
log.Errorf("unable to query web api for fee response: %v",
|
||||
err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Once we've obtained the response, we'll instruct the WebAPIFeeSource
|
||||
// to parse out the body to obtain our final result.
|
||||
feesByBlockTarget, err := w.apiSource.ParseResponse(resp.Body)
|
||||
if err != nil {
|
||||
log.Errorf("unable to query web api for fee response: %v",
|
||||
err)
|
||||
return
|
||||
}
|
||||
|
||||
w.feesMtx.Lock()
|
||||
w.feeByBlockTarget = feesByBlockTarget
|
||||
w.feesMtx.Unlock()
|
||||
}
|
||||
|
||||
// feeUpdateManager updates the fee estimates whenever a new block comes in.
|
||||
func (w *WebAPIEstimator) feeUpdateManager() {
|
||||
defer w.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-w.updateFeeTicker.C:
|
||||
w.updateFeeEstimates()
|
||||
case <-w.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure that WebAPIEstimator implements the
|
||||
// Estimator interface.
|
||||
var _ Estimator = (*WebAPIEstimator)(nil)
|
||||
29
vendor/github.com/lightningnetwork/lnd/lnwallet/chainfee/log.go
generated
vendored
Normal file
29
vendor/github.com/lightningnetwork/lnd/lnwallet/chainfee/log.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package chainfee
|
||||
|
||||
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("CFEE", 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
|
||||
}
|
||||
58
vendor/github.com/lightningnetwork/lnd/lnwallet/chainfee/rates.go
generated
vendored
Normal file
58
vendor/github.com/lightningnetwork/lnd/lnwallet/chainfee/rates.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package chainfee
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// FeePerKwFloor is the lowest fee rate in sat/kw that we should use for
|
||||
// estimating transaction fees before signing.
|
||||
FeePerKwFloor SatPerKWeight = 253
|
||||
|
||||
// AbsoluteFeePerKwFloor is the lowest fee rate in sat/kw of a
|
||||
// transaction that we should ever _create_. This is the the equivalent
|
||||
// of 1 sat/byte in sat/kw.
|
||||
AbsoluteFeePerKwFloor SatPerKWeight = 250
|
||||
)
|
||||
|
||||
// SatPerKVByte represents a fee rate in sat/kb.
|
||||
type SatPerKVByte btcutil.Amount
|
||||
|
||||
// FeeForVSize calculates the fee resulting from this fee rate and the given
|
||||
// vsize in vbytes.
|
||||
func (s SatPerKVByte) FeeForVSize(vbytes int64) btcutil.Amount {
|
||||
return btcutil.Amount(s) * btcutil.Amount(vbytes) / 1000
|
||||
}
|
||||
|
||||
// FeePerKWeight converts the current fee rate from sat/kb to sat/kw.
|
||||
func (s SatPerKVByte) FeePerKWeight() SatPerKWeight {
|
||||
return SatPerKWeight(s / blockchain.WitnessScaleFactor)
|
||||
}
|
||||
|
||||
// String returns a human-readable string of the fee rate.
|
||||
func (s SatPerKVByte) String() string {
|
||||
return fmt.Sprintf("%v sat/kb", int64(s))
|
||||
}
|
||||
|
||||
// SatPerKWeight represents a fee rate in sat/kw.
|
||||
type SatPerKWeight btcutil.Amount
|
||||
|
||||
// FeeForWeight calculates the fee resulting from this fee rate and the given
|
||||
// weight in weight units (wu).
|
||||
func (s SatPerKWeight) FeeForWeight(wu int64) btcutil.Amount {
|
||||
// The resulting fee is rounded down, as specified in BOLT#03.
|
||||
return btcutil.Amount(s) * btcutil.Amount(wu) / 1000
|
||||
}
|
||||
|
||||
// FeePerKVByte converts the current fee rate from sat/kw to sat/kb.
|
||||
func (s SatPerKWeight) FeePerKVByte() SatPerKVByte {
|
||||
return SatPerKVByte(s * blockchain.WitnessScaleFactor)
|
||||
}
|
||||
|
||||
// String returns a human-readable string of the fee rate.
|
||||
func (s SatPerKWeight) String() string {
|
||||
return fmt.Sprintf("%v sat/kw", int64(s))
|
||||
}
|
||||
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)
|
||||
6700
vendor/github.com/lightningnetwork/lnd/lnwallet/channel.go
generated
vendored
Normal file
6700
vendor/github.com/lightningnetwork/lnd/lnwallet/channel.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
205
vendor/github.com/lightningnetwork/lnd/lnwallet/chanvalidate/validate.go
generated
vendored
Normal file
205
vendor/github.com/lightningnetwork/lnd/lnwallet/chanvalidate/validate.go
generated
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
package chanvalidate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidOutPoint is returned when the ChanLocator is unable to
|
||||
// find the target outpoint.
|
||||
ErrInvalidOutPoint = fmt.Errorf("output meant to create channel cannot " +
|
||||
"be found")
|
||||
|
||||
// ErrWrongPkScript is returned when the alleged funding transaction is
|
||||
// found to have an incorrect pkSript.
|
||||
ErrWrongPkScript = fmt.Errorf("wrong pk script")
|
||||
|
||||
// ErrInvalidSize is returned when the alleged funding transaction
|
||||
// output has the wrong size (channel capacity).
|
||||
ErrInvalidSize = fmt.Errorf("channel has wrong size")
|
||||
)
|
||||
|
||||
// ErrScriptValidateError is returned when Script VM validation fails for an
|
||||
// alleged channel output.
|
||||
type ErrScriptValidateError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// Error returns a human readable string describing the error.
|
||||
func (e *ErrScriptValidateError) Error() string {
|
||||
return fmt.Sprintf("script validation failed: %v", e.err)
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying wrapped VM execution failure error.
|
||||
func (e *ErrScriptValidateError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// ChanLocator abstracts away obtaining the output that created the channel, as
|
||||
// well as validating its existence given the funding transaction. We need
|
||||
// this as there are several ways (outpoint, short chan ID) to identify the
|
||||
// output of a channel given the funding transaction.
|
||||
type ChanLocator interface {
|
||||
// Locate attempts to locate the funding output within the funding
|
||||
// transaction. It also returns the final out point of the channel
|
||||
// which uniquely identifies the output which creates the channel. If
|
||||
// the target output cannot be found, or cannot exist on the funding
|
||||
// transaction, then an error is to be returned.
|
||||
Locate(*wire.MsgTx) (*wire.TxOut, *wire.OutPoint, error)
|
||||
}
|
||||
|
||||
// OutPointChanLocator is an implementation of the ChanLocator that can be used
|
||||
// when one already knows the expected chan point.
|
||||
type OutPointChanLocator struct {
|
||||
// ChanPoint is the expected chan point.
|
||||
ChanPoint wire.OutPoint
|
||||
}
|
||||
|
||||
// Locate attempts to locate the funding output within the passed funding
|
||||
// transaction.
|
||||
//
|
||||
// NOTE: Part of the ChanLocator interface.
|
||||
func (o *OutPointChanLocator) Locate(fundingTx *wire.MsgTx) (
|
||||
*wire.TxOut, *wire.OutPoint, error) {
|
||||
|
||||
// If the expected index is greater than the amount of output in the
|
||||
// transaction, then we'll reject this channel as it's invalid.
|
||||
if int(o.ChanPoint.Index) >= len(fundingTx.TxOut) {
|
||||
return nil, nil, ErrInvalidOutPoint
|
||||
}
|
||||
|
||||
// As an extra sanity check, we'll also ensure the txid hash matches.
|
||||
fundingHash := fundingTx.TxHash()
|
||||
if !bytes.Equal(fundingHash[:], o.ChanPoint.Hash[:]) {
|
||||
return nil, nil, ErrInvalidOutPoint
|
||||
}
|
||||
|
||||
return fundingTx.TxOut[o.ChanPoint.Index], &o.ChanPoint, nil
|
||||
}
|
||||
|
||||
// ShortChanIDChanLocator is an implementation of the ChanLocator that can be
|
||||
// used when one only knows the short channel ID of a channel. This should be
|
||||
// used in contexts when one is verifying a 3rd party channel.
|
||||
type ShortChanIDChanLocator struct {
|
||||
// ID is the short channel ID of the target channel.
|
||||
ID lnwire.ShortChannelID
|
||||
}
|
||||
|
||||
// Locate attempts to locate the funding output within the passed funding
|
||||
// transaction.
|
||||
//
|
||||
// NOTE: Part of the ChanLocator interface.
|
||||
func (s *ShortChanIDChanLocator) Locate(fundingTx *wire.MsgTx) (
|
||||
*wire.TxOut, *wire.OutPoint, error) {
|
||||
|
||||
// If the expected index is greater than the amount of output in the
|
||||
// transaction, then we'll reject this channel as it's invalid.
|
||||
outputIndex := s.ID.TxPosition
|
||||
if int(outputIndex) >= len(fundingTx.TxOut) {
|
||||
return nil, nil, ErrInvalidOutPoint
|
||||
}
|
||||
|
||||
chanPoint := wire.OutPoint{
|
||||
Hash: fundingTx.TxHash(),
|
||||
Index: uint32(outputIndex),
|
||||
}
|
||||
|
||||
return fundingTx.TxOut[outputIndex], &chanPoint, nil
|
||||
}
|
||||
|
||||
// CommitmentContext is optional validation context that can be passed into the
|
||||
// main Validate for self-owned channel. The information in this context allows
|
||||
// us to fully verify out initial commitment spend based on the on-chain state
|
||||
// of the funding output.
|
||||
type CommitmentContext struct {
|
||||
// Value is the known size of the channel.
|
||||
Value btcutil.Amount
|
||||
|
||||
// FullySignedCommitTx is the fully signed commitment transaction. This
|
||||
// should include a valid witness.
|
||||
FullySignedCommitTx *wire.MsgTx
|
||||
}
|
||||
|
||||
// Context is the main validation contxet. For a given channel, all fields but
|
||||
// the optional CommitCtx should be populated based on existing
|
||||
// known-to-be-valid parameters.
|
||||
type Context struct {
|
||||
// Locator is a concrete implementation of the ChanLocator interface.
|
||||
Locator ChanLocator
|
||||
|
||||
// MultiSigPkScript is the fully serialized witness script of the
|
||||
// multi-sig output. This is the final witness program that should be
|
||||
// found in the funding output.
|
||||
MultiSigPkScript []byte
|
||||
|
||||
// FundingTx is channel funding transaction as found confirmed in the
|
||||
// chain.
|
||||
FundingTx *wire.MsgTx
|
||||
|
||||
// CommitCtx is an optional additional set of validation context
|
||||
// required to validate a self-owned channel. If present, then a full
|
||||
// Script VM validation will be performed.
|
||||
CommitCtx *CommitmentContext
|
||||
}
|
||||
|
||||
// Validate given the specified context, this function validates that the
|
||||
// alleged channel is well formed, and spendable (if the optional CommitCtx is
|
||||
// specified). If this method returns an error, then the alleged channel is
|
||||
// invalid and should be abandoned immediately.
|
||||
func Validate(ctx *Context) (*wire.OutPoint, error) {
|
||||
// First, we'll attempt to locate the target outpoint in the funding
|
||||
// transaction. If this returns an error, then we know that the
|
||||
// outpoint doesn't actually exist, so we'll exit early.
|
||||
fundingOutput, chanPoint, err := ctx.Locator.Locate(
|
||||
ctx.FundingTx,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The scripts should match up exactly, otherwise the channel is
|
||||
// invalid.
|
||||
fundingScript := fundingOutput.PkScript
|
||||
if !bytes.Equal(ctx.MultiSigPkScript, fundingScript) {
|
||||
return nil, ErrWrongPkScript
|
||||
}
|
||||
|
||||
// If there's no commitment context, then we're done here as this is a
|
||||
// 3rd party channel.
|
||||
if ctx.CommitCtx == nil {
|
||||
return chanPoint, nil
|
||||
}
|
||||
|
||||
// Now that we know this is our channel, we'll verify the amount of the
|
||||
// created output against our expected size of the channel.
|
||||
fundingValue := fundingOutput.Value
|
||||
if btcutil.Amount(fundingValue) != ctx.CommitCtx.Value {
|
||||
return nil, ErrInvalidSize
|
||||
}
|
||||
|
||||
// If we reach this point, then all other checks have succeeded, so
|
||||
// we'll now attempt a full Script VM execution to ensure that we're
|
||||
// able to close the channel using this initial state.
|
||||
vm, err := txscript.NewEngine(
|
||||
ctx.MultiSigPkScript, ctx.CommitCtx.FullySignedCommitTx,
|
||||
0, txscript.StandardVerifyFlags, nil, nil, fundingValue,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, we'll attempt to verify our full spend, if this fails then
|
||||
// the channel is definitely invalid.
|
||||
err = vm.Execute()
|
||||
if err != nil {
|
||||
return nil, &ErrScriptValidateError{err: err}
|
||||
}
|
||||
|
||||
return chanPoint, nil
|
||||
}
|
||||
114
vendor/github.com/lightningnetwork/lnd/lnwallet/commit_sort.go
generated
vendored
Normal file
114
vendor/github.com/lightningnetwork/lnd/lnwallet/commit_sort.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// InPlaceCommitSort performs an in-place sort of a commitment transaction,
|
||||
// given an unsorted transaction and a list of CLTV values for the HTLCs.
|
||||
//
|
||||
// The sort applied is a modified BIP69 sort, that uses the CLTV values of HTLCs
|
||||
// as a tie breaker in case two HTLC outputs have an identical amount and
|
||||
// pkscript. The pkscripts can be the same if they share the same payment hash,
|
||||
// but since the CLTV is enforced via the nLockTime of the second-layer
|
||||
// transactions, the script does not directly commit to them. Instead, the CLTVs
|
||||
// must be supplied separately to act as a tie-breaker, otherwise we may produce
|
||||
// invalid HTLC signatures if the receiver produces an alternative ordering
|
||||
// during verification.
|
||||
//
|
||||
// NOTE: Commitment outputs should have a 0 CLTV corresponding to their index on
|
||||
// the unsorted commitment transaction.
|
||||
func InPlaceCommitSort(tx *wire.MsgTx, cltvs []uint32) {
|
||||
if len(tx.TxOut) != len(cltvs) {
|
||||
panic("output and cltv list size mismatch")
|
||||
}
|
||||
|
||||
sort.Sort(sortableInputSlice(tx.TxIn))
|
||||
sort.Sort(sortableCommitOutputSlice{tx.TxOut, cltvs})
|
||||
}
|
||||
|
||||
// sortableInputSlice is a slice of transaction inputs that supports sorting via
|
||||
// BIP69.
|
||||
type sortableInputSlice []*wire.TxIn
|
||||
|
||||
// Len returns the length of the sortableInputSlice.
|
||||
//
|
||||
// NOTE: Part of the sort.Interface interface.
|
||||
func (s sortableInputSlice) Len() int { return len(s) }
|
||||
|
||||
// Swap exchanges the position of inputs i and j.
|
||||
//
|
||||
// NOTE: Part of the sort.Interface interface.
|
||||
func (s sortableInputSlice) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less is the BIP69 input comparison function. The sort is first applied on
|
||||
// input hash (reversed / rpc-style), then index. This logic is copied from
|
||||
// btcutil/txsort.
|
||||
//
|
||||
// NOTE: Part of the sort.Interface interface.
|
||||
func (s sortableInputSlice) Less(i, j int) bool {
|
||||
// Input hashes are the same, so compare the index.
|
||||
ihash := s[i].PreviousOutPoint.Hash
|
||||
jhash := s[j].PreviousOutPoint.Hash
|
||||
if ihash == jhash {
|
||||
return s[i].PreviousOutPoint.Index < s[j].PreviousOutPoint.Index
|
||||
}
|
||||
|
||||
// At this point, the hashes are not equal, so reverse them to
|
||||
// big-endian and return the result of the comparison.
|
||||
const hashSize = chainhash.HashSize
|
||||
for b := 0; b < hashSize/2; b++ {
|
||||
ihash[b], ihash[hashSize-1-b] = ihash[hashSize-1-b], ihash[b]
|
||||
jhash[b], jhash[hashSize-1-b] = jhash[hashSize-1-b], jhash[b]
|
||||
}
|
||||
return bytes.Compare(ihash[:], jhash[:]) == -1
|
||||
}
|
||||
|
||||
// sortableCommitOutputSlice is a slice of transaction outputs on a commitment
|
||||
// transaction and the corresponding CLTV values of any HTLCs. Commitment
|
||||
// outputs should have a CLTV of 0 and the same index in cltvs.
|
||||
type sortableCommitOutputSlice struct {
|
||||
txouts []*wire.TxOut
|
||||
cltvs []uint32
|
||||
}
|
||||
|
||||
// Len returns the length of the sortableCommitOutputSlice.
|
||||
//
|
||||
// NOTE: Part of the sort.Interface interface.
|
||||
func (s sortableCommitOutputSlice) Len() int {
|
||||
return len(s.txouts)
|
||||
}
|
||||
|
||||
// Swap exchanges the position of outputs i and j, as well as their
|
||||
// corresponding CLTV values.
|
||||
//
|
||||
// NOTE: Part of the sort.Interface interface.
|
||||
func (s sortableCommitOutputSlice) Swap(i, j int) {
|
||||
s.txouts[i], s.txouts[j] = s.txouts[j], s.txouts[i]
|
||||
s.cltvs[i], s.cltvs[j] = s.cltvs[j], s.cltvs[i]
|
||||
}
|
||||
|
||||
// Less is a modified BIP69 output comparison, that sorts based on value, then
|
||||
// pkscript, then CLTV value.
|
||||
//
|
||||
// NOTE: Part of the sort.Interface interface.
|
||||
func (s sortableCommitOutputSlice) Less(i, j int) bool {
|
||||
outi, outj := s.txouts[i], s.txouts[j]
|
||||
|
||||
if outi.Value != outj.Value {
|
||||
return outi.Value < outj.Value
|
||||
}
|
||||
|
||||
pkScriptCmp := bytes.Compare(outi.PkScript, outj.PkScript)
|
||||
if pkScriptCmp != 0 {
|
||||
return pkScriptCmp < 0
|
||||
}
|
||||
|
||||
return s.cltvs[i] < s.cltvs[j]
|
||||
}
|
||||
774
vendor/github.com/lightningnetwork/lnd/lnwallet/commitment.go
generated
vendored
Normal file
774
vendor/github.com/lightningnetwork/lnd/lnwallet/commitment.go
generated
vendored
Normal file
@@ -0,0 +1,774 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// anchorSize is the constant anchor output size.
|
||||
const anchorSize = btcutil.Amount(330)
|
||||
|
||||
// CommitmentKeyRing holds all derived keys needed to construct commitment and
|
||||
// HTLC transactions. The keys are derived differently depending whether the
|
||||
// commitment transaction is ours or the remote peer's. Private keys associated
|
||||
// with each key may belong to the commitment owner or the "other party" which
|
||||
// is referred to in the field comments, regardless of which is local and which
|
||||
// is remote.
|
||||
type CommitmentKeyRing struct {
|
||||
// CommitPoint is the "per commitment point" used to derive the tweak
|
||||
// for each base point.
|
||||
CommitPoint *btcec.PublicKey
|
||||
|
||||
// LocalCommitKeyTweak is the tweak used to derive the local public key
|
||||
// from the local payment base point or the local private key from the
|
||||
// base point secret. This may be included in a SignDescriptor to
|
||||
// generate signatures for the local payment key.
|
||||
//
|
||||
// NOTE: This will always refer to "our" local key, regardless of
|
||||
// whether this is our commit or not.
|
||||
LocalCommitKeyTweak []byte
|
||||
|
||||
// TODO(roasbeef): need delay tweak as well?
|
||||
|
||||
// LocalHtlcKeyTweak is the tweak used to derive the local HTLC key
|
||||
// from the local HTLC base point. This value is needed in order to
|
||||
// derive the final key used within the HTLC scripts in the commitment
|
||||
// transaction.
|
||||
//
|
||||
// NOTE: This will always refer to "our" local HTLC key, regardless of
|
||||
// whether this is our commit or not.
|
||||
LocalHtlcKeyTweak []byte
|
||||
|
||||
// LocalHtlcKey is the key that will be used in any clause paying to
|
||||
// our node of any HTLC scripts within the commitment transaction for
|
||||
// this key ring set.
|
||||
//
|
||||
// NOTE: This will always refer to "our" local HTLC key, regardless of
|
||||
// whether this is our commit or not.
|
||||
LocalHtlcKey *btcec.PublicKey
|
||||
|
||||
// RemoteHtlcKey is the key that will be used in clauses within the
|
||||
// HTLC script that send money to the remote party.
|
||||
//
|
||||
// NOTE: This will always refer to "their" remote HTLC key, regardless
|
||||
// of whether this is our commit or not.
|
||||
RemoteHtlcKey *btcec.PublicKey
|
||||
|
||||
// ToLocalKey is the commitment transaction owner's key which is
|
||||
// included in HTLC success and timeout transaction scripts. This is
|
||||
// the public key used for the to_local output of the commitment
|
||||
// transaction.
|
||||
//
|
||||
// NOTE: Who's key this is depends on the current perspective. If this
|
||||
// is our commitment this will be our key.
|
||||
ToLocalKey *btcec.PublicKey
|
||||
|
||||
// ToRemoteKey is the non-owner's payment key in the commitment tx.
|
||||
// This is the key used to generate the to_remote output within the
|
||||
// commitment transaction.
|
||||
//
|
||||
// NOTE: Who's key this is depends on the current perspective. If this
|
||||
// is our commitment this will be their key.
|
||||
ToRemoteKey *btcec.PublicKey
|
||||
|
||||
// RevocationKey is the key that can be used by the other party to
|
||||
// redeem outputs from a revoked commitment transaction if it were to
|
||||
// be published.
|
||||
//
|
||||
// NOTE: Who can sign for this key depends on the current perspective.
|
||||
// If this is our commitment, it means the remote node can sign for
|
||||
// this key in case of a breach.
|
||||
RevocationKey *btcec.PublicKey
|
||||
}
|
||||
|
||||
// DeriveCommitmentKeys generates a new commitment key set using the base points
|
||||
// and commitment point. The keys are derived differently depending on the type
|
||||
// of channel, and whether the commitment transaction is ours or the remote
|
||||
// peer's.
|
||||
func DeriveCommitmentKeys(commitPoint *btcec.PublicKey,
|
||||
isOurCommit bool, chanType channeldb.ChannelType,
|
||||
localChanCfg, remoteChanCfg *channeldb.ChannelConfig) *CommitmentKeyRing {
|
||||
|
||||
tweaklessCommit := chanType.IsTweakless()
|
||||
|
||||
// Depending on if this is our commit or not, we'll choose the correct
|
||||
// base point.
|
||||
localBasePoint := localChanCfg.PaymentBasePoint
|
||||
if isOurCommit {
|
||||
localBasePoint = localChanCfg.DelayBasePoint
|
||||
}
|
||||
|
||||
// First, we'll derive all the keys that don't depend on the context of
|
||||
// whose commitment transaction this is.
|
||||
keyRing := &CommitmentKeyRing{
|
||||
CommitPoint: commitPoint,
|
||||
|
||||
LocalCommitKeyTweak: input.SingleTweakBytes(
|
||||
commitPoint, localBasePoint.PubKey,
|
||||
),
|
||||
LocalHtlcKeyTweak: input.SingleTweakBytes(
|
||||
commitPoint, localChanCfg.HtlcBasePoint.PubKey,
|
||||
),
|
||||
LocalHtlcKey: input.TweakPubKey(
|
||||
localChanCfg.HtlcBasePoint.PubKey, commitPoint,
|
||||
),
|
||||
RemoteHtlcKey: input.TweakPubKey(
|
||||
remoteChanCfg.HtlcBasePoint.PubKey, commitPoint,
|
||||
),
|
||||
}
|
||||
|
||||
// We'll now compute the to_local, to_remote, and revocation key based
|
||||
// on the current commitment point. All keys are tweaked each state in
|
||||
// order to ensure the keys from each state are unlinkable. To create
|
||||
// the revocation key, we take the opposite party's revocation base
|
||||
// point and combine that with the current commitment point.
|
||||
var (
|
||||
toLocalBasePoint *btcec.PublicKey
|
||||
toRemoteBasePoint *btcec.PublicKey
|
||||
revocationBasePoint *btcec.PublicKey
|
||||
)
|
||||
if isOurCommit {
|
||||
toLocalBasePoint = localChanCfg.DelayBasePoint.PubKey
|
||||
toRemoteBasePoint = remoteChanCfg.PaymentBasePoint.PubKey
|
||||
revocationBasePoint = remoteChanCfg.RevocationBasePoint.PubKey
|
||||
} else {
|
||||
toLocalBasePoint = remoteChanCfg.DelayBasePoint.PubKey
|
||||
toRemoteBasePoint = localChanCfg.PaymentBasePoint.PubKey
|
||||
revocationBasePoint = localChanCfg.RevocationBasePoint.PubKey
|
||||
}
|
||||
|
||||
// With the base points assigned, we can now derive the actual keys
|
||||
// using the base point, and the current commitment tweak.
|
||||
keyRing.ToLocalKey = input.TweakPubKey(toLocalBasePoint, commitPoint)
|
||||
keyRing.RevocationKey = input.DeriveRevocationPubkey(
|
||||
revocationBasePoint, commitPoint,
|
||||
)
|
||||
|
||||
// If this commitment should omit the tweak for the remote point, then
|
||||
// we'll use that directly, and ignore the commitPoint tweak.
|
||||
if tweaklessCommit {
|
||||
keyRing.ToRemoteKey = toRemoteBasePoint
|
||||
|
||||
// If this is not our commitment, the above ToRemoteKey will be
|
||||
// ours, and we blank out the local commitment tweak to
|
||||
// indicate that the key should not be tweaked when signing.
|
||||
if !isOurCommit {
|
||||
keyRing.LocalCommitKeyTweak = nil
|
||||
}
|
||||
} else {
|
||||
keyRing.ToRemoteKey = input.TweakPubKey(
|
||||
toRemoteBasePoint, commitPoint,
|
||||
)
|
||||
}
|
||||
|
||||
return keyRing
|
||||
}
|
||||
|
||||
// ScriptInfo holds a redeem script and hash.
|
||||
type ScriptInfo struct {
|
||||
// PkScript is the output's PkScript.
|
||||
PkScript []byte
|
||||
|
||||
// WitnessScript is the full script required to properly redeem the
|
||||
// output. This field should be set to the full script if a p2wsh
|
||||
// output is being signed. For p2wkh it should be set equal to the
|
||||
// PkScript.
|
||||
WitnessScript []byte
|
||||
}
|
||||
|
||||
// CommitScriptToRemote creates the script that will pay to the non-owner of
|
||||
// the commitment transaction, adding a delay to the script based on the
|
||||
// channel type. The second return value is the CSV deleay of the output
|
||||
// script, what must be satisfied in order to spend the output.
|
||||
func CommitScriptToRemote(chanType channeldb.ChannelType,
|
||||
key *btcec.PublicKey) (*ScriptInfo, uint32, error) {
|
||||
|
||||
// If this channel type has anchors, we derive the delayed to_remote
|
||||
// script.
|
||||
if chanType.HasAnchors() {
|
||||
script, err := input.CommitScriptToRemoteConfirmed(key)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
p2wsh, err := input.WitnessScriptHash(script)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return &ScriptInfo{
|
||||
PkScript: p2wsh,
|
||||
WitnessScript: script,
|
||||
}, 1, nil
|
||||
}
|
||||
|
||||
// Otherwise the to_remote will be a simple p2wkh.
|
||||
p2wkh, err := input.CommitScriptUnencumbered(key)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Since this is a regular P2WKH, the WitnessScipt and PkScript should
|
||||
// both be set to the script hash.
|
||||
return &ScriptInfo{
|
||||
WitnessScript: p2wkh,
|
||||
PkScript: p2wkh,
|
||||
}, 0, nil
|
||||
}
|
||||
|
||||
// HtlcSigHashType returns the sighash type to use for HTLC success and timeout
|
||||
// transactions given the channel type.
|
||||
func HtlcSigHashType(chanType channeldb.ChannelType) txscript.SigHashType {
|
||||
if chanType.HasAnchors() {
|
||||
return txscript.SigHashSingle | txscript.SigHashAnyOneCanPay
|
||||
}
|
||||
|
||||
return txscript.SigHashAll
|
||||
}
|
||||
|
||||
// HtlcSecondLevelInputSequence dictates the sequence number we must use on the
|
||||
// input to a second level HTLC transaction.
|
||||
func HtlcSecondLevelInputSequence(chanType channeldb.ChannelType) uint32 {
|
||||
if chanType.HasAnchors() {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// CommitWeight returns the base commitment weight before adding HTLCs.
|
||||
func CommitWeight(chanType channeldb.ChannelType) int64 {
|
||||
// If this commitment has anchors, it will be slightly heavier.
|
||||
if chanType.HasAnchors() {
|
||||
return input.AnchorCommitWeight
|
||||
}
|
||||
|
||||
return input.CommitWeight
|
||||
}
|
||||
|
||||
// HtlcTimeoutFee returns the fee in satoshis required for an HTLC timeout
|
||||
// transaction based on the current fee rate.
|
||||
func HtlcTimeoutFee(chanType channeldb.ChannelType,
|
||||
feePerKw chainfee.SatPerKWeight) btcutil.Amount {
|
||||
|
||||
if chanType.HasAnchors() {
|
||||
return feePerKw.FeeForWeight(input.HtlcTimeoutWeightConfirmed)
|
||||
}
|
||||
|
||||
return feePerKw.FeeForWeight(input.HtlcTimeoutWeight)
|
||||
}
|
||||
|
||||
// HtlcSuccessFee returns the fee in satoshis required for an HTLC success
|
||||
// transaction based on the current fee rate.
|
||||
func HtlcSuccessFee(chanType channeldb.ChannelType,
|
||||
feePerKw chainfee.SatPerKWeight) btcutil.Amount {
|
||||
|
||||
if chanType.HasAnchors() {
|
||||
return feePerKw.FeeForWeight(input.HtlcSuccessWeightConfirmed)
|
||||
}
|
||||
return feePerKw.FeeForWeight(input.HtlcSuccessWeight)
|
||||
}
|
||||
|
||||
// CommitScriptAnchors return the scripts to use for the local and remote
|
||||
// anchor.
|
||||
func CommitScriptAnchors(localChanCfg,
|
||||
remoteChanCfg *channeldb.ChannelConfig) (*ScriptInfo,
|
||||
*ScriptInfo, error) {
|
||||
|
||||
// Helper to create anchor ScriptInfo from key.
|
||||
anchorScript := func(key *btcec.PublicKey) (*ScriptInfo, error) {
|
||||
script, err := input.CommitScriptAnchor(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scriptHash, err := input.WitnessScriptHash(script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ScriptInfo{
|
||||
PkScript: scriptHash,
|
||||
WitnessScript: script,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get the script used for the anchor output spendable by the local
|
||||
// node.
|
||||
localAnchor, err := anchorScript(localChanCfg.MultiSigKey.PubKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// And the anchor spendable by the remote node.
|
||||
remoteAnchor, err := anchorScript(remoteChanCfg.MultiSigKey.PubKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return localAnchor, remoteAnchor, nil
|
||||
}
|
||||
|
||||
// CommitmentBuilder is a type that wraps the type of channel we are dealing
|
||||
// with, and abstracts the various ways of constructing commitment
|
||||
// transactions.
|
||||
type CommitmentBuilder struct {
|
||||
// chanState is the underlying channels's state struct, used to
|
||||
// determine the type of channel we are dealing with, and relevant
|
||||
// parameters.
|
||||
chanState *channeldb.OpenChannel
|
||||
|
||||
// obfuscator is a 48-bit state hint that's used to obfuscate the
|
||||
// current state number on the commitment transactions.
|
||||
obfuscator [StateHintSize]byte
|
||||
}
|
||||
|
||||
// NewCommitmentBuilder creates a new CommitmentBuilder from chanState.
|
||||
func NewCommitmentBuilder(chanState *channeldb.OpenChannel) *CommitmentBuilder {
|
||||
// The anchor channel type MUST be tweakless.
|
||||
if chanState.ChanType.HasAnchors() && !chanState.ChanType.IsTweakless() {
|
||||
panic("invalid channel type combination")
|
||||
}
|
||||
|
||||
return &CommitmentBuilder{
|
||||
chanState: chanState,
|
||||
obfuscator: createStateHintObfuscator(chanState),
|
||||
}
|
||||
}
|
||||
|
||||
// createStateHintObfuscator derives and assigns the state hint obfuscator for
|
||||
// the channel, which is used to encode the commitment height in the sequence
|
||||
// number of commitment transaction inputs.
|
||||
func createStateHintObfuscator(state *channeldb.OpenChannel) [StateHintSize]byte {
|
||||
if state.IsInitiator {
|
||||
return DeriveStateHintObfuscator(
|
||||
state.LocalChanCfg.PaymentBasePoint.PubKey,
|
||||
state.RemoteChanCfg.PaymentBasePoint.PubKey,
|
||||
)
|
||||
}
|
||||
|
||||
return DeriveStateHintObfuscator(
|
||||
state.RemoteChanCfg.PaymentBasePoint.PubKey,
|
||||
state.LocalChanCfg.PaymentBasePoint.PubKey,
|
||||
)
|
||||
}
|
||||
|
||||
// unsignedCommitmentTx is the final commitment created from evaluating an HTLC
|
||||
// view at a given height, along with some meta data.
|
||||
type unsignedCommitmentTx struct {
|
||||
// txn is the final, unsigned commitment transaction for this view.
|
||||
txn *wire.MsgTx
|
||||
|
||||
// fee is the total fee of the commitment transaction.
|
||||
fee btcutil.Amount
|
||||
|
||||
// ourBalance is our balance on this commitment *after* subtracting
|
||||
// commitment fees and anchor outputs. This can be different than the
|
||||
// balances before creating the commitment transaction as one party must
|
||||
// pay the commitment fee.
|
||||
ourBalance lnwire.MilliSatoshi
|
||||
|
||||
// theirBalance is their balance of this commitment *after* subtracting
|
||||
// commitment fees and anchor outputs. This can be different than the
|
||||
// balances before creating the commitment transaction as one party must
|
||||
// pay the commitment fee.
|
||||
theirBalance lnwire.MilliSatoshi
|
||||
|
||||
// cltvs is a sorted list of CLTV deltas for each HTLC on the commitment
|
||||
// transaction. Any non-htlc outputs will have a CLTV delay of zero.
|
||||
cltvs []uint32
|
||||
}
|
||||
|
||||
// createUnsignedCommitmentTx generates the unsigned commitment transaction for
|
||||
// a commitment view and returns it as part of the unsignedCommitmentTx. The
|
||||
// passed in balances should be balances *before* subtracting any commitment
|
||||
// fees, but after anchor outputs.
|
||||
func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
||||
theirBalance lnwire.MilliSatoshi, isOurs bool,
|
||||
feePerKw chainfee.SatPerKWeight, height uint64,
|
||||
filteredHTLCView *htlcView,
|
||||
keyRing *CommitmentKeyRing) (*unsignedCommitmentTx, error) {
|
||||
|
||||
dustLimit := cb.chanState.LocalChanCfg.DustLimit
|
||||
if !isOurs {
|
||||
dustLimit = cb.chanState.RemoteChanCfg.DustLimit
|
||||
}
|
||||
|
||||
numHTLCs := int64(0)
|
||||
for _, htlc := range filteredHTLCView.ourUpdates {
|
||||
if htlcIsDust(
|
||||
cb.chanState.ChanType, false, isOurs, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
numHTLCs++
|
||||
}
|
||||
for _, htlc := range filteredHTLCView.theirUpdates {
|
||||
if htlcIsDust(
|
||||
cb.chanState.ChanType, true, isOurs, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
numHTLCs++
|
||||
}
|
||||
|
||||
// Next, we'll calculate the fee for the commitment transaction based
|
||||
// on its total weight. Once we have the total weight, we'll multiply
|
||||
// by the current fee-per-kw, then divide by 1000 to get the proper
|
||||
// fee.
|
||||
totalCommitWeight := CommitWeight(cb.chanState.ChanType) +
|
||||
input.HTLCWeight*numHTLCs
|
||||
|
||||
// With the weight known, we can now calculate the commitment fee,
|
||||
// ensuring that we account for any dust outputs trimmed above.
|
||||
commitFee := feePerKw.FeeForWeight(totalCommitWeight)
|
||||
commitFeeMSat := lnwire.NewMSatFromSatoshis(commitFee)
|
||||
|
||||
// Currently, within the protocol, the initiator always pays the fees.
|
||||
// So we'll subtract the fee amount from the balance of the current
|
||||
// initiator. If the initiator is unable to pay the fee fully, then
|
||||
// their entire output is consumed.
|
||||
switch {
|
||||
case cb.chanState.IsInitiator && commitFee > ourBalance.ToSatoshis():
|
||||
ourBalance = 0
|
||||
|
||||
case cb.chanState.IsInitiator:
|
||||
ourBalance -= commitFeeMSat
|
||||
|
||||
case !cb.chanState.IsInitiator && commitFee > theirBalance.ToSatoshis():
|
||||
theirBalance = 0
|
||||
|
||||
case !cb.chanState.IsInitiator:
|
||||
theirBalance -= commitFeeMSat
|
||||
}
|
||||
|
||||
var (
|
||||
commitTx *wire.MsgTx
|
||||
err error
|
||||
)
|
||||
|
||||
// Depending on whether the transaction is ours or not, we call
|
||||
// CreateCommitTx with parameters matching the perspective, to generate
|
||||
// a new commitment transaction with all the latest unsettled/un-timed
|
||||
// out HTLCs.
|
||||
if isOurs {
|
||||
commitTx, err = CreateCommitTx(
|
||||
cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing,
|
||||
&cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg,
|
||||
ourBalance.ToSatoshis(), theirBalance.ToSatoshis(),
|
||||
numHTLCs,
|
||||
)
|
||||
} else {
|
||||
commitTx, err = CreateCommitTx(
|
||||
cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing,
|
||||
&cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg,
|
||||
theirBalance.ToSatoshis(), ourBalance.ToSatoshis(),
|
||||
numHTLCs,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We'll now add all the HTLC outputs to the commitment transaction.
|
||||
// Each output includes an off-chain 2-of-2 covenant clause, so we'll
|
||||
// need the objective local/remote keys for this particular commitment
|
||||
// as well. For any non-dust HTLCs that are manifested on the commitment
|
||||
// transaction, we'll also record its CLTV which is required to sort the
|
||||
// commitment transaction below. The slice is initially sized to the
|
||||
// number of existing outputs, since any outputs already added are
|
||||
// commitment outputs and should correspond to zero values for the
|
||||
// purposes of sorting.
|
||||
cltvs := make([]uint32, len(commitTx.TxOut))
|
||||
for _, htlc := range filteredHTLCView.ourUpdates {
|
||||
if htlcIsDust(
|
||||
cb.chanState.ChanType, false, isOurs, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
err := addHTLC(
|
||||
commitTx, isOurs, false, htlc, keyRing,
|
||||
cb.chanState.ChanType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cltvs = append(cltvs, htlc.Timeout)
|
||||
}
|
||||
for _, htlc := range filteredHTLCView.theirUpdates {
|
||||
if htlcIsDust(
|
||||
cb.chanState.ChanType, true, isOurs, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
err := addHTLC(
|
||||
commitTx, isOurs, true, htlc, keyRing,
|
||||
cb.chanState.ChanType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cltvs = append(cltvs, htlc.Timeout)
|
||||
}
|
||||
|
||||
// Set the state hint of the commitment transaction to facilitate
|
||||
// quickly recovering the necessary penalty state in the case of an
|
||||
// uncooperative broadcast.
|
||||
err = SetStateNumHint(commitTx, height, cb.obfuscator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sort the transactions according to the agreed upon canonical
|
||||
// ordering. This lets us skip sending the entire transaction over,
|
||||
// instead we'll just send signatures.
|
||||
InPlaceCommitSort(commitTx, cltvs)
|
||||
|
||||
// Next, we'll ensure that we don't accidentally create a commitment
|
||||
// transaction which would be invalid by consensus.
|
||||
uTx := btcutil.NewTx(commitTx)
|
||||
if err := blockchain.CheckTransactionSanity(uTx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, we'll assert that were not attempting to draw more out of
|
||||
// the channel that was originally placed within it.
|
||||
var totalOut btcutil.Amount
|
||||
for _, txOut := range commitTx.TxOut {
|
||||
totalOut += btcutil.Amount(txOut.Value)
|
||||
}
|
||||
if totalOut > cb.chanState.Capacity {
|
||||
return nil, fmt.Errorf("height=%v, for ChannelPoint(%v) "+
|
||||
"attempts to consume %v while channel capacity is %v",
|
||||
height, cb.chanState.FundingOutpoint,
|
||||
totalOut, cb.chanState.Capacity)
|
||||
}
|
||||
|
||||
return &unsignedCommitmentTx{
|
||||
txn: commitTx,
|
||||
fee: commitFee,
|
||||
ourBalance: ourBalance,
|
||||
theirBalance: theirBalance,
|
||||
cltvs: cltvs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateCommitTx creates a commitment transaction, spending from specified
|
||||
// funding output. The commitment transaction contains two outputs: one local
|
||||
// output paying to the "owner" of the commitment transaction which can be
|
||||
// spent after a relative block delay or revocation event, and a remote output
|
||||
// paying the counterparty within the channel, which can be spent immediately
|
||||
// or after a delay depending on the commitment type..
|
||||
func CreateCommitTx(chanType channeldb.ChannelType,
|
||||
fundingOutput wire.TxIn, keyRing *CommitmentKeyRing,
|
||||
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
|
||||
amountToLocal, amountToRemote btcutil.Amount,
|
||||
numHTLCs int64) (*wire.MsgTx, error) {
|
||||
|
||||
// First, we create the script for the delayed "pay-to-self" output.
|
||||
// This output has 2 main redemption clauses: either we can redeem the
|
||||
// output after a relative block delay, or the remote node can claim
|
||||
// the funds with the revocation key if we broadcast a revoked
|
||||
// commitment transaction.
|
||||
toLocalRedeemScript, err := input.CommitScriptToSelf(
|
||||
uint32(localChanCfg.CsvDelay), keyRing.ToLocalKey,
|
||||
keyRing.RevocationKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toLocalScriptHash, err := input.WitnessScriptHash(
|
||||
toLocalRedeemScript,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Next, we create the script paying to the remote.
|
||||
toRemoteScript, _, err := CommitScriptToRemote(
|
||||
chanType, keyRing.ToRemoteKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now that both output scripts have been created, we can finally create
|
||||
// the transaction itself. We use a transaction version of 2 since CSV
|
||||
// will fail unless the tx version is >= 2.
|
||||
commitTx := wire.NewMsgTx(2)
|
||||
commitTx.AddTxIn(&fundingOutput)
|
||||
|
||||
// Avoid creating dust outputs within the commitment transaction.
|
||||
localOutput := amountToLocal >= localChanCfg.DustLimit
|
||||
if localOutput {
|
||||
commitTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: toLocalScriptHash,
|
||||
Value: int64(amountToLocal),
|
||||
})
|
||||
}
|
||||
|
||||
remoteOutput := amountToRemote >= localChanCfg.DustLimit
|
||||
if remoteOutput {
|
||||
commitTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: toRemoteScript.PkScript,
|
||||
Value: int64(amountToRemote),
|
||||
})
|
||||
}
|
||||
|
||||
// If this channel type has anchors, we'll also add those.
|
||||
if chanType.HasAnchors() {
|
||||
localAnchor, remoteAnchor, err := CommitScriptAnchors(
|
||||
localChanCfg, remoteChanCfg,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add local anchor output only if we have a commitment output
|
||||
// or there are HTLCs.
|
||||
if localOutput || numHTLCs > 0 {
|
||||
commitTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: localAnchor.PkScript,
|
||||
Value: int64(anchorSize),
|
||||
})
|
||||
}
|
||||
|
||||
// Add anchor output to remote only if they have a commitment
|
||||
// output or there are HTLCs.
|
||||
if remoteOutput || numHTLCs > 0 {
|
||||
commitTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: remoteAnchor.PkScript,
|
||||
Value: int64(anchorSize),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return commitTx, nil
|
||||
}
|
||||
|
||||
// genHtlcScript generates the proper P2WSH public key scripts for the HTLC
|
||||
// output modified by two-bits denoting if this is an incoming HTLC, and if the
|
||||
// HTLC is being applied to their commitment transaction or ours.
|
||||
func genHtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool,
|
||||
timeout uint32, rHash [32]byte,
|
||||
keyRing *CommitmentKeyRing) ([]byte, []byte, error) {
|
||||
|
||||
var (
|
||||
witnessScript []byte
|
||||
err error
|
||||
)
|
||||
|
||||
// Choose scripts based on channel type.
|
||||
confirmedHtlcSpends := false
|
||||
if chanType.HasAnchors() {
|
||||
confirmedHtlcSpends = true
|
||||
}
|
||||
|
||||
// Generate the proper redeem scripts for the HTLC output modified by
|
||||
// two-bits denoting if this is an incoming HTLC, and if the HTLC is
|
||||
// being applied to their commitment transaction or ours.
|
||||
switch {
|
||||
// The HTLC is paying to us, and being applied to our commitment
|
||||
// transaction. So we need to use the receiver's version of HTLC the
|
||||
// script.
|
||||
case isIncoming && ourCommit:
|
||||
witnessScript, err = input.ReceiverHTLCScript(
|
||||
timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
||||
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
|
||||
)
|
||||
|
||||
// We're being paid via an HTLC by the remote party, and the HTLC is
|
||||
// being added to their commitment transaction, so we use the sender's
|
||||
// version of the HTLC script.
|
||||
case isIncoming && !ourCommit:
|
||||
witnessScript, err = input.SenderHTLCScript(
|
||||
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
||||
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
|
||||
)
|
||||
|
||||
// We're sending an HTLC which is being added to our commitment
|
||||
// transaction. Therefore, we need to use the sender's version of the
|
||||
// HTLC script.
|
||||
case !isIncoming && ourCommit:
|
||||
witnessScript, err = input.SenderHTLCScript(
|
||||
keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
|
||||
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
|
||||
)
|
||||
|
||||
// Finally, we're paying the remote party via an HTLC, which is being
|
||||
// added to their commitment transaction. Therefore, we use the
|
||||
// receiver's version of the HTLC script.
|
||||
case !isIncoming && !ourCommit:
|
||||
witnessScript, err = input.ReceiverHTLCScript(
|
||||
timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
|
||||
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Now that we have the redeem scripts, create the P2WSH public key
|
||||
// script for the output itself.
|
||||
htlcP2WSH, err := input.WitnessScriptHash(witnessScript)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return htlcP2WSH, witnessScript, nil
|
||||
}
|
||||
|
||||
// addHTLC adds a new HTLC to the passed commitment transaction. One of four
|
||||
// full scripts will be generated for the HTLC output depending on if the HTLC
|
||||
// is incoming and if it's being applied to our commitment transaction or that
|
||||
// of the remote node's. Additionally, in order to be able to efficiently
|
||||
// locate the added HTLC on the commitment transaction from the
|
||||
// PaymentDescriptor that generated it, the generated script is stored within
|
||||
// the descriptor itself.
|
||||
func addHTLC(commitTx *wire.MsgTx, ourCommit bool,
|
||||
isIncoming bool, paymentDesc *PaymentDescriptor,
|
||||
keyRing *CommitmentKeyRing, chanType channeldb.ChannelType) error {
|
||||
|
||||
timeout := paymentDesc.Timeout
|
||||
rHash := paymentDesc.RHash
|
||||
|
||||
p2wsh, witnessScript, err := genHtlcScript(
|
||||
chanType, isIncoming, ourCommit, timeout, rHash, keyRing,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the new HTLC outputs to the respective commitment transactions.
|
||||
amountPending := int64(paymentDesc.Amount.ToSatoshis())
|
||||
commitTx.AddTxOut(wire.NewTxOut(amountPending, p2wsh))
|
||||
|
||||
// Store the pkScript of this particular PaymentDescriptor so we can
|
||||
// quickly locate it within the commitment transaction later.
|
||||
if ourCommit {
|
||||
paymentDesc.ourPkScript = p2wsh
|
||||
paymentDesc.ourWitnessScript = witnessScript
|
||||
} else {
|
||||
paymentDesc.theirPkScript = p2wsh
|
||||
paymentDesc.theirWitnessScript = witnessScript
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
59
vendor/github.com/lightningnetwork/lnd/lnwallet/config.go
generated
vendored
Normal file
59
vendor/github.com/lightningnetwork/lnd/lnwallet/config.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
|
||||
// Config is a struct which houses configuration parameters which modify the
|
||||
// behaviour of LightningWallet.
|
||||
//
|
||||
// NOTE: The passed channeldb, and ChainNotifier should already be fully
|
||||
// initialized/started before being passed as a function argument.
|
||||
type Config struct {
|
||||
// Database is a wrapper around a namespace within boltdb reserved for
|
||||
// ln-based wallet metadata. See the 'channeldb' package for further
|
||||
// information.
|
||||
Database *channeldb.DB
|
||||
|
||||
// Notifier is used by in order to obtain notifications about funding
|
||||
// transaction reaching a specified confirmation depth, and to catch
|
||||
// counterparty's broadcasting revoked commitment states.
|
||||
Notifier chainntnfs.ChainNotifier
|
||||
|
||||
// SecretKeyRing is used by the wallet during the funding workflow
|
||||
// process to obtain keys to be used directly within contracts. Usage
|
||||
// of this interface ensures that all key derivation is itself fully
|
||||
// deterministic.
|
||||
SecretKeyRing keychain.SecretKeyRing
|
||||
|
||||
// WalletController is the core wallet, all non Lightning Network
|
||||
// specific interaction is proxied to the internal wallet.
|
||||
WalletController WalletController
|
||||
|
||||
// Signer is the wallet's current Signer implementation. This Signer is
|
||||
// used to generate signature for all inputs to potential funding
|
||||
// transactions, as well as for spends from the funding transaction to
|
||||
// update the commitment state.
|
||||
Signer input.Signer
|
||||
|
||||
// FeeEstimator is the implementation that the wallet will use for the
|
||||
// calculation of on-chain transaction fees.
|
||||
FeeEstimator chainfee.Estimator
|
||||
|
||||
// ChainIO is an instance of the BlockChainIO interface. ChainIO is
|
||||
// used to lookup the existence of outputs within the UTXO set.
|
||||
ChainIO BlockChainIO
|
||||
|
||||
// DefaultConstraints is the set of default constraints that will be
|
||||
// used for any incoming or outgoing channel reservation requests.
|
||||
DefaultConstraints channeldb.ChannelConstraints
|
||||
|
||||
// NetParams is the set of parameters that tells the wallet which chain
|
||||
// it will be operating on.
|
||||
NetParams chaincfg.Params
|
||||
}
|
||||
189
vendor/github.com/lightningnetwork/lnd/lnwallet/errors.go
generated
vendored
Normal file
189
vendor/github.com/lightningnetwork/lnd/lnwallet/errors.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// ReservationError wraps certain errors returned during channel reservation
|
||||
// that can be sent across the wire to the remote peer. Errors not being
|
||||
// ReservationErrors will not be sent to the remote in case of a failed channel
|
||||
// reservation, as they may contain private information.
|
||||
type ReservationError struct {
|
||||
error
|
||||
}
|
||||
|
||||
// A compile time check to ensure ReservationError implements the error
|
||||
// interface.
|
||||
var _ error = (*ReservationError)(nil)
|
||||
|
||||
// ErrZeroCapacity returns an error indicating the funder attempted to put zero
|
||||
// funds into the channel.
|
||||
func ErrZeroCapacity() ReservationError {
|
||||
return ReservationError{
|
||||
errors.New("zero channel funds"),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrChainMismatch returns an error indicating that the initiator tried to
|
||||
// open a channel for an unknown chain.
|
||||
func ErrChainMismatch(knownChain,
|
||||
unknownChain *chainhash.Hash) ReservationError {
|
||||
return ReservationError{
|
||||
fmt.Errorf("Unknown chain=%v. Supported chain=%v",
|
||||
unknownChain, knownChain),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrFunderBalanceDust returns an error indicating the initial balance of the
|
||||
// funder is considered dust at the current commitment fee.
|
||||
func ErrFunderBalanceDust(commitFee, funderBalance,
|
||||
minBalance int64) ReservationError {
|
||||
return ReservationError{
|
||||
fmt.Errorf("Funder balance too small (%v) with fee=%v sat, "+
|
||||
"minimum=%v sat required", funderBalance,
|
||||
commitFee, minBalance),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrCsvDelayTooLarge returns an error indicating that the CSV delay was to
|
||||
// large to be accepted, along with the current max.
|
||||
func ErrCsvDelayTooLarge(remoteDelay, maxDelay uint16) ReservationError {
|
||||
return ReservationError{
|
||||
fmt.Errorf("CSV delay too large: %v, max is %v",
|
||||
remoteDelay, maxDelay),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrChanReserveTooSmall returns an error indicating that the channel reserve
|
||||
// the remote is requiring is too small to be accepted.
|
||||
func ErrChanReserveTooSmall(reserve, dustLimit btcutil.Amount) ReservationError {
|
||||
return ReservationError{
|
||||
fmt.Errorf("channel reserve of %v sat is too small, min is %v "+
|
||||
"sat", int64(reserve), int64(dustLimit)),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrChanReserveTooLarge returns an error indicating that the chan reserve the
|
||||
// remote is requiring, is too large to be accepted.
|
||||
func ErrChanReserveTooLarge(reserve,
|
||||
maxReserve btcutil.Amount) ReservationError {
|
||||
return ReservationError{
|
||||
fmt.Errorf("Channel reserve is too large: %v sat, max "+
|
||||
"is %v sat", int64(reserve), int64(maxReserve)),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrNonZeroPushAmount is returned by a remote peer that receives a
|
||||
// FundingOpen request for a channel with non-zero push amount while
|
||||
// they have 'rejectpush' enabled.
|
||||
func ErrNonZeroPushAmount() ReservationError {
|
||||
return ReservationError{errors.New("Non-zero push amounts are disabled")}
|
||||
}
|
||||
|
||||
// ErrMinHtlcTooLarge returns an error indicating that the MinHTLC value the
|
||||
// remote required is too large to be accepted.
|
||||
func ErrMinHtlcTooLarge(minHtlc,
|
||||
maxMinHtlc lnwire.MilliSatoshi) ReservationError {
|
||||
return ReservationError{
|
||||
fmt.Errorf("Minimum HTLC value is too large: %v, max is %v",
|
||||
minHtlc, maxMinHtlc),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrMaxHtlcNumTooLarge returns an error indicating that the 'max HTLCs in
|
||||
// flight' value the remote required is too large to be accepted.
|
||||
func ErrMaxHtlcNumTooLarge(maxHtlc, maxMaxHtlc uint16) ReservationError {
|
||||
return ReservationError{
|
||||
fmt.Errorf("maxHtlcs is too large: %d, max is %d",
|
||||
maxHtlc, maxMaxHtlc),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrMaxHtlcNumTooSmall returns an error indicating that the 'max HTLCs in
|
||||
// flight' value the remote required is too small to be accepted.
|
||||
func ErrMaxHtlcNumTooSmall(maxHtlc, minMaxHtlc uint16) ReservationError {
|
||||
return ReservationError{
|
||||
fmt.Errorf("maxHtlcs is too small: %d, min is %d",
|
||||
maxHtlc, minMaxHtlc),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrMaxValueInFlightTooSmall returns an error indicating that the 'max HTLC
|
||||
// value in flight' the remote required is too small to be accepted.
|
||||
func ErrMaxValueInFlightTooSmall(maxValInFlight,
|
||||
minMaxValInFlight lnwire.MilliSatoshi) ReservationError {
|
||||
return ReservationError{
|
||||
fmt.Errorf("maxValueInFlight too small: %v, min is %v",
|
||||
maxValInFlight, minMaxValInFlight),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrNumConfsTooLarge returns an error indicating that the number of
|
||||
// confirmations required for a channel is too large.
|
||||
func ErrNumConfsTooLarge(numConfs, maxNumConfs uint32) error {
|
||||
return ReservationError{
|
||||
fmt.Errorf("minimum depth of %d is too large, max is %d",
|
||||
numConfs, maxNumConfs),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrChanTooSmall returns an error indicating that an incoming channel request
|
||||
// was too small. We'll reject any incoming channels if they're below our
|
||||
// configured value for the min channel size we'll accept.
|
||||
func ErrChanTooSmall(chanSize, minChanSize btcutil.Amount) ReservationError {
|
||||
return ReservationError{
|
||||
fmt.Errorf("chan size of %v is below min chan size of %v",
|
||||
chanSize, minChanSize),
|
||||
}
|
||||
}
|
||||
|
||||
// ErrHtlcIndexAlreadyFailed is returned when the HTLC index has already been
|
||||
// failed, but has not been committed by our commitment state.
|
||||
type ErrHtlcIndexAlreadyFailed uint64
|
||||
|
||||
// Error returns a message indicating the index that had already been failed.
|
||||
func (e ErrHtlcIndexAlreadyFailed) Error() string {
|
||||
return fmt.Sprintf("HTLC with ID %d has already been failed", e)
|
||||
}
|
||||
|
||||
// ErrHtlcIndexAlreadySettled is returned when the HTLC index has already been
|
||||
// settled, but has not been committed by our commitment state.
|
||||
type ErrHtlcIndexAlreadySettled uint64
|
||||
|
||||
// Error returns a message indicating the index that had already been settled.
|
||||
func (e ErrHtlcIndexAlreadySettled) Error() string {
|
||||
return fmt.Sprintf("HTLC with ID %d has already been settled", e)
|
||||
}
|
||||
|
||||
// ErrInvalidSettlePreimage is returned when trying to settle an HTLC, but the
|
||||
// preimage does not correspond to the payment hash.
|
||||
type ErrInvalidSettlePreimage struct {
|
||||
preimage []byte
|
||||
rhash []byte
|
||||
}
|
||||
|
||||
// Error returns an error message with the offending preimage and intended
|
||||
// payment hash.
|
||||
func (e ErrInvalidSettlePreimage) Error() string {
|
||||
return fmt.Sprintf("Invalid payment preimage %x for hash %x",
|
||||
e.preimage, e.rhash)
|
||||
}
|
||||
|
||||
// ErrUnknownHtlcIndex is returned when locally settling or failing an HTLC, but
|
||||
// the HTLC index is not known to the channel. This typically indicates that the
|
||||
// HTLC was already settled in a prior commitment.
|
||||
type ErrUnknownHtlcIndex struct {
|
||||
chanID lnwire.ShortChannelID
|
||||
index uint64
|
||||
}
|
||||
|
||||
// Error returns an error logging the channel and HTLC index that was unknown.
|
||||
func (e ErrUnknownHtlcIndex) Error() string {
|
||||
return fmt.Sprintf("No HTLC with ID %d in channel %v",
|
||||
e.index, e.chanID)
|
||||
}
|
||||
372
vendor/github.com/lightningnetwork/lnd/lnwallet/interface.go
generated
vendored
Normal file
372
vendor/github.com/lightningnetwork/lnd/lnwallet/interface.go
generated
vendored
Normal file
@@ -0,0 +1,372 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
|
||||
// AddressType is an enum-like type which denotes the possible address types
|
||||
// WalletController supports.
|
||||
type AddressType uint8
|
||||
|
||||
const (
|
||||
// UnknownAddressType represents an output with an unknown or non-standard
|
||||
// script.
|
||||
UnknownAddressType AddressType = iota
|
||||
|
||||
// WitnessPubKey represents a p2wkh address.
|
||||
WitnessPubKey
|
||||
|
||||
// NestedWitnessPubKey represents a p2sh output which is itself a
|
||||
// nested p2wkh output.
|
||||
NestedWitnessPubKey
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultPublicPassphrase is the default public passphrase used for the
|
||||
// wallet.
|
||||
DefaultPublicPassphrase = []byte("public")
|
||||
|
||||
// DefaultPrivatePassphrase is the default private passphrase used for
|
||||
// the wallet.
|
||||
DefaultPrivatePassphrase = []byte("hello")
|
||||
|
||||
// ErrDoubleSpend is returned from PublishTransaction in case the
|
||||
// tx being published is spending an output spent by a conflicting
|
||||
// transaction.
|
||||
ErrDoubleSpend = errors.New("Transaction rejected: output already spent")
|
||||
|
||||
// ErrNotMine is an error denoting that a WalletController instance is
|
||||
// unable to spend a specified output.
|
||||
ErrNotMine = errors.New("the passed output doesn't belong to the wallet")
|
||||
)
|
||||
|
||||
// ErrNoOutputs is returned if we try to create a transaction with no outputs
|
||||
// or send coins to a set of outputs that is empty.
|
||||
var ErrNoOutputs = errors.New("no outputs")
|
||||
|
||||
// Utxo is an unspent output denoted by its outpoint, and output value of the
|
||||
// original output.
|
||||
type Utxo struct {
|
||||
AddressType AddressType
|
||||
Value btcutil.Amount
|
||||
Confirmations int64
|
||||
PkScript []byte
|
||||
wire.OutPoint
|
||||
}
|
||||
|
||||
// TransactionDetail describes a transaction with either inputs which belong to
|
||||
// the wallet, or has outputs that pay to the wallet.
|
||||
type TransactionDetail struct {
|
||||
// Hash is the transaction hash of the transaction.
|
||||
Hash chainhash.Hash
|
||||
|
||||
// Value is the net value of this transaction (in satoshis) from the
|
||||
// PoV of the wallet. If this transaction purely spends from the
|
||||
// wallet's funds, then this value will be negative. Similarly, if this
|
||||
// transaction credits the wallet, then this value will be positive.
|
||||
Value btcutil.Amount
|
||||
|
||||
// NumConfirmations is the number of confirmations this transaction
|
||||
// has. If the transaction is unconfirmed, then this value will be
|
||||
// zero.
|
||||
NumConfirmations int32
|
||||
|
||||
// BlockHeight is the hash of the block which includes this
|
||||
// transaction. Unconfirmed transactions will have a nil value for this
|
||||
// field.
|
||||
BlockHash *chainhash.Hash
|
||||
|
||||
// BlockHeight is the height of the block including this transaction.
|
||||
// Unconfirmed transaction will show a height of zero.
|
||||
BlockHeight int32
|
||||
|
||||
// Timestamp is the unix timestamp of the block including this
|
||||
// transaction. If the transaction is unconfirmed, then this will be a
|
||||
// timestamp of txn creation.
|
||||
Timestamp int64
|
||||
|
||||
// TotalFees is the total fee in satoshis paid by this transaction.
|
||||
TotalFees int64
|
||||
|
||||
// DestAddresses are the destinations for a transaction
|
||||
DestAddresses []btcutil.Address
|
||||
|
||||
// RawTx returns the raw serialized transaction.
|
||||
RawTx []byte
|
||||
}
|
||||
|
||||
// TransactionSubscription is an interface which describes an object capable of
|
||||
// receiving notifications of new transaction related to the underlying wallet.
|
||||
// TODO(roasbeef): add balance updates?
|
||||
type TransactionSubscription interface {
|
||||
// ConfirmedTransactions returns a channel which will be sent on as new
|
||||
// relevant transactions are confirmed.
|
||||
ConfirmedTransactions() chan *TransactionDetail
|
||||
|
||||
// UnconfirmedTransactions returns a channel which will be sent on as
|
||||
// new relevant transactions are seen within the network.
|
||||
UnconfirmedTransactions() chan *TransactionDetail
|
||||
|
||||
// Cancel finalizes the subscription, cleaning up any resources
|
||||
// allocated.
|
||||
Cancel()
|
||||
}
|
||||
|
||||
// WalletController defines an abstract interface for controlling a local Pure
|
||||
// Go wallet, a local or remote wallet via an RPC mechanism, or possibly even
|
||||
// a daemon assisted hardware wallet. This interface serves the purpose of
|
||||
// allowing LightningWallet to be seamlessly compatible with several wallets
|
||||
// such as: uspv, btcwallet, Bitcoin Core, Electrum, etc. This interface then
|
||||
// serves as a "base wallet", with Lightning Network awareness taking place at
|
||||
// a "higher" level of abstraction. Essentially, an overlay wallet.
|
||||
// Implementors of this interface must closely adhere to the documented
|
||||
// behavior of all interface methods in order to ensure identical behavior
|
||||
// across all concrete implementations.
|
||||
type WalletController interface {
|
||||
// FetchInputInfo queries for the WalletController's knowledge of the
|
||||
// passed outpoint. If the base wallet determines this output is under
|
||||
// its control, then the original txout should be returned. Otherwise,
|
||||
// a non-nil error value of ErrNotMine should be returned instead.
|
||||
FetchInputInfo(prevOut *wire.OutPoint) (*Utxo, error)
|
||||
|
||||
// ConfirmedBalance returns the sum of all the wallet's unspent outputs
|
||||
// that have at least confs confirmations. If confs is set to zero,
|
||||
// then all unspent outputs, including those currently in the mempool
|
||||
// will be included in the final sum.
|
||||
//
|
||||
// NOTE: Only witness outputs should be included in the computation of
|
||||
// the total spendable balance of the wallet. We require this as only
|
||||
// witness inputs can be used for funding channels.
|
||||
ConfirmedBalance(confs int32) (btcutil.Amount, error)
|
||||
|
||||
// NewAddress returns the next external or internal address for the
|
||||
// wallet dictated by the value of the `change` parameter. If change is
|
||||
// true, then an internal address should be used, otherwise an external
|
||||
// address should be returned. The type of address returned is dictated
|
||||
// by the wallet's capabilities, and may be of type: p2sh, p2wkh,
|
||||
// p2wsh, etc.
|
||||
NewAddress(addrType AddressType, change bool) (btcutil.Address, error)
|
||||
|
||||
// LastUnusedAddress returns the last *unused* address known by the
|
||||
// wallet. An address is unused if it hasn't received any payments.
|
||||
// This can be useful in UIs in order to continually show the
|
||||
// "freshest" address without having to worry about "address inflation"
|
||||
// caused by continual refreshing. Similar to NewAddress it can derive
|
||||
// a specified address type. By default, this is a non-change address.
|
||||
LastUnusedAddress(addrType AddressType) (btcutil.Address, error)
|
||||
|
||||
// IsOurAddress checks if the passed address belongs to this wallet
|
||||
IsOurAddress(a btcutil.Address) bool
|
||||
|
||||
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying
|
||||
// out to the specified outputs. In the case the wallet has insufficient
|
||||
// funds, or the outputs are non-standard, an error should be returned.
|
||||
// This method also takes the target fee expressed in sat/kw that should
|
||||
// be used when crafting the transaction.
|
||||
SendOutputs(outputs []*wire.TxOut,
|
||||
feeRate chainfee.SatPerKWeight) (*wire.MsgTx, error)
|
||||
|
||||
// CreateSimpleTx creates a Bitcoin transaction paying to the specified
|
||||
// outputs. The transaction is not broadcasted to the network. In the
|
||||
// case the wallet has insufficient funds, or the outputs are
|
||||
// non-standard, an error should be returned. This method also takes
|
||||
// the target fee expressed in sat/kw that should be used when crafting
|
||||
// the transaction.
|
||||
//
|
||||
// NOTE: The dryRun argument can be set true to create a tx that
|
||||
// doesn't alter the database. A tx created with this set to true
|
||||
// SHOULD NOT be broadcasted.
|
||||
CreateSimpleTx(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
|
||||
dryRun bool) (*txauthor.AuthoredTx, error)
|
||||
|
||||
// ListUnspentWitness returns all unspent outputs which are version 0
|
||||
// witness programs. The 'minconfirms' and 'maxconfirms' parameters
|
||||
// indicate the minimum and maximum number of confirmations an output
|
||||
// needs in order to be returned by this method. Passing -1 as
|
||||
// 'minconfirms' indicates that even unconfirmed outputs should be
|
||||
// returned. Using MaxInt32 as 'maxconfirms' implies returning all
|
||||
// outputs with at least 'minconfirms'.
|
||||
ListUnspentWitness(minconfirms, maxconfirms int32) ([]*Utxo, error)
|
||||
|
||||
// ListTransactionDetails returns a list of all transactions which are
|
||||
// relevant to the wallet.
|
||||
ListTransactionDetails() ([]*TransactionDetail, error)
|
||||
|
||||
// LockOutpoint marks an outpoint as locked meaning it will no longer
|
||||
// be deemed as eligible for coin selection. Locking outputs are
|
||||
// utilized in order to avoid race conditions when selecting inputs for
|
||||
// usage when funding a channel.
|
||||
LockOutpoint(o wire.OutPoint)
|
||||
|
||||
// UnlockOutpoint unlocks a previously locked output, marking it
|
||||
// eligible for coin selection.
|
||||
UnlockOutpoint(o wire.OutPoint)
|
||||
|
||||
// PublishTransaction performs cursory validation (dust checks, etc),
|
||||
// then finally broadcasts the passed transaction to the Bitcoin network.
|
||||
// If the transaction is rejected because it is conflicting with an
|
||||
// already known transaction, ErrDoubleSpend is returned. If the
|
||||
// transaction is already known (published already), no error will be
|
||||
// returned. Other error returned depends on the currently active chain
|
||||
// backend.
|
||||
PublishTransaction(tx *wire.MsgTx) error
|
||||
|
||||
// SubscribeTransactions returns a TransactionSubscription client which
|
||||
// is capable of receiving async notifications as new transactions
|
||||
// related to the wallet are seen within the network, or found in
|
||||
// blocks.
|
||||
//
|
||||
// NOTE: a non-nil error should be returned if notifications aren't
|
||||
// supported.
|
||||
//
|
||||
// TODO(roasbeef): make distinct interface?
|
||||
SubscribeTransactions() (TransactionSubscription, error)
|
||||
|
||||
// IsSynced returns a boolean indicating if from the PoV of the wallet,
|
||||
// it has fully synced to the current best block in the main chain.
|
||||
// It also returns an int64 indicating the timestamp of the best block
|
||||
// known to the wallet, expressed in Unix epoch time
|
||||
IsSynced() (bool, int64, error)
|
||||
|
||||
// Start initializes the wallet, making any necessary connections,
|
||||
// starting up required goroutines etc.
|
||||
Start() error
|
||||
|
||||
// Stop signals the wallet for shutdown. Shutdown may entail closing
|
||||
// any active sockets, database handles, stopping goroutines, etc.
|
||||
Stop() error
|
||||
|
||||
// BackEnd returns a name for the wallet's backing chain service,
|
||||
// which could be e.g. btcd, bitcoind, neutrino, or another consensus
|
||||
// service.
|
||||
BackEnd() string
|
||||
}
|
||||
|
||||
// BlockChainIO is a dedicated source which will be used to obtain queries
|
||||
// related to the current state of the blockchain. The data returned by each of
|
||||
// the defined methods within this interface should always return the most up
|
||||
// to date data possible.
|
||||
//
|
||||
// TODO(roasbeef): move to diff package perhaps?
|
||||
// TODO(roasbeef): move publish txn here?
|
||||
type BlockChainIO interface {
|
||||
// GetBestBlock returns the current height and block hash of the valid
|
||||
// most-work chain the implementation is aware of.
|
||||
GetBestBlock() (*chainhash.Hash, int32, error)
|
||||
|
||||
// GetUtxo attempts to return the passed outpoint if it's still a
|
||||
// member of the utxo set. The passed height hint should be the "birth
|
||||
// height" of the passed outpoint. The script passed should be the
|
||||
// script that the outpoint creates. In the case that the output is in
|
||||
// the UTXO set, then the output corresponding to that output is
|
||||
// returned. Otherwise, a non-nil error will be returned.
|
||||
// As for some backends this call can initiate a rescan, the passed
|
||||
// cancel channel can be closed to abort the call.
|
||||
GetUtxo(op *wire.OutPoint, pkScript []byte, heightHint uint32,
|
||||
cancel <-chan struct{}) (*wire.TxOut, error)
|
||||
|
||||
// GetBlockHash returns the hash of the block in the best blockchain
|
||||
// at the given height.
|
||||
GetBlockHash(blockHeight int64) (*chainhash.Hash, error)
|
||||
|
||||
// GetBlock returns the block in the main chain identified by the given
|
||||
// hash.
|
||||
GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error)
|
||||
}
|
||||
|
||||
// MessageSigner represents an abstract object capable of signing arbitrary
|
||||
// messages. The capabilities of this interface are used to sign announcements
|
||||
// to the network, or just arbitrary messages that leverage the wallet's keys
|
||||
// to attest to some message.
|
||||
type MessageSigner interface {
|
||||
// SignMessage attempts to sign a target message with the private key
|
||||
// that corresponds to the passed public key. If the target private key
|
||||
// is unable to be found, then an error will be returned. The actual
|
||||
// digest signed is the double SHA-256 of the passed message.
|
||||
SignMessage(pubKey *btcec.PublicKey, msg []byte) (input.Signature, error)
|
||||
}
|
||||
|
||||
// WalletDriver represents a "driver" for a particular concrete
|
||||
// WalletController implementation. A driver is identified by a globally unique
|
||||
// string identifier along with a 'New()' method which is responsible for
|
||||
// initializing a particular WalletController concrete implementation.
|
||||
type WalletDriver struct {
|
||||
// WalletType is a string which uniquely identifies the
|
||||
// WalletController that this driver, drives.
|
||||
WalletType string
|
||||
|
||||
// New creates a new instance of a concrete WalletController
|
||||
// implementation given a variadic set up arguments. The function takes
|
||||
// a variadic number of interface parameters in order to provide
|
||||
// initialization flexibility, thereby accommodating several potential
|
||||
// WalletController implementations.
|
||||
New func(args ...interface{}) (WalletController, error)
|
||||
|
||||
// BackEnds returns a list of available chain service drivers for the
|
||||
// wallet driver. This could be e.g. bitcoind, btcd, neutrino, etc.
|
||||
BackEnds func() []string
|
||||
}
|
||||
|
||||
var (
|
||||
wallets = make(map[string]*WalletDriver)
|
||||
registerMtx sync.Mutex
|
||||
)
|
||||
|
||||
// RegisteredWallets returns a slice of all currently registered notifiers.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func RegisteredWallets() []*WalletDriver {
|
||||
registerMtx.Lock()
|
||||
defer registerMtx.Unlock()
|
||||
|
||||
registeredWallets := make([]*WalletDriver, 0, len(wallets))
|
||||
for _, wallet := range wallets {
|
||||
registeredWallets = append(registeredWallets, wallet)
|
||||
}
|
||||
|
||||
return registeredWallets
|
||||
}
|
||||
|
||||
// RegisterWallet registers a WalletDriver which is capable of driving a
|
||||
// concrete WalletController interface. In the case that this driver has
|
||||
// already been registered, an error is returned.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func RegisterWallet(driver *WalletDriver) error {
|
||||
registerMtx.Lock()
|
||||
defer registerMtx.Unlock()
|
||||
|
||||
if _, ok := wallets[driver.WalletType]; ok {
|
||||
return fmt.Errorf("wallet already registered")
|
||||
}
|
||||
|
||||
wallets[driver.WalletType] = driver
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportedWallets returns a slice of strings that represents the wallet
|
||||
// drivers that have been registered and are therefore supported.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func SupportedWallets() []string {
|
||||
registerMtx.Lock()
|
||||
defer registerMtx.Unlock()
|
||||
|
||||
supportedWallets := make([]string, 0, len(wallets))
|
||||
for walletName := range wallets {
|
||||
supportedWallets = append(supportedWallets, walletName)
|
||||
}
|
||||
|
||||
return supportedWallets
|
||||
}
|
||||
55
vendor/github.com/lightningnetwork/lnd/lnwallet/log.go
generated
vendored
Normal file
55
vendor/github.com/lightningnetwork/lnd/lnwallet/log.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
btcwallet "github.com/btcsuite/btcwallet/wallet"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
|
||||
// walletLog 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 walletLog btclog.Logger
|
||||
|
||||
// The default amount of logging is none.
|
||||
func init() {
|
||||
UseLogger(build.NewSubLogger("LNWL", 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) {
|
||||
walletLog = logger
|
||||
|
||||
btcwallet.UseLogger(logger)
|
||||
wtxmgr.UseLogger(logger)
|
||||
chain.UseLogger(logger)
|
||||
chainfee.UseLogger(logger)
|
||||
}
|
||||
|
||||
// logClosure is used to provide a closure over expensive logging operations
|
||||
// so don't have to be performed when the logging level doesn't warrant it.
|
||||
type logClosure func() string
|
||||
|
||||
// String invokes the underlying function and returns the result.
|
||||
func (c logClosure) String() string {
|
||||
return c()
|
||||
}
|
||||
|
||||
// newLogClosure returns a new closure over a function that returns a string
|
||||
// which itself provides a Stringer interface so that it can be used with the
|
||||
// logging system.
|
||||
func newLogClosure(c func() string) logClosure {
|
||||
return logClosure(c)
|
||||
}
|
||||
13
vendor/github.com/lightningnetwork/lnd/lnwallet/parameters.go
generated
vendored
Normal file
13
vendor/github.com/lightningnetwork/lnd/lnwallet/parameters.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/wallet/txrules"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
)
|
||||
|
||||
// DefaultDustLimit is used to calculate the dust HTLC amount which will be
|
||||
// send to other node during funding process.
|
||||
func DefaultDustLimit() btcutil.Amount {
|
||||
return txrules.GetDustThreshold(input.P2WSHSize, txrules.DefaultRelayFeePerKb)
|
||||
}
|
||||
690
vendor/github.com/lightningnetwork/lnd/lnwallet/reservation.go
generated
vendored
Normal file
690
vendor/github.com/lightningnetwork/lnd/lnwallet/reservation.go
generated
vendored
Normal file
@@ -0,0 +1,690 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// CommitmentType is an enum indicating the commitment type we should use for
|
||||
// the channel we are opening.
|
||||
type CommitmentType int
|
||||
|
||||
const (
|
||||
// CommitmentTypeLegacy is the legacy commitment format with a tweaked
|
||||
// to_remote key.
|
||||
CommitmentTypeLegacy = iota
|
||||
|
||||
// CommitmentTypeTweakless is a newer commitment format where the
|
||||
// to_remote key is static.
|
||||
CommitmentTypeTweakless
|
||||
|
||||
// CommitmentTypeAnchors is a commitment type that is tweakless, and
|
||||
// has extra anchor ouputs in order to bump the fee of the commitment
|
||||
// transaction.
|
||||
CommitmentTypeAnchors
|
||||
)
|
||||
|
||||
// String returns the name of the CommitmentType.
|
||||
func (c CommitmentType) String() string {
|
||||
switch c {
|
||||
case CommitmentTypeLegacy:
|
||||
return "legacy"
|
||||
case CommitmentTypeTweakless:
|
||||
return "tweakless"
|
||||
case CommitmentTypeAnchors:
|
||||
return "anchors"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
// ChannelContribution is the primary constituent of the funding workflow
|
||||
// within lnwallet. Each side first exchanges their respective contributions
|
||||
// along with channel specific parameters like the min fee/KB. Once
|
||||
// contributions have been exchanged, each side will then produce signatures
|
||||
// for all their inputs to the funding transactions, and finally a signature
|
||||
// for the other party's version of the commitment transaction.
|
||||
type ChannelContribution struct {
|
||||
// FundingOutpoint is the amount of funds contributed to the funding
|
||||
// transaction.
|
||||
FundingAmount btcutil.Amount
|
||||
|
||||
// Inputs to the funding transaction.
|
||||
Inputs []*wire.TxIn
|
||||
|
||||
// ChangeOutputs are the Outputs to be used in the case that the total
|
||||
// value of the funding inputs is greater than the total potential
|
||||
// channel capacity.
|
||||
ChangeOutputs []*wire.TxOut
|
||||
|
||||
// FirstCommitmentPoint is the first commitment point that will be used
|
||||
// to create the revocation key in the first commitment transaction we
|
||||
// send to the remote party.
|
||||
FirstCommitmentPoint *btcec.PublicKey
|
||||
|
||||
// ChannelConfig is the concrete contribution that this node is
|
||||
// offering to the channel. This includes all the various constraints
|
||||
// such as the min HTLC, and also all the keys which will be used for
|
||||
// the duration of the channel.
|
||||
*channeldb.ChannelConfig
|
||||
|
||||
// UpfrontShutdown is an optional address to which the channel should be
|
||||
// paid out to on cooperative close.
|
||||
UpfrontShutdown lnwire.DeliveryAddress
|
||||
}
|
||||
|
||||
// toChanConfig returns the raw channel configuration generated by a node's
|
||||
// contribution to the channel.
|
||||
func (c *ChannelContribution) toChanConfig() channeldb.ChannelConfig {
|
||||
return *c.ChannelConfig
|
||||
}
|
||||
|
||||
// ChannelReservation represents an intent to open a lightning payment channel
|
||||
// with a counterparty. The funding processes from reservation to channel opening
|
||||
// is a 3-step process. In order to allow for full concurrency during the
|
||||
// reservation workflow, resources consumed by a contribution are "locked"
|
||||
// themselves. This prevents a number of race conditions such as two funding
|
||||
// transactions double-spending the same input. A reservation can also be
|
||||
// canceled, which removes the resources from limbo, allowing another
|
||||
// reservation to claim them.
|
||||
//
|
||||
// The reservation workflow consists of the following three steps:
|
||||
// 1. lnwallet.InitChannelReservation
|
||||
// * One requests the wallet to allocate the necessary resources for a
|
||||
// channel reservation. These resources are put in limbo for the lifetime
|
||||
// of a reservation.
|
||||
// * Once completed the reservation will have the wallet's contribution
|
||||
// accessible via the .OurContribution() method. This contribution
|
||||
// contains the necessary items to allow the remote party to build both
|
||||
// the funding, and commitment transactions.
|
||||
// 2. ChannelReservation.ProcessContribution/ChannelReservation.ProcessSingleContribution
|
||||
// * The counterparty presents their contribution to the payment channel.
|
||||
// This allows us to build the funding, and commitment transactions
|
||||
// ourselves.
|
||||
// * We're now able to sign our inputs to the funding transactions, and
|
||||
// the counterparty's version of the commitment transaction.
|
||||
// * All signatures crafted by us, are now available via .OurSignatures().
|
||||
// 3. ChannelReservation.CompleteReservation/ChannelReservation.CompleteReservationSingle
|
||||
// * The final step in the workflow. The counterparty presents the
|
||||
// signatures for all their inputs to the funding transaction, as well
|
||||
// as a signature to our version of the commitment transaction.
|
||||
// * We then verify the validity of all signatures before considering the
|
||||
// channel "open".
|
||||
type ChannelReservation struct {
|
||||
// This mutex MUST be held when either reading or modifying any of the
|
||||
// fields below.
|
||||
sync.RWMutex
|
||||
|
||||
// fundingTx is the funding transaction for this pending channel.
|
||||
fundingTx *wire.MsgTx
|
||||
|
||||
// In order of sorted inputs. Sorting is done in accordance
|
||||
// to BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
|
||||
ourFundingInputScripts []*input.Script
|
||||
theirFundingInputScripts []*input.Script
|
||||
|
||||
// Our signature for their version of the commitment transaction.
|
||||
ourCommitmentSig input.Signature
|
||||
theirCommitmentSig input.Signature
|
||||
|
||||
ourContribution *ChannelContribution
|
||||
theirContribution *ChannelContribution
|
||||
|
||||
partialState *channeldb.OpenChannel
|
||||
nodeAddr net.Addr
|
||||
|
||||
// The ID of this reservation, used to uniquely track the reservation
|
||||
// throughout its lifetime.
|
||||
reservationID uint64
|
||||
|
||||
// pendingChanID is the pending channel ID for this channel as
|
||||
// identified within the wire protocol.
|
||||
pendingChanID [32]byte
|
||||
|
||||
// pushMSat the amount of milli-satoshis that should be pushed to the
|
||||
// responder of a single funding channel as part of the initial
|
||||
// commitment state.
|
||||
pushMSat lnwire.MilliSatoshi
|
||||
|
||||
wallet *LightningWallet
|
||||
chanFunder chanfunding.Assembler
|
||||
|
||||
fundingIntent chanfunding.Intent
|
||||
}
|
||||
|
||||
// NewChannelReservation creates a new channel reservation. This function is
|
||||
// used only internally by lnwallet. In order to concurrent safety, the
|
||||
// creation of all channel reservations should be carried out via the
|
||||
// lnwallet.InitChannelReservation interface.
|
||||
func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
commitFeePerKw chainfee.SatPerKWeight, wallet *LightningWallet,
|
||||
id uint64, pushMSat lnwire.MilliSatoshi, chainHash *chainhash.Hash,
|
||||
flags lnwire.FundingFlag, commitType CommitmentType,
|
||||
fundingAssembler chanfunding.Assembler,
|
||||
pendingChanID [32]byte, thawHeight uint32) (*ChannelReservation, error) {
|
||||
|
||||
var (
|
||||
ourBalance lnwire.MilliSatoshi
|
||||
theirBalance lnwire.MilliSatoshi
|
||||
initiator bool
|
||||
)
|
||||
|
||||
// Based on the channel type, we determine the initial commit weight
|
||||
// and fee.
|
||||
commitWeight := int64(input.CommitWeight)
|
||||
if commitType == CommitmentTypeAnchors {
|
||||
commitWeight = input.AnchorCommitWeight
|
||||
}
|
||||
commitFee := commitFeePerKw.FeeForWeight(commitWeight)
|
||||
|
||||
localFundingMSat := lnwire.NewMSatFromSatoshis(localFundingAmt)
|
||||
// TODO(halseth): make method take remote funding amount directly
|
||||
// instead of inferring it from capacity and local amt.
|
||||
capacityMSat := lnwire.NewMSatFromSatoshis(capacity)
|
||||
|
||||
// The total fee paid by the initiator will be the commitment fee in
|
||||
// addition to the two anchor outputs.
|
||||
feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
|
||||
if commitType == CommitmentTypeAnchors {
|
||||
feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize)
|
||||
}
|
||||
|
||||
// If we're the responder to a single-funder reservation, then we have
|
||||
// no initial balance in the channel unless the remote party is pushing
|
||||
// some funds to us within the first commitment state.
|
||||
if localFundingAmt == 0 {
|
||||
ourBalance = pushMSat
|
||||
theirBalance = capacityMSat - feeMSat - pushMSat
|
||||
initiator = false
|
||||
|
||||
// If the responder doesn't have enough funds to actually pay
|
||||
// the fees, then we'll bail our early.
|
||||
if int64(theirBalance) < 0 {
|
||||
return nil, ErrFunderBalanceDust(
|
||||
int64(commitFee), int64(theirBalance.ToSatoshis()),
|
||||
int64(2*DefaultDustLimit()),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// TODO(roasbeef): need to rework fee structure in general and
|
||||
// also when we "unlock" dual funder within the daemon
|
||||
|
||||
if capacity == localFundingAmt {
|
||||
// If we're initiating a single funder workflow, then
|
||||
// we pay all the initial fees within the commitment
|
||||
// transaction. We also deduct our balance by the
|
||||
// amount pushed as part of the initial state.
|
||||
ourBalance = capacityMSat - feeMSat - pushMSat
|
||||
theirBalance = pushMSat
|
||||
} else {
|
||||
// Otherwise, this is a dual funder workflow where both
|
||||
// slides split the amount funded and the commitment
|
||||
// fee.
|
||||
ourBalance = localFundingMSat - (feeMSat / 2)
|
||||
theirBalance = capacityMSat - localFundingMSat - (feeMSat / 2) + pushMSat
|
||||
}
|
||||
|
||||
initiator = true
|
||||
|
||||
// If we, the initiator don't have enough funds to actually pay
|
||||
// the fees, then we'll exit with an error.
|
||||
if int64(ourBalance) < 0 {
|
||||
return nil, ErrFunderBalanceDust(
|
||||
int64(commitFee), int64(ourBalance),
|
||||
int64(2*DefaultDustLimit()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// If we're the initiator and our starting balance within the channel
|
||||
// after we take account of fees is below 2x the dust limit, then we'll
|
||||
// reject this channel creation request.
|
||||
//
|
||||
// TODO(roasbeef): reject if 30% goes to fees? dust channel
|
||||
if initiator && ourBalance.ToSatoshis() <= 2*DefaultDustLimit() {
|
||||
return nil, ErrFunderBalanceDust(
|
||||
int64(commitFee),
|
||||
int64(ourBalance.ToSatoshis()),
|
||||
int64(2*DefaultDustLimit()),
|
||||
)
|
||||
}
|
||||
|
||||
// Similarly we ensure their balance is reasonable if we are not the
|
||||
// initiator.
|
||||
if !initiator && theirBalance.ToSatoshis() <= 2*DefaultDustLimit() {
|
||||
return nil, ErrFunderBalanceDust(
|
||||
int64(commitFee),
|
||||
int64(theirBalance.ToSatoshis()),
|
||||
int64(2*DefaultDustLimit()),
|
||||
)
|
||||
}
|
||||
|
||||
// Next we'll set the channel type based on what we can ascertain about
|
||||
// the balances/push amount within the channel.
|
||||
var chanType channeldb.ChannelType
|
||||
|
||||
// If either of the balances are zero at this point, or we have a
|
||||
// non-zero push amt (there's no pushing for dual funder), then this is
|
||||
// a single-funder channel.
|
||||
if ourBalance == 0 || theirBalance == 0 || pushMSat != 0 {
|
||||
// Both the tweakless type and the anchor type is tweakless,
|
||||
// hence set the bit.
|
||||
if commitType == CommitmentTypeTweakless ||
|
||||
commitType == CommitmentTypeAnchors {
|
||||
|
||||
chanType |= channeldb.SingleFunderTweaklessBit
|
||||
} else {
|
||||
chanType |= channeldb.SingleFunderBit
|
||||
}
|
||||
|
||||
// If this intent isn't one that's able to provide us with a
|
||||
// funding transaction, then we'll set the chanType bit to
|
||||
// signal that we don't have access to one.
|
||||
if _, ok := fundingAssembler.(chanfunding.FundingTxAssembler); !ok {
|
||||
chanType |= channeldb.NoFundingTxBit
|
||||
}
|
||||
|
||||
} else {
|
||||
// Otherwise, this is a dual funder channel, and no side is
|
||||
// technically the "initiator"
|
||||
initiator = false
|
||||
chanType |= channeldb.DualFunderBit
|
||||
}
|
||||
|
||||
// We are adding anchor outputs to our commitment.
|
||||
if commitType == CommitmentTypeAnchors {
|
||||
chanType |= channeldb.AnchorOutputsBit
|
||||
}
|
||||
|
||||
// If the channel is meant to be frozen, then we'll set the frozen bit
|
||||
// now so once the channel is open, it can be interpreted properly.
|
||||
if thawHeight != 0 {
|
||||
chanType |= channeldb.FrozenBit
|
||||
}
|
||||
|
||||
return &ChannelReservation{
|
||||
ourContribution: &ChannelContribution{
|
||||
FundingAmount: ourBalance.ToSatoshis(),
|
||||
ChannelConfig: &channeldb.ChannelConfig{},
|
||||
},
|
||||
theirContribution: &ChannelContribution{
|
||||
FundingAmount: theirBalance.ToSatoshis(),
|
||||
ChannelConfig: &channeldb.ChannelConfig{},
|
||||
},
|
||||
partialState: &channeldb.OpenChannel{
|
||||
ChanType: chanType,
|
||||
ChainHash: *chainHash,
|
||||
IsPending: true,
|
||||
IsInitiator: initiator,
|
||||
ChannelFlags: flags,
|
||||
Capacity: capacity,
|
||||
LocalCommitment: channeldb.ChannelCommitment{
|
||||
LocalBalance: ourBalance,
|
||||
RemoteBalance: theirBalance,
|
||||
FeePerKw: btcutil.Amount(commitFeePerKw),
|
||||
CommitFee: commitFee,
|
||||
},
|
||||
RemoteCommitment: channeldb.ChannelCommitment{
|
||||
LocalBalance: ourBalance,
|
||||
RemoteBalance: theirBalance,
|
||||
FeePerKw: btcutil.Amount(commitFeePerKw),
|
||||
CommitFee: commitFee,
|
||||
},
|
||||
ThawHeight: thawHeight,
|
||||
Db: wallet.Cfg.Database,
|
||||
},
|
||||
pushMSat: pushMSat,
|
||||
pendingChanID: pendingChanID,
|
||||
reservationID: id,
|
||||
wallet: wallet,
|
||||
chanFunder: fundingAssembler,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetNumConfsRequired sets the number of confirmations that are required for
|
||||
// the ultimate funding transaction before the channel can be considered open.
|
||||
// This is distinct from the main reservation workflow as it allows
|
||||
// implementations a bit more flexibility w.r.t to if the responder of the
|
||||
// initiator sets decides the number of confirmations needed.
|
||||
func (r *ChannelReservation) SetNumConfsRequired(numConfs uint16) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
r.partialState.NumConfsRequired = numConfs
|
||||
}
|
||||
|
||||
// CommitConstraints takes the constraints that the remote party specifies for
|
||||
// the type of commitments that we can generate for them. These constraints
|
||||
// include several parameters that serve as flow control restricting the amount
|
||||
// of satoshis that can be transferred in a single commitment. This function
|
||||
// will also attempt to verify the constraints for sanity, returning an error
|
||||
// if the parameters are seemed unsound.
|
||||
func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
// Fail if we consider csvDelay excessively large.
|
||||
// TODO(halseth): find a more scientific choice of value.
|
||||
const maxDelay = 10000
|
||||
if c.CsvDelay > maxDelay {
|
||||
return ErrCsvDelayTooLarge(c.CsvDelay, maxDelay)
|
||||
}
|
||||
|
||||
// The channel reserve should always be greater or equal to the dust
|
||||
// limit. The reservation request should be denied if otherwise.
|
||||
if c.DustLimit > c.ChanReserve {
|
||||
return ErrChanReserveTooSmall(c.ChanReserve, c.DustLimit)
|
||||
}
|
||||
|
||||
// Fail if we consider the channel reserve to be too large. We
|
||||
// currently fail if it is greater than 20% of the channel capacity.
|
||||
maxChanReserve := r.partialState.Capacity / 5
|
||||
if c.ChanReserve > maxChanReserve {
|
||||
return ErrChanReserveTooLarge(c.ChanReserve, maxChanReserve)
|
||||
}
|
||||
|
||||
// Fail if the minimum HTLC value is too large. If this is too large,
|
||||
// the channel won't be useful for sending small payments. This limit
|
||||
// is currently set to maxValueInFlight, effectively letting the remote
|
||||
// setting this as large as it wants.
|
||||
if c.MinHTLC > c.MaxPendingAmount {
|
||||
return ErrMinHtlcTooLarge(c.MinHTLC, c.MaxPendingAmount)
|
||||
}
|
||||
|
||||
// Fail if maxHtlcs is above the maximum allowed number of 483. This
|
||||
// number is specified in BOLT-02.
|
||||
if c.MaxAcceptedHtlcs > uint16(input.MaxHTLCNumber/2) {
|
||||
return ErrMaxHtlcNumTooLarge(
|
||||
c.MaxAcceptedHtlcs, uint16(input.MaxHTLCNumber/2),
|
||||
)
|
||||
}
|
||||
|
||||
// Fail if we consider maxHtlcs too small. If this is too small we
|
||||
// cannot offer many HTLCs to the remote.
|
||||
const minNumHtlc = 5
|
||||
if c.MaxAcceptedHtlcs < minNumHtlc {
|
||||
return ErrMaxHtlcNumTooSmall(c.MaxAcceptedHtlcs, minNumHtlc)
|
||||
}
|
||||
|
||||
// Fail if we consider maxValueInFlight too small. We currently require
|
||||
// the remote to at least allow minNumHtlc * minHtlc in flight.
|
||||
if c.MaxPendingAmount < minNumHtlc*c.MinHTLC {
|
||||
return ErrMaxValueInFlightTooSmall(
|
||||
c.MaxPendingAmount, minNumHtlc*c.MinHTLC,
|
||||
)
|
||||
}
|
||||
|
||||
// Our dust limit should always be less than or equal to our proposed
|
||||
// channel reserve.
|
||||
if r.ourContribution.DustLimit > c.ChanReserve {
|
||||
r.ourContribution.DustLimit = c.ChanReserve
|
||||
}
|
||||
|
||||
r.ourContribution.ChanReserve = c.ChanReserve
|
||||
r.ourContribution.MaxPendingAmount = c.MaxPendingAmount
|
||||
r.ourContribution.MinHTLC = c.MinHTLC
|
||||
r.ourContribution.MaxAcceptedHtlcs = c.MaxAcceptedHtlcs
|
||||
r.ourContribution.CsvDelay = c.CsvDelay
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OurContribution returns the wallet's fully populated contribution to the
|
||||
// pending payment channel. See 'ChannelContribution' for further details
|
||||
// regarding the contents of a contribution.
|
||||
//
|
||||
// NOTE: This SHOULD NOT be modified.
|
||||
// TODO(roasbeef): make copy?
|
||||
func (r *ChannelReservation) OurContribution() *ChannelContribution {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
|
||||
return r.ourContribution
|
||||
}
|
||||
|
||||
// ProcessContribution verifies the counterparty's contribution to the pending
|
||||
// payment channel. As a result of this incoming message, lnwallet is able to
|
||||
// build the funding transaction, and both commitment transactions. Once this
|
||||
// message has been processed, all signatures to inputs to the funding
|
||||
// transaction belonging to the wallet are available. Additionally, the wallet
|
||||
// will generate a signature to the counterparty's version of the commitment
|
||||
// transaction.
|
||||
func (r *ChannelReservation) ProcessContribution(theirContribution *ChannelContribution) error {
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
r.wallet.msgChan <- &addContributionMsg{
|
||||
pendingFundingID: r.reservationID,
|
||||
contribution: theirContribution,
|
||||
err: errChan,
|
||||
}
|
||||
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
// IsPsbt returns true if there is a PSBT funding intent mapped to this
|
||||
// reservation.
|
||||
func (r *ChannelReservation) IsPsbt() bool {
|
||||
_, ok := r.fundingIntent.(*chanfunding.PsbtIntent)
|
||||
return ok
|
||||
}
|
||||
|
||||
// ProcessPsbt continues a previously paused funding flow that involves PSBT to
|
||||
// construct the funding transaction. This method can be called once the PSBT is
|
||||
// finalized and the signed transaction is available.
|
||||
func (r *ChannelReservation) ProcessPsbt() error {
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
r.wallet.msgChan <- &continueContributionMsg{
|
||||
pendingFundingID: r.reservationID,
|
||||
err: errChan,
|
||||
}
|
||||
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
// RemoteCanceled informs the PSBT funding state machine that the remote peer
|
||||
// has canceled the pending reservation, likely due to a timeout.
|
||||
func (r *ChannelReservation) RemoteCanceled() {
|
||||
psbtIntent, ok := r.fundingIntent.(*chanfunding.PsbtIntent)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
psbtIntent.RemoteCanceled()
|
||||
}
|
||||
|
||||
// ProcessSingleContribution verifies, and records the initiator's contribution
|
||||
// to this pending single funder channel. Internally, no further action is
|
||||
// taken other than recording the initiator's contribution to the single funder
|
||||
// channel.
|
||||
func (r *ChannelReservation) ProcessSingleContribution(theirContribution *ChannelContribution) error {
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
r.wallet.msgChan <- &addSingleContributionMsg{
|
||||
pendingFundingID: r.reservationID,
|
||||
contribution: theirContribution,
|
||||
err: errChan,
|
||||
}
|
||||
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
// TheirContribution returns the counterparty's pending contribution to the
|
||||
// payment channel. See 'ChannelContribution' for further details regarding the
|
||||
// contents of a contribution. This attribute will ONLY be available after a
|
||||
// call to .ProcessContribution().
|
||||
//
|
||||
// NOTE: This SHOULD NOT be modified.
|
||||
func (r *ChannelReservation) TheirContribution() *ChannelContribution {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
return r.theirContribution
|
||||
}
|
||||
|
||||
// OurSignatures retrieves the wallet's signatures to all inputs to the funding
|
||||
// transaction belonging to itself, and also a signature for the counterparty's
|
||||
// version of the commitment transaction. The signatures for the wallet's
|
||||
// inputs to the funding transaction are returned in sorted order according to
|
||||
// BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
|
||||
//
|
||||
// NOTE: These signatures will only be populated after a call to
|
||||
// .ProcessContribution()
|
||||
func (r *ChannelReservation) OurSignatures() ([]*input.Script,
|
||||
input.Signature) {
|
||||
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
return r.ourFundingInputScripts, r.ourCommitmentSig
|
||||
}
|
||||
|
||||
// CompleteReservation finalizes the pending channel reservation, transitioning
|
||||
// from a pending payment channel, to an open payment channel. All passed
|
||||
// signatures to the counterparty's inputs to the funding transaction will be
|
||||
// fully verified. Signatures are expected to be passed in sorted order
|
||||
// according to BIP-69:
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
|
||||
// Additionally, verification is performed in order to ensure that the
|
||||
// counterparty supplied a valid signature to our version of the commitment
|
||||
// transaction. Once this method returns, callers should broadcast the
|
||||
// created funding transaction, then call .WaitForChannelOpen() which will
|
||||
// block until the funding transaction obtains the configured number of
|
||||
// confirmations. Once the method unblocks, a LightningChannel instance is
|
||||
// returned, marking the channel available for updates.
|
||||
func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*input.Script,
|
||||
commitmentSig input.Signature) (*channeldb.OpenChannel, error) {
|
||||
|
||||
// TODO(roasbeef): add flag for watch or not?
|
||||
errChan := make(chan error, 1)
|
||||
completeChan := make(chan *channeldb.OpenChannel, 1)
|
||||
|
||||
r.wallet.msgChan <- &addCounterPartySigsMsg{
|
||||
pendingFundingID: r.reservationID,
|
||||
theirFundingInputScripts: fundingInputScripts,
|
||||
theirCommitmentSig: commitmentSig,
|
||||
completeChan: completeChan,
|
||||
err: errChan,
|
||||
}
|
||||
|
||||
return <-completeChan, <-errChan
|
||||
}
|
||||
|
||||
// CompleteReservationSingle finalizes the pending single funder channel
|
||||
// reservation. Using the funding outpoint of the constructed funding
|
||||
// transaction, and the initiator's signature for our version of the commitment
|
||||
// transaction, we are able to verify the correctness of our commitment
|
||||
// transaction as crafted by the initiator. Once this method returns, our
|
||||
// signature for the initiator's version of the commitment transaction is
|
||||
// available via the .OurSignatures() method. As this method should only be
|
||||
// called as a response to a single funder channel, only a commitment signature
|
||||
// will be populated.
|
||||
func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoint,
|
||||
commitSig input.Signature) (*channeldb.OpenChannel, error) {
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
completeChan := make(chan *channeldb.OpenChannel, 1)
|
||||
|
||||
r.wallet.msgChan <- &addSingleFunderSigsMsg{
|
||||
pendingFundingID: r.reservationID,
|
||||
fundingOutpoint: fundingPoint,
|
||||
theirCommitmentSig: commitSig,
|
||||
completeChan: completeChan,
|
||||
err: errChan,
|
||||
}
|
||||
|
||||
return <-completeChan, <-errChan
|
||||
}
|
||||
|
||||
// TheirSignatures returns the counterparty's signatures to all inputs to the
|
||||
// funding transaction belonging to them, as well as their signature for the
|
||||
// wallet's version of the commitment transaction. This methods is provided for
|
||||
// additional verification, such as needed by tests.
|
||||
//
|
||||
// NOTE: These attributes will be unpopulated before a call to
|
||||
// .CompleteReservation().
|
||||
func (r *ChannelReservation) TheirSignatures() ([]*input.Script,
|
||||
input.Signature) {
|
||||
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
return r.theirFundingInputScripts, r.theirCommitmentSig
|
||||
}
|
||||
|
||||
// FinalFundingTx returns the finalized, fully signed funding transaction for
|
||||
// this reservation.
|
||||
//
|
||||
// NOTE: If this reservation was created as the non-initiator to a single
|
||||
// funding workflow, then the full funding transaction will not be available.
|
||||
// Instead we will only have the final outpoint of the funding transaction.
|
||||
func (r *ChannelReservation) FinalFundingTx() *wire.MsgTx {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
return r.fundingTx
|
||||
}
|
||||
|
||||
// FundingOutpoint returns the outpoint of the funding transaction.
|
||||
//
|
||||
// NOTE: The pointer returned will only be set once the .ProcessContribution()
|
||||
// method is called in the case of the initiator of a single funder workflow,
|
||||
// and after the .CompleteReservationSingle() method is called in the case of
|
||||
// a responder to a single funder workflow.
|
||||
func (r *ChannelReservation) FundingOutpoint() *wire.OutPoint {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
return &r.partialState.FundingOutpoint
|
||||
}
|
||||
|
||||
// SetOurUpfrontShutdown sets the upfront shutdown address on our contribution.
|
||||
func (r *ChannelReservation) SetOurUpfrontShutdown(shutdown lnwire.DeliveryAddress) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
r.ourContribution.UpfrontShutdown = shutdown
|
||||
}
|
||||
|
||||
// Capacity returns the channel capacity for this reservation.
|
||||
func (r *ChannelReservation) Capacity() btcutil.Amount {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
return r.partialState.Capacity
|
||||
}
|
||||
|
||||
// Cancel abandons this channel reservation. This method should be called in
|
||||
// the scenario that communications with the counterparty break down. Upon
|
||||
// cancellation, all resources previously reserved for this pending payment
|
||||
// channel are returned to the free pool, allowing subsequent reservations to
|
||||
// utilize the now freed resources.
|
||||
func (r *ChannelReservation) Cancel() error {
|
||||
errChan := make(chan error, 1)
|
||||
r.wallet.msgChan <- &fundingReserveCancelMsg{
|
||||
pendingFundingID: r.reservationID,
|
||||
err: errChan,
|
||||
}
|
||||
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
// OpenChannelDetails wraps the finalized fully confirmed channel which
|
||||
// resulted from a ChannelReservation instance with details concerning exactly
|
||||
// _where_ in the chain the channel was ultimately opened.
|
||||
type OpenChannelDetails struct {
|
||||
// Channel is the active channel created by an instance of a
|
||||
// ChannelReservation and the required funding workflow.
|
||||
Channel *LightningChannel
|
||||
|
||||
// ConfirmationHeight is the block height within the chain that
|
||||
// included the channel.
|
||||
ConfirmationHeight uint32
|
||||
|
||||
// TransactionIndex is the index within the confirming block that the
|
||||
// transaction resides.
|
||||
TransactionIndex uint32
|
||||
}
|
||||
307
vendor/github.com/lightningnetwork/lnd/lnwallet/sigpool.go
generated
vendored
Normal file
307
vendor/github.com/lightningnetwork/lnd/lnwallet/sigpool.go
generated
vendored
Normal file
@@ -0,0 +1,307 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
const (
|
||||
// jobBuffer is a constant the represents the buffer of jobs in the two
|
||||
// main queues. This allows clients avoid necessarily blocking when
|
||||
// submitting jobs into the queue.
|
||||
jobBuffer = 100
|
||||
|
||||
// TODO(roasbeef): job buffer pool?
|
||||
)
|
||||
|
||||
// VerifyJob is a job sent to the sigPool sig pool to verify a signature
|
||||
// on a transaction. The items contained in the struct are necessary and
|
||||
// sufficient to verify the full signature. The passed sigHash closure function
|
||||
// should be set to a function that generates the relevant sighash.
|
||||
//
|
||||
// TODO(roasbeef): when we move to ecschnorr, make into batch signature
|
||||
// verification using bos-coster (or pip?).
|
||||
type VerifyJob struct {
|
||||
// PubKey is the public key that was used to generate the purported
|
||||
// valid signature. Note that with the current channel construction,
|
||||
// this public key will likely have been tweaked using the current per
|
||||
// commitment point for a particular commitment transactions.
|
||||
PubKey *btcec.PublicKey
|
||||
|
||||
// Sig is the raw signature generated using the above public key. This
|
||||
// is the signature to be verified.
|
||||
Sig *btcec.Signature
|
||||
|
||||
// SigHash is a function closure generates the sighashes that the
|
||||
// passed signature is known to have signed.
|
||||
SigHash func() ([]byte, error)
|
||||
|
||||
// HtlcIndex is the index of the HTLC from the PoV of the remote
|
||||
// party's update log.
|
||||
HtlcIndex uint64
|
||||
|
||||
// Cancel is a channel that should be closed if the caller wishes to
|
||||
// cancel all pending verification jobs part of a single batch. This
|
||||
// channel is to be closed in the case that a single signature in a
|
||||
// batch has been returned as invalid, as there is no need to verify
|
||||
// the remainder of the signatures.
|
||||
Cancel chan struct{}
|
||||
|
||||
// ErrResp is the channel that the result of the signature verification
|
||||
// is to be sent over. In the see that the signature is valid, a nil
|
||||
// error will be passed. Otherwise, a concrete error detailing the
|
||||
// issue will be passed.
|
||||
ErrResp chan *HtlcIndexErr
|
||||
}
|
||||
|
||||
// HtlcIndexErr is a special type of error that also includes a pointer to the
|
||||
// original validation job. This error message allows us to craft more detailed
|
||||
// errors at upper layers.
|
||||
type HtlcIndexErr struct {
|
||||
error
|
||||
|
||||
*VerifyJob
|
||||
}
|
||||
|
||||
// SignJob is a job sent to the sigPool sig pool to generate a valid
|
||||
// signature according to the passed SignDescriptor for the passed transaction.
|
||||
// Jobs are intended to be sent in batches in order to parallelize the job of
|
||||
// generating signatures for a new commitment transaction.
|
||||
type SignJob struct {
|
||||
// SignDesc is intended to be a full populated SignDescriptor which
|
||||
// encodes the necessary material (keys, witness script, etc) required
|
||||
// to generate a valid signature for the specified input.
|
||||
SignDesc input.SignDescriptor
|
||||
|
||||
// Tx is the transaction to be signed. This is required to generate the
|
||||
// proper sighash for the input to be signed.
|
||||
Tx *wire.MsgTx
|
||||
|
||||
// OutputIndex is the output index of the HTLC on the commitment
|
||||
// transaction being signed.
|
||||
OutputIndex int32
|
||||
|
||||
// Cancel is a channel that should be closed if the caller wishes to
|
||||
// abandon all pending sign jobs part of a single batch.
|
||||
Cancel chan struct{}
|
||||
|
||||
// Resp is the channel that the response to this particular SignJob
|
||||
// will be sent over.
|
||||
//
|
||||
// TODO(roasbeef): actually need to allow caller to set, need to retain
|
||||
// order mark commit sig as special
|
||||
Resp chan SignJobResp
|
||||
}
|
||||
|
||||
// SignJobResp is the response to a sign job. Both channels are to be read in
|
||||
// order to ensure no unnecessary goroutine blocking occurs. Additionally, both
|
||||
// channels should be buffered.
|
||||
type SignJobResp struct {
|
||||
// Sig is the generated signature for a particular SignJob In the case
|
||||
// of an error during signature generation, then this value sent will
|
||||
// be nil.
|
||||
Sig lnwire.Sig
|
||||
|
||||
// Err is the error that occurred when executing the specified
|
||||
// signature job. In the case that no error occurred, this value will
|
||||
// be nil.
|
||||
Err error
|
||||
}
|
||||
|
||||
// TODO(roasbeef); fix description
|
||||
|
||||
// SigPool is a struct that is meant to allow the current channel state
|
||||
// machine to parallelize all signature generation and verification. This
|
||||
// struct is needed as _each_ HTLC when creating a commitment transaction
|
||||
// requires a signature, and similarly a receiver of a new commitment must
|
||||
// verify all the HTLC signatures included within the CommitSig message. A pool
|
||||
// of workers will be maintained by the sigPool. Batches of jobs (either
|
||||
// to sign or verify) can be sent to the pool of workers which will
|
||||
// asynchronously perform the specified job.
|
||||
type SigPool struct {
|
||||
started sync.Once
|
||||
stopped sync.Once
|
||||
|
||||
signer input.Signer
|
||||
|
||||
verifyJobs chan VerifyJob
|
||||
signJobs chan SignJob
|
||||
|
||||
wg sync.WaitGroup
|
||||
quit chan struct{}
|
||||
|
||||
numWorkers int
|
||||
}
|
||||
|
||||
// NewSigPool creates a new signature pool with the specified number of
|
||||
// workers. The recommended parameter for the number of works is the number of
|
||||
// physical CPU cores available on the target machine.
|
||||
func NewSigPool(numWorkers int, signer input.Signer) *SigPool {
|
||||
return &SigPool{
|
||||
signer: signer,
|
||||
numWorkers: numWorkers,
|
||||
verifyJobs: make(chan VerifyJob, jobBuffer),
|
||||
signJobs: make(chan SignJob, jobBuffer),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts of all goroutines that the sigPool sig pool needs to
|
||||
// carry out its duties.
|
||||
func (s *SigPool) Start() error {
|
||||
s.started.Do(func() {
|
||||
for i := 0; i < s.numWorkers; i++ {
|
||||
s.wg.Add(1)
|
||||
go s.poolWorker()
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop signals any active workers carrying out jobs to exit so the sigPool can
|
||||
// gracefully shutdown.
|
||||
func (s *SigPool) Stop() error {
|
||||
s.stopped.Do(func() {
|
||||
close(s.quit)
|
||||
s.wg.Wait()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// poolWorker is the main worker goroutine within the sigPool sig pool.
|
||||
// Individual batches are distributed amongst each of the active workers. The
|
||||
// workers then execute the task based on the type of job, and return the
|
||||
// result back to caller.
|
||||
func (s *SigPool) poolWorker() {
|
||||
defer s.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
// We've just received a new signature job. Given the items
|
||||
// contained within the message, we'll craft a signature and
|
||||
// send the result along with a possible error back to the
|
||||
// caller.
|
||||
case sigMsg := <-s.signJobs:
|
||||
rawSig, err := s.signer.SignOutputRaw(
|
||||
sigMsg.Tx, &sigMsg.SignDesc,
|
||||
)
|
||||
if err != nil {
|
||||
select {
|
||||
case sigMsg.Resp <- SignJobResp{
|
||||
Sig: lnwire.Sig{},
|
||||
Err: err,
|
||||
}:
|
||||
continue
|
||||
case <-sigMsg.Cancel:
|
||||
continue
|
||||
case <-s.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sig, err := lnwire.NewSigFromSignature(rawSig)
|
||||
select {
|
||||
case sigMsg.Resp <- SignJobResp{
|
||||
Sig: sig,
|
||||
Err: err,
|
||||
}:
|
||||
case <-sigMsg.Cancel:
|
||||
continue
|
||||
case <-s.quit:
|
||||
return
|
||||
}
|
||||
|
||||
// We've just received a new verification job from the outside
|
||||
// world. We'll attempt to construct the sighash, parse the
|
||||
// signature, and finally verify the signature.
|
||||
case verifyMsg := <-s.verifyJobs:
|
||||
sigHash, err := verifyMsg.SigHash()
|
||||
if err != nil {
|
||||
select {
|
||||
case verifyMsg.ErrResp <- &HtlcIndexErr{
|
||||
error: err,
|
||||
VerifyJob: &verifyMsg,
|
||||
}:
|
||||
continue
|
||||
case <-verifyMsg.Cancel:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
rawSig := verifyMsg.Sig
|
||||
|
||||
if !rawSig.Verify(sigHash, verifyMsg.PubKey) {
|
||||
err := fmt.Errorf("invalid signature "+
|
||||
"sighash: %x, sig: %x", sigHash,
|
||||
rawSig.Serialize())
|
||||
|
||||
select {
|
||||
case verifyMsg.ErrResp <- &HtlcIndexErr{
|
||||
error: err,
|
||||
VerifyJob: &verifyMsg,
|
||||
}:
|
||||
case <-verifyMsg.Cancel:
|
||||
case <-s.quit:
|
||||
return
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case verifyMsg.ErrResp <- nil:
|
||||
case <-verifyMsg.Cancel:
|
||||
case <-s.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// The sigPool sig pool is exiting, so we will as well.
|
||||
case <-s.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SubmitSignBatch submits a batch of signature jobs to the sigPool. The
|
||||
// response and cancel channels for each of the SignJob's are expected to be
|
||||
// fully populated, as the response for each job will be sent over the
|
||||
// response channel within the job itself.
|
||||
func (s *SigPool) SubmitSignBatch(signJobs []SignJob) {
|
||||
for _, job := range signJobs {
|
||||
select {
|
||||
case s.signJobs <- job:
|
||||
case <-job.Cancel:
|
||||
// TODO(roasbeef): return error?
|
||||
case <-s.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SubmitVerifyBatch submits a batch of verification jobs to the sigPool. For
|
||||
// each job submitted, an error will be passed into the returned channel
|
||||
// denoting if signature verification was valid or not. The passed cancelChan
|
||||
// allows the caller to cancel all pending jobs in the case that they wish to
|
||||
// bail early.
|
||||
func (s *SigPool) SubmitVerifyBatch(verifyJobs []VerifyJob,
|
||||
cancelChan chan struct{}) <-chan *HtlcIndexErr {
|
||||
|
||||
errChan := make(chan *HtlcIndexErr, len(verifyJobs))
|
||||
|
||||
for _, job := range verifyJobs {
|
||||
job.Cancel = cancelChan
|
||||
job.ErrResp = errChan
|
||||
|
||||
select {
|
||||
case s.verifyJobs <- job:
|
||||
case <-job.Cancel:
|
||||
return errChan
|
||||
}
|
||||
}
|
||||
|
||||
return errChan
|
||||
}
|
||||
516
vendor/github.com/lightningnetwork/lnd/lnwallet/test_utils.go
generated
vendored
Normal file
516
vendor/github.com/lightningnetwork/lnd/lnwallet/test_utils.go
generated
vendored
Normal file
@@ -0,0 +1,516 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
prand "math/rand"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/shachain"
|
||||
)
|
||||
|
||||
var (
|
||||
// For simplicity a single priv key controls all of our test outputs.
|
||||
testWalletPrivKey = []byte{
|
||||
0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
|
||||
0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
|
||||
0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
|
||||
0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
|
||||
}
|
||||
|
||||
// We're alice :)
|
||||
bobsPrivKey = []byte{
|
||||
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
|
||||
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
|
||||
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
|
||||
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
|
||||
}
|
||||
|
||||
// Use a hard-coded HD seed.
|
||||
testHdSeed = chainhash.Hash{
|
||||
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
|
||||
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
|
||||
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
|
||||
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
|
||||
}
|
||||
|
||||
// A serializable txn for testing funding txn.
|
||||
testTx = &wire.MsgTx{
|
||||
Version: 1,
|
||||
TxIn: []*wire.TxIn{
|
||||
{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{},
|
||||
Index: 0xffffffff,
|
||||
},
|
||||
SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62},
|
||||
Sequence: 0xffffffff,
|
||||
},
|
||||
},
|
||||
TxOut: []*wire.TxOut{
|
||||
{
|
||||
Value: 5000000000,
|
||||
PkScript: []byte{
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||
0xa6, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
},
|
||||
},
|
||||
},
|
||||
LockTime: 5,
|
||||
}
|
||||
|
||||
// A valid, DER-encoded signature (taken from btcec unit tests).
|
||||
testSigBytes = []byte{
|
||||
0x30, 0x44, 0x02, 0x20, 0x4e, 0x45, 0xe1, 0x69,
|
||||
0x32, 0xb8, 0xaf, 0x51, 0x49, 0x61, 0xa1, 0xd3,
|
||||
0xa1, 0xa2, 0x5f, 0xdf, 0x3f, 0x4f, 0x77, 0x32,
|
||||
0xe9, 0xd6, 0x24, 0xc6, 0xc6, 0x15, 0x48, 0xab,
|
||||
0x5f, 0xb8, 0xcd, 0x41, 0x02, 0x20, 0x18, 0x15,
|
||||
0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, 0x48, 0x60,
|
||||
0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, 0x83, 0x1c,
|
||||
0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, 0x08, 0x22,
|
||||
0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09,
|
||||
}
|
||||
)
|
||||
|
||||
// CreateTestChannels creates to fully populated channels to be used within
|
||||
// testing fixtures. The channels will be returned as if the funding process
|
||||
// has just completed. The channel itself is funded with 10 BTC, with 5 BTC
|
||||
// allocated to each side. Within the channel, Alice is the initiator. The
|
||||
// function also returns a "cleanup" function that is meant to be called once
|
||||
// the test has been finalized. The clean up function will remote all temporary
|
||||
// files created. If tweaklessCommits is true, then the commits within the
|
||||
// channels will use the new format, otherwise the legacy format.
|
||||
func CreateTestChannels(chanType channeldb.ChannelType) (
|
||||
*LightningChannel, *LightningChannel, func(), error) {
|
||||
|
||||
channelCapacity, err := btcutil.NewAmount(10)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
channelBal := channelCapacity / 2
|
||||
aliceDustLimit := btcutil.Amount(200)
|
||||
bobDustLimit := btcutil.Amount(1300)
|
||||
csvTimeoutAlice := uint32(5)
|
||||
csvTimeoutBob := uint32(4)
|
||||
|
||||
prevOut := &wire.OutPoint{
|
||||
Hash: chainhash.Hash(testHdSeed),
|
||||
Index: prand.Uint32(),
|
||||
}
|
||||
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
|
||||
|
||||
// For each party, we'll create a distinct set of keys in order to
|
||||
// emulate the typical set up with live channels.
|
||||
var (
|
||||
aliceKeys []*btcec.PrivateKey
|
||||
bobKeys []*btcec.PrivateKey
|
||||
)
|
||||
for i := 0; i < 5; i++ {
|
||||
key := make([]byte, len(testWalletPrivKey))
|
||||
copy(key[:], testWalletPrivKey[:])
|
||||
key[0] ^= byte(i + 1)
|
||||
|
||||
aliceKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), key)
|
||||
aliceKeys = append(aliceKeys, aliceKey)
|
||||
|
||||
key = make([]byte, len(bobsPrivKey))
|
||||
copy(key[:], bobsPrivKey)
|
||||
key[0] ^= byte(i + 1)
|
||||
|
||||
bobKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), key)
|
||||
bobKeys = append(bobKeys, bobKey)
|
||||
}
|
||||
|
||||
aliceCfg := channeldb.ChannelConfig{
|
||||
ChannelConstraints: channeldb.ChannelConstraints{
|
||||
DustLimit: aliceDustLimit,
|
||||
MaxPendingAmount: lnwire.NewMSatFromSatoshis(channelCapacity),
|
||||
ChanReserve: channelCapacity / 100,
|
||||
MinHTLC: 0,
|
||||
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
|
||||
CsvDelay: uint16(csvTimeoutAlice),
|
||||
},
|
||||
MultiSigKey: keychain.KeyDescriptor{
|
||||
PubKey: aliceKeys[0].PubKey(),
|
||||
},
|
||||
RevocationBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: aliceKeys[1].PubKey(),
|
||||
},
|
||||
PaymentBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: aliceKeys[2].PubKey(),
|
||||
},
|
||||
DelayBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: aliceKeys[3].PubKey(),
|
||||
},
|
||||
HtlcBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: aliceKeys[4].PubKey(),
|
||||
},
|
||||
}
|
||||
bobCfg := channeldb.ChannelConfig{
|
||||
ChannelConstraints: channeldb.ChannelConstraints{
|
||||
DustLimit: bobDustLimit,
|
||||
MaxPendingAmount: lnwire.NewMSatFromSatoshis(channelCapacity),
|
||||
ChanReserve: channelCapacity / 100,
|
||||
MinHTLC: 0,
|
||||
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
|
||||
CsvDelay: uint16(csvTimeoutBob),
|
||||
},
|
||||
MultiSigKey: keychain.KeyDescriptor{
|
||||
PubKey: bobKeys[0].PubKey(),
|
||||
},
|
||||
RevocationBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: bobKeys[1].PubKey(),
|
||||
},
|
||||
PaymentBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: bobKeys[2].PubKey(),
|
||||
},
|
||||
DelayBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: bobKeys[3].PubKey(),
|
||||
},
|
||||
HtlcBasePoint: keychain.KeyDescriptor{
|
||||
PubKey: bobKeys[4].PubKey(),
|
||||
},
|
||||
}
|
||||
|
||||
bobRoot, err := chainhash.NewHash(bobKeys[0].Serialize())
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
bobPreimageProducer := shachain.NewRevocationProducer(*bobRoot)
|
||||
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
bobCommitPoint := input.ComputeCommitmentPoint(bobFirstRevoke[:])
|
||||
|
||||
aliceRoot, err := chainhash.NewHash(aliceKeys[0].Serialize())
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
alicePreimageProducer := shachain.NewRevocationProducer(*aliceRoot)
|
||||
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:])
|
||||
|
||||
aliceCommitTx, bobCommitTx, err := CreateCommitmentTxns(
|
||||
channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint,
|
||||
bobCommitPoint, *fundingTxIn, chanType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
alicePath, err := ioutil.TempDir("", "alicedb")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
dbAlice, err := channeldb.Open(alicePath)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
bobPath, err := ioutil.TempDir("", "bobdb")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
dbBob, err := channeldb.Open(bobPath)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
estimator := chainfee.NewStaticEstimator(6000, 0)
|
||||
feePerKw, err := estimator.EstimateFeePerKW(1)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
commitFee := calcStaticFee(chanType, 0)
|
||||
var anchorAmt btcutil.Amount
|
||||
if chanType.HasAnchors() {
|
||||
anchorAmt += 2 * anchorSize
|
||||
}
|
||||
|
||||
aliceBalance := lnwire.NewMSatFromSatoshis(
|
||||
channelBal - commitFee - anchorAmt,
|
||||
)
|
||||
bobBalance := lnwire.NewMSatFromSatoshis(channelBal)
|
||||
|
||||
aliceCommit := channeldb.ChannelCommitment{
|
||||
CommitHeight: 0,
|
||||
LocalBalance: aliceBalance,
|
||||
RemoteBalance: bobBalance,
|
||||
CommitFee: commitFee,
|
||||
FeePerKw: btcutil.Amount(feePerKw),
|
||||
CommitTx: aliceCommitTx,
|
||||
CommitSig: testSigBytes,
|
||||
}
|
||||
bobCommit := channeldb.ChannelCommitment{
|
||||
CommitHeight: 0,
|
||||
LocalBalance: bobBalance,
|
||||
RemoteBalance: aliceBalance,
|
||||
CommitFee: commitFee,
|
||||
FeePerKw: btcutil.Amount(feePerKw),
|
||||
CommitTx: bobCommitTx,
|
||||
CommitSig: testSigBytes,
|
||||
}
|
||||
|
||||
var chanIDBytes [8]byte
|
||||
if _, err := io.ReadFull(rand.Reader, chanIDBytes[:]); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
shortChanID := lnwire.NewShortChanIDFromInt(
|
||||
binary.BigEndian.Uint64(chanIDBytes[:]),
|
||||
)
|
||||
|
||||
aliceChannelState := &channeldb.OpenChannel{
|
||||
LocalChanCfg: aliceCfg,
|
||||
RemoteChanCfg: bobCfg,
|
||||
IdentityPub: aliceKeys[0].PubKey(),
|
||||
FundingOutpoint: *prevOut,
|
||||
ShortChannelID: shortChanID,
|
||||
ChanType: chanType,
|
||||
IsInitiator: true,
|
||||
Capacity: channelCapacity,
|
||||
RemoteCurrentRevocation: bobCommitPoint,
|
||||
RevocationProducer: alicePreimageProducer,
|
||||
RevocationStore: shachain.NewRevocationStore(),
|
||||
LocalCommitment: aliceCommit,
|
||||
RemoteCommitment: aliceCommit,
|
||||
Db: dbAlice,
|
||||
Packager: channeldb.NewChannelPackager(shortChanID),
|
||||
FundingTxn: testTx,
|
||||
}
|
||||
bobChannelState := &channeldb.OpenChannel{
|
||||
LocalChanCfg: bobCfg,
|
||||
RemoteChanCfg: aliceCfg,
|
||||
IdentityPub: bobKeys[0].PubKey(),
|
||||
FundingOutpoint: *prevOut,
|
||||
ShortChannelID: shortChanID,
|
||||
ChanType: chanType,
|
||||
IsInitiator: false,
|
||||
Capacity: channelCapacity,
|
||||
RemoteCurrentRevocation: aliceCommitPoint,
|
||||
RevocationProducer: bobPreimageProducer,
|
||||
RevocationStore: shachain.NewRevocationStore(),
|
||||
LocalCommitment: bobCommit,
|
||||
RemoteCommitment: bobCommit,
|
||||
Db: dbBob,
|
||||
Packager: channeldb.NewChannelPackager(shortChanID),
|
||||
}
|
||||
|
||||
aliceSigner := &input.MockSigner{Privkeys: aliceKeys}
|
||||
bobSigner := &input.MockSigner{Privkeys: bobKeys}
|
||||
|
||||
// TODO(roasbeef): make mock version of pre-image store
|
||||
|
||||
alicePool := NewSigPool(1, aliceSigner)
|
||||
channelAlice, err := NewLightningChannel(
|
||||
aliceSigner, aliceChannelState, alicePool,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
alicePool.Start()
|
||||
|
||||
obfuscator := createStateHintObfuscator(aliceChannelState)
|
||||
|
||||
bobPool := NewSigPool(1, bobSigner)
|
||||
channelBob, err := NewLightningChannel(
|
||||
bobSigner, bobChannelState, bobPool,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
bobPool.Start()
|
||||
|
||||
err = SetStateNumHint(
|
||||
aliceCommitTx, 0, obfuscator,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
err = SetStateNumHint(
|
||||
bobCommitTx, 0, obfuscator,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
addr := &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 18556,
|
||||
}
|
||||
if err := channelAlice.channelState.SyncPending(addr, 101); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
addr = &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 18555,
|
||||
}
|
||||
|
||||
if err := channelBob.channelState.SyncPending(addr, 101); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
cleanUpFunc := func() {
|
||||
os.RemoveAll(bobPath)
|
||||
os.RemoveAll(alicePath)
|
||||
|
||||
alicePool.Stop()
|
||||
bobPool.Stop()
|
||||
}
|
||||
|
||||
// Now that the channel are open, simulate the start of a session by
|
||||
// having Alice and Bob extend their revocation windows to each other.
|
||||
err = initRevocationWindows(channelAlice, channelBob)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return channelAlice, channelBob, cleanUpFunc, nil
|
||||
}
|
||||
|
||||
// initRevocationWindows simulates a new channel being opened within the p2p
|
||||
// network by populating the initial revocation windows of the passed
|
||||
// commitment state machines.
|
||||
func initRevocationWindows(chanA, chanB *LightningChannel) error {
|
||||
aliceNextRevoke, err := chanA.NextRevocationKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chanB.InitNextRevocation(aliceNextRevoke); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bobNextRevoke, err := chanB.NextRevocationKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chanA.InitNextRevocation(bobNextRevoke); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// pubkeyFromHex parses a Bitcoin public key from a hex encoded string.
|
||||
func pubkeyFromHex(keyHex string) (*btcec.PublicKey, error) {
|
||||
bytes, err := hex.DecodeString(keyHex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return btcec.ParsePubKey(bytes, btcec.S256())
|
||||
}
|
||||
|
||||
// privkeyFromHex parses a Bitcoin private key from a hex encoded string.
|
||||
func privkeyFromHex(keyHex string) (*btcec.PrivateKey, error) {
|
||||
bytes, err := hex.DecodeString(keyHex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, _ := btcec.PrivKeyFromBytes(btcec.S256(), bytes)
|
||||
return key, nil
|
||||
|
||||
}
|
||||
|
||||
// blockFromHex parses a full Bitcoin block from a hex encoded string.
|
||||
func blockFromHex(blockHex string) (*btcutil.Block, error) {
|
||||
bytes, err := hex.DecodeString(blockHex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return btcutil.NewBlockFromBytes(bytes)
|
||||
}
|
||||
|
||||
// txFromHex parses a full Bitcoin transaction from a hex encoded string.
|
||||
func txFromHex(txHex string) (*btcutil.Tx, error) {
|
||||
bytes, err := hex.DecodeString(txHex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return btcutil.NewTxFromBytes(bytes)
|
||||
}
|
||||
|
||||
// calcStaticFee calculates appropriate fees for commitment transactions. This
|
||||
// function provides a simple way to allow test balance assertions to take fee
|
||||
// calculations into account.
|
||||
//
|
||||
// TODO(bvu): Refactor when dynamic fee estimation is added.
|
||||
func calcStaticFee(chanType channeldb.ChannelType, numHTLCs int) btcutil.Amount {
|
||||
const (
|
||||
htlcWeight = 172
|
||||
feePerKw = btcutil.Amount(24/4) * 1000
|
||||
)
|
||||
return feePerKw *
|
||||
(btcutil.Amount(CommitWeight(chanType) +
|
||||
htlcWeight*int64(numHTLCs))) / 1000
|
||||
}
|
||||
|
||||
// ForceStateTransition executes the necessary interaction between the two
|
||||
// commitment state machines to transition to a new state locking in any
|
||||
// pending updates. This method is useful when testing interactions between two
|
||||
// live state machines.
|
||||
func ForceStateTransition(chanA, chanB *LightningChannel) error {
|
||||
aliceSig, aliceHtlcSigs, _, err := chanA.SignNextCommitment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = chanB.ReceiveNewCommitment(aliceSig, aliceHtlcSigs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bobRevocation, _, err := chanB.RevokeCurrentCommitment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bobSig, bobHtlcSigs, _, err := chanB.SignNextCommitment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, _, _, _, err := chanA.ReceiveRevocation(bobRevocation); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chanA.ReceiveNewCommitment(bobSig, bobHtlcSigs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aliceRevocation, _, err := chanA.RevokeCurrentCommitment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, _, _, _, err := chanB.ReceiveRevocation(aliceRevocation); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
210
vendor/github.com/lightningnetwork/lnd/lnwallet/transactions.go
generated
vendored
Normal file
210
vendor/github.com/lightningnetwork/lnd/lnwallet/transactions.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
)
|
||||
|
||||
const (
|
||||
// StateHintSize is the total number of bytes used between the sequence
|
||||
// number and locktime of the commitment transaction use to encode a hint
|
||||
// to the state number of a particular commitment transaction.
|
||||
StateHintSize = 6
|
||||
|
||||
// MaxStateHint is the maximum state number we're able to encode using
|
||||
// StateHintSize bytes amongst the sequence number and locktime fields
|
||||
// of the commitment transaction.
|
||||
maxStateHint uint64 = (1 << 48) - 1
|
||||
)
|
||||
|
||||
var (
|
||||
// TimelockShift is used to make sure the commitment transaction is
|
||||
// spendable by setting the locktime with it so that it is larger than
|
||||
// 500,000,000, thus interpreting it as Unix epoch timestamp and not
|
||||
// a block height. It is also smaller than the current timestamp which
|
||||
// has bit (1 << 30) set, so there is no risk of having the commitment
|
||||
// transaction be rejected. This way we can safely use the lower 24 bits
|
||||
// of the locktime field for part of the obscured commitment transaction
|
||||
// number.
|
||||
TimelockShift = uint32(1 << 29)
|
||||
)
|
||||
|
||||
// createHtlcSuccessTx creates a transaction that spends the output on the
|
||||
// commitment transaction of the peer that receives an HTLC. This transaction
|
||||
// essentially acts as an off-chain covenant as it's only permitted to spend
|
||||
// the designated HTLC output, and also that spend can _only_ be used as a
|
||||
// state transition to create another output which actually allows redemption
|
||||
// or revocation of an HTLC.
|
||||
//
|
||||
// In order to spend the HTLC output, the witness for the passed transaction
|
||||
// should be:
|
||||
// * <0> <sender sig> <recvr sig> <preimage>
|
||||
func createHtlcSuccessTx(chanType channeldb.ChannelType,
|
||||
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay uint32,
|
||||
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
|
||||
|
||||
// Create a version two transaction (as the success version of this
|
||||
// spends an output with a CSV timeout).
|
||||
successTx := wire.NewMsgTx(2)
|
||||
|
||||
// The input to the transaction is the outpoint that creates the
|
||||
// original HTLC on the sender's commitment transaction. Set the
|
||||
// sequence number based on the channel type.
|
||||
txin := &wire.TxIn{
|
||||
PreviousOutPoint: htlcOutput,
|
||||
Sequence: HtlcSecondLevelInputSequence(chanType),
|
||||
}
|
||||
successTx.AddTxIn(txin)
|
||||
|
||||
// Next, we'll generate the script used as the output for all second
|
||||
// level HTLC which forces a covenant w.r.t what can be done with all
|
||||
// HTLC outputs.
|
||||
witnessScript, err := input.SecondLevelHtlcScript(revocationKey, delayKey,
|
||||
csvDelay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkScript, err := input.WitnessScriptHash(witnessScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, the output is simply the amount of the HTLC (minus the
|
||||
// required fees), paying to the timeout script.
|
||||
successTx.AddTxOut(&wire.TxOut{
|
||||
Value: int64(htlcAmt),
|
||||
PkScript: pkScript,
|
||||
})
|
||||
|
||||
return successTx, nil
|
||||
}
|
||||
|
||||
// createHtlcTimeoutTx creates a transaction that spends the HTLC output on the
|
||||
// commitment transaction of the peer that created an HTLC (the sender). This
|
||||
// transaction essentially acts as an off-chain covenant as it spends a 2-of-2
|
||||
// multi-sig output. This output requires a signature from both the sender and
|
||||
// receiver of the HTLC. By using a distinct transaction, we're able to
|
||||
// uncouple the timeout and delay clauses of the HTLC contract. This
|
||||
// transaction is locked with an absolute lock-time so the sender can only
|
||||
// attempt to claim the output using it after the lock time has passed.
|
||||
//
|
||||
// In order to spend the HTLC output, the witness for the passed transaction
|
||||
// should be:
|
||||
// * <0> <sender sig> <receiver sig> <0>
|
||||
//
|
||||
// NOTE: The passed amount for the HTLC should take into account the required
|
||||
// fee rate at the time the HTLC was created. The fee should be able to
|
||||
// entirely pay for this (tiny: 1-in 1-out) transaction.
|
||||
func createHtlcTimeoutTx(chanType channeldb.ChannelType,
|
||||
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||
cltvExpiry, csvDelay uint32,
|
||||
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
|
||||
|
||||
// Create a version two transaction (as the success version of this
|
||||
// spends an output with a CSV timeout), and set the lock-time to the
|
||||
// specified absolute lock-time in blocks.
|
||||
timeoutTx := wire.NewMsgTx(2)
|
||||
timeoutTx.LockTime = cltvExpiry
|
||||
|
||||
// The input to the transaction is the outpoint that creates the
|
||||
// original HTLC on the sender's commitment transaction. Set the
|
||||
// sequence number based on the channel type.
|
||||
txin := &wire.TxIn{
|
||||
PreviousOutPoint: htlcOutput,
|
||||
Sequence: HtlcSecondLevelInputSequence(chanType),
|
||||
}
|
||||
timeoutTx.AddTxIn(txin)
|
||||
|
||||
// Next, we'll generate the script used as the output for all second
|
||||
// level HTLC which forces a covenant w.r.t what can be done with all
|
||||
// HTLC outputs.
|
||||
witnessScript, err := input.SecondLevelHtlcScript(revocationKey, delayKey,
|
||||
csvDelay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkScript, err := input.WitnessScriptHash(witnessScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, the output is simply the amount of the HTLC (minus the
|
||||
// required fees), paying to the regular second level HTLC script.
|
||||
timeoutTx.AddTxOut(&wire.TxOut{
|
||||
Value: int64(htlcAmt),
|
||||
PkScript: pkScript,
|
||||
})
|
||||
|
||||
return timeoutTx, nil
|
||||
}
|
||||
|
||||
// SetStateNumHint encodes the current state number within the passed
|
||||
// commitment transaction by re-purposing the locktime and sequence fields in
|
||||
// the commitment transaction to encode the obfuscated state number. The state
|
||||
// number is encoded using 48 bits. The lower 24 bits of the lock time are the
|
||||
// lower 24 bits of the obfuscated state number and the lower 24 bits of the
|
||||
// sequence field are the higher 24 bits. Finally before encoding, the
|
||||
// obfuscator is XOR'd against the state number in order to hide the exact
|
||||
// state number from the PoV of outside parties.
|
||||
func SetStateNumHint(commitTx *wire.MsgTx, stateNum uint64,
|
||||
obfuscator [StateHintSize]byte) error {
|
||||
|
||||
// With the current schema we are only able to encode state num
|
||||
// hints up to 2^48. Therefore if the passed height is greater than our
|
||||
// state hint ceiling, then exit early.
|
||||
if stateNum > maxStateHint {
|
||||
return fmt.Errorf("unable to encode state, %v is greater "+
|
||||
"state num that max of %v", stateNum, maxStateHint)
|
||||
}
|
||||
|
||||
if len(commitTx.TxIn) != 1 {
|
||||
return fmt.Errorf("commitment tx must have exactly 1 input, "+
|
||||
"instead has %v", len(commitTx.TxIn))
|
||||
}
|
||||
|
||||
// Convert the obfuscator into a uint64, then XOR that against the
|
||||
// targeted height in order to obfuscate the state number of the
|
||||
// commitment transaction in the case that either commitment
|
||||
// transaction is broadcast directly on chain.
|
||||
var obfs [8]byte
|
||||
copy(obfs[2:], obfuscator[:])
|
||||
xorInt := binary.BigEndian.Uint64(obfs[:])
|
||||
|
||||
stateNum = stateNum ^ xorInt
|
||||
|
||||
// Set the height bit of the sequence number in order to disable any
|
||||
// sequence locks semantics.
|
||||
commitTx.TxIn[0].Sequence = uint32(stateNum>>24) | wire.SequenceLockTimeDisabled
|
||||
commitTx.LockTime = uint32(stateNum&0xFFFFFF) | TimelockShift
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStateNumHint recovers the current state number given a commitment
|
||||
// transaction which has previously had the state number encoded within it via
|
||||
// setStateNumHint and a shared obfuscator.
|
||||
//
|
||||
// See setStateNumHint for further details w.r.t exactly how the state-hints
|
||||
// are encoded.
|
||||
func GetStateNumHint(commitTx *wire.MsgTx, obfuscator [StateHintSize]byte) uint64 {
|
||||
// Convert the obfuscator into a uint64, this will be used to
|
||||
// de-obfuscate the final recovered state number.
|
||||
var obfs [8]byte
|
||||
copy(obfs[2:], obfuscator[:])
|
||||
xorInt := binary.BigEndian.Uint64(obfs[:])
|
||||
|
||||
// Retrieve the state hint from the sequence number and locktime
|
||||
// of the transaction.
|
||||
stateNumXor := uint64(commitTx.TxIn[0].Sequence&0xFFFFFF) << 24
|
||||
stateNumXor |= uint64(commitTx.LockTime & 0xFFFFFF)
|
||||
|
||||
// Finally, to obtain the final state number, we XOR by the obfuscator
|
||||
// value to de-obfuscate the state number.
|
||||
return stateNumXor ^ xorInt
|
||||
}
|
||||
1816
vendor/github.com/lightningnetwork/lnd/lnwallet/wallet.go
generated
vendored
Normal file
1816
vendor/github.com/lightningnetwork/lnd/lnwallet/wallet.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user