Release v0.3.0

This commit is contained in:
Manu Herrera
2020-11-09 10:05:29 -03:00
parent 4e9aa7a3c5
commit 8107c4478b
1265 changed files with 440488 additions and 107809 deletions

View File

@@ -0,0 +1,26 @@
lnwallet
=========
[![Build Status](http://img.shields.io/travis/lightningnetwork/lnd.svg)](https://travis-ci.org/lightningnetwork/lnd)
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/lightningnetwork/lnd/blob/master/LICENSE)
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](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
```

View 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)

View 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
}

View 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))
}

View 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()
}

View 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)

View 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
}

View 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
}

View 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
}

View 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)

File diff suppressed because it is too large Load Diff

View 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
}

View 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]
}

View 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
}

View 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
}

View 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)
}

View 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
View 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)
}

View 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)
}

View 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
}

View 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
}

View 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
}

View 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

File diff suppressed because it is too large Load Diff