Merge pull request #3557 from guggero/loop-sweeper-integration

input+sweep: rework witness type into interface to be used externally
This commit is contained in:
Conner Fromknecht authored and GitHub committed 2019-10-16 13:09:03 +02:00
commit 93247d0304
12 files changed
+287 -207

No files matched your search

+13 -32
View File
@@ -821,7 +821,7 @@ func (b *breachArbiter) handleBreachHandoff(breachEvent *ContractBreachEvent) {
type breachedOutput struct { type breachedOutput struct {
amt btcutil.Amount amt btcutil.Amount
outpoint wire.OutPoint outpoint wire.OutPoint
witnessType input.WitnessType witnessType input.StandardWitnessType
signDesc input.SignDescriptor signDesc input.SignDescriptor
confHeight uint32 confHeight uint32
@@ -833,7 +833,7 @@ type breachedOutput struct {
// makeBreachedOutput assembles a new breachedOutput that can be used by the // makeBreachedOutput assembles a new breachedOutput that can be used by the
// breach arbiter to construct a justice or sweep transaction. // breach arbiter to construct a justice or sweep transaction.
func makeBreachedOutput(outpoint *wire.OutPoint, func makeBreachedOutput(outpoint *wire.OutPoint,
witnessType input.WitnessType, witnessType input.StandardWitnessType,
secondLevelScript []byte, secondLevelScript []byte,
signDescriptor *input.SignDescriptor, signDescriptor *input.SignDescriptor,
confHeight uint32) breachedOutput { confHeight uint32) breachedOutput {
@@ -883,9 +883,7 @@ func (bo *breachedOutput) CraftInputScript(signer input.Signer, txn *wire.MsgTx,
// First, we ensure that the witness generation function has been // First, we ensure that the witness generation function has been
// initialized for this breached output. // initialized for this breached output.
bo.witnessFunc = bo.witnessType.GenWitnessFunc( bo.witnessFunc = bo.witnessType.WitnessGenerator(signer, bo.SignDesc())
signer, bo.SignDesc(),
)
// Now that we have ensured that the witness generation function has // Now that we have ensured that the witness generation function has
// been initialized, we can proceed to execute it and generate the // been initialized, we can proceed to execute it and generate the
@@ -993,7 +991,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
// Using the breachedHtlc's incoming flag, determine the // Using the breachedHtlc's incoming flag, determine the
// appropriate witness type that needs to be generated in order // appropriate witness type that needs to be generated in order
// to sweep the HTLC output. // to sweep the HTLC output.
var htlcWitnessType input.WitnessType var htlcWitnessType input.StandardWitnessType
if breachedHtlc.IsIncoming { if breachedHtlc.IsIncoming {
htlcWitnessType = input.HtlcAcceptedRevoke htlcWitnessType = input.HtlcAcceptedRevoke
} else { } else {
@@ -1051,32 +1049,15 @@ func (b *breachArbiter) createJusticeTx(
// Grab locally scoped reference to breached output. // Grab locally scoped reference to breached output.
inp := &r.breachedOutputs[i] inp := &r.breachedOutputs[i]
// First, select the appropriate estimated witness weight for // First, determine the appropriate estimated witness weight for
// the give witness type of this breached output. If the witness // the give witness type of this breached output. If the witness
// type is unrecognized, we will omit it from the transaction. // weight cannot be estimated, we will omit it from the
var witnessWeight int // transaction.
switch inp.WitnessType() { witnessWeight, _, err := inp.WitnessType().SizeUpperBound()
case input.CommitSpendNoDelayTweakless: if err != nil {
fallthrough brarLog.Warnf("could not determine witness weight "+
case input.CommitmentNoDelay: "for breached output in retribution info: %v",
witnessWeight = input.P2WKHWitnessSize err)
case input.CommitmentRevoke:
witnessWeight = input.ToLocalPenaltyWitnessSize
case input.HtlcOfferedRevoke:
witnessWeight = input.OfferedHtlcPenaltyWitnessSize
case input.HtlcAcceptedRevoke:
witnessWeight = input.AcceptedHtlcPenaltyWitnessSize
case input.HtlcSecondLevelRevoke:
witnessWeight = input.ToLocalPenaltyWitnessSize
default:
brarLog.Warnf("breached output in retribution info "+
"contains unexpected witness type: %v",
inp.WitnessType())
continue continue
} }
weightEstimate.AddWitnessInput(witnessWeight) weightEstimate.AddWitnessInput(witnessWeight)
@@ -1555,7 +1536,7 @@ func (bo *breachedOutput) Decode(r io.Reader) error {
if _, err := io.ReadFull(r, scratch[:2]); err != nil { if _, err := io.ReadFull(r, scratch[:2]); err != nil {
return err return err
} }
bo.witnessType = input.WitnessType( bo.witnessType = input.StandardWitnessType(
binary.BigEndian.Uint16(scratch[:2]), binary.BigEndian.Uint16(scratch[:2]),
) )
+1 -3
View File
@@ -114,9 +114,7 @@ func NewBaseInput(outpoint *wire.OutPoint, witnessType WitnessType,
func (bi *BaseInput) CraftInputScript(signer Signer, txn *wire.MsgTx, func (bi *BaseInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) (*Script, error) { hashCache *txscript.TxSigHashes, txinIdx int) (*Script, error) {
witnessFunc := bi.witnessType.GenWitnessFunc( witnessFunc := bi.witnessType.WitnessGenerator(signer, bi.SignDesc())
signer, bi.SignDesc(),
)
return witnessFunc(txn, hashCache, txinIdx) return witnessFunc(txn, hashCache, txinIdx)
} }
+173 -30
View File
@@ -7,87 +7,125 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
) )
// WitnessType determines how an output's witness will be generated. The // WitnessGenerator represents a function that is able to generate the final
// default commitmentTimeLock type will generate a witness that will allow // witness for a particular public key script. Additionally, if required, this
// spending of a time-locked transaction enforced by CheckSequenceVerify. // function will also return the sigScript for spending nested P2SH witness
type WitnessType uint16 // outputs. This function acts as an abstraction layer, hiding the details of
// the underlying script.
type WitnessGenerator func(tx *wire.MsgTx, hc *txscript.TxSigHashes,
inputIndex int) (*Script, error)
// WitnessType determines how an output's witness will be generated. This
// interface can be implemented to be used for custom sweep scripts if the
// pre-defined StandardWitnessType list doesn't provide a suitable one.
type WitnessType interface {
// String returns a human readable version of the WitnessType.
String() string
// WitnessGenerator will return a WitnessGenerator function that an
// output uses to generate the witness and optionally the sigScript for
// a sweep transaction.
WitnessGenerator(signer Signer,
descriptor *SignDescriptor) WitnessGenerator
// SizeUpperBound returns the maximum length of the witness of this
// WitnessType if it would be included in a tx. It also returns if the
// output itself is a nested p2sh output, if so then we need to take
// into account the extra sigScript data size.
SizeUpperBound() (int, bool, error)
// AddWeightEstimation adds the estimated size of the witness in bytes
// to the given weight estimator and returns the number of
// CSVs/CLTVs used by the script.
AddWeightEstimation(estimator *TxWeightEstimator) (int, int, error)
}
// StandardWitnessType is a numeric representation of standard pre-defined types
// of witness configurations.
type StandardWitnessType uint16
// A compile time check to ensure StandardWitnessType implements the
// WitnessType interface.
var _ WitnessType = (StandardWitnessType)(0)
const ( const (
// CommitmentTimeLock is a witness that allows us to spend the output of // CommitmentTimeLock is a witness that allows us to spend the output of
// a commitment transaction after a relative lock-time lockout. // a commitment transaction after a relative lock-time lockout.
CommitmentTimeLock WitnessType = 0 CommitmentTimeLock StandardWitnessType = 0
// CommitmentNoDelay is a witness that allows us to spend a settled // CommitmentNoDelay is a witness that allows us to spend a settled
// no-delay output immediately on a counterparty's commitment // no-delay output immediately on a counterparty's commitment
// transaction. // transaction.
CommitmentNoDelay WitnessType = 1 CommitmentNoDelay StandardWitnessType = 1
// CommitmentRevoke is a witness that allows us to sweep the settled // CommitmentRevoke is a witness that allows us to sweep the settled
// output of a malicious counterparty's who broadcasts a revoked // output of a malicious counterparty's who broadcasts a revoked
// commitment transaction. // commitment transaction.
CommitmentRevoke WitnessType = 2 CommitmentRevoke StandardWitnessType = 2
// HtlcOfferedRevoke is a witness that allows us to sweep an HTLC which // HtlcOfferedRevoke is a witness that allows us to sweep an HTLC which
// we offered to the remote party in the case that they broadcast a // we offered to the remote party in the case that they broadcast a
// revoked commitment state. // revoked commitment state.
HtlcOfferedRevoke WitnessType = 3 HtlcOfferedRevoke StandardWitnessType = 3
// HtlcAcceptedRevoke is a witness that allows us to sweep an HTLC // HtlcAcceptedRevoke is a witness that allows us to sweep an HTLC
// output sent to us in the case that the remote party broadcasts a // output sent to us in the case that the remote party broadcasts a
// revoked commitment state. // revoked commitment state.
HtlcAcceptedRevoke WitnessType = 4 HtlcAcceptedRevoke StandardWitnessType = 4
// HtlcOfferedTimeoutSecondLevel is a witness that allows us to sweep // HtlcOfferedTimeoutSecondLevel is a witness that allows us to sweep
// an HTLC output that we extended to a party, but was never fulfilled. // an HTLC output that we extended to a party, but was never fulfilled.
// This HTLC output isn't directly on the commitment transaction, but // This HTLC output isn't directly on the commitment transaction, but
// is the result of a confirmed second-level HTLC transaction. As a // is the result of a confirmed second-level HTLC transaction. As a
// result, we can only spend this after a CSV delay. // result, we can only spend this after a CSV delay.
HtlcOfferedTimeoutSecondLevel WitnessType = 5 HtlcOfferedTimeoutSecondLevel StandardWitnessType = 5
// HtlcAcceptedSuccessSecondLevel is a witness that allows us to sweep // HtlcAcceptedSuccessSecondLevel is a witness that allows us to sweep
// an HTLC output that was offered to us, and for which we have a // an HTLC output that was offered to us, and for which we have a
// payment preimage. This HTLC output isn't directly on our commitment // payment preimage. This HTLC output isn't directly on our commitment
// transaction, but is the result of confirmed second-level HTLC // transaction, but is the result of confirmed second-level HTLC
// transaction. As a result, we can only spend this after a CSV delay. // transaction. As a result, we can only spend this after a CSV delay.
HtlcAcceptedSuccessSecondLevel WitnessType = 6 HtlcAcceptedSuccessSecondLevel StandardWitnessType = 6
// HtlcOfferedRemoteTimeout is a witness that allows us to sweep an // HtlcOfferedRemoteTimeout is a witness that allows us to sweep an
// HTLC that we offered to the remote party which lies in the // HTLC that we offered to the remote party which lies in the
// commitment transaction of the remote party. We can spend this output // commitment transaction of the remote party. We can spend this output
// after the absolute CLTV timeout of the HTLC as passed. // after the absolute CLTV timeout of the HTLC as passed.
HtlcOfferedRemoteTimeout WitnessType = 7 HtlcOfferedRemoteTimeout StandardWitnessType = 7
// HtlcAcceptedRemoteSuccess is a witness that allows us to sweep an // HtlcAcceptedRemoteSuccess is a witness that allows us to sweep an
// HTLC that was offered to us by the remote party. We use this witness // HTLC that was offered to us by the remote party. We use this witness
// in the case that the remote party goes to chain, and we know the // in the case that the remote party goes to chain, and we know the
// pre-image to the HTLC. We can sweep this without any additional // pre-image to the HTLC. We can sweep this without any additional
// timeout. // timeout.
HtlcAcceptedRemoteSuccess WitnessType = 8 HtlcAcceptedRemoteSuccess StandardWitnessType = 8
// HtlcSecondLevelRevoke is a witness that allows us to sweep an HTLC // HtlcSecondLevelRevoke is a witness that allows us to sweep an HTLC
// from the remote party's commitment transaction in the case that the // from the remote party's commitment transaction in the case that the
// broadcast a revoked commitment, but then also immediately attempt to // broadcast a revoked commitment, but then also immediately attempt to
// go to the second level to claim the HTLC. // go to the second level to claim the HTLC.
HtlcSecondLevelRevoke WitnessType = 9 HtlcSecondLevelRevoke StandardWitnessType = 9
// WitnessKeyHash is a witness type that allows us to spend a regular // WitnessKeyHash is a witness type that allows us to spend a regular
// p2wkh output that's sent to an output which is under complete // p2wkh output that's sent to an output which is under complete
// control of the backing wallet. // control of the backing wallet.
WitnessKeyHash WitnessType = 10 WitnessKeyHash StandardWitnessType = 10
// NestedWitnessKeyHash is a witness type that allows us to sweep an // NestedWitnessKeyHash is a witness type that allows us to sweep an
// output that sends to a nested P2SH script that pays to a key solely // output that sends to a nested P2SH script that pays to a key solely
// under our control. The witness generated needs to include the // under our control. The witness generated needs to include the
NestedWitnessKeyHash WitnessType = 11 NestedWitnessKeyHash StandardWitnessType = 11
// CommitSpendNoDelayTweakless is similar to the CommitSpendNoDelay // CommitSpendNoDelayTweakless is similar to the CommitSpendNoDelay
// type, but it omits the tweak that randomizes the key we need to // type, but it omits the tweak that randomizes the key we need to
// spend with a channel peer supplied set of randomness. // spend with a channel peer supplied set of randomness.
CommitSpendNoDelayTweakless = 12 CommitSpendNoDelayTweakless StandardWitnessType = 12
) )
// Stirng returns a human readable version of the target WitnessType. // String returns a human readable version of the target WitnessType.
func (wt WitnessType) String() string { //
// NOTE: This is part of the WitnessType interface.
func (wt StandardWitnessType) String() string {
switch wt { switch wt {
case CommitmentTimeLock: case CommitmentTimeLock:
return "CommitmentTimeLock" return "CommitmentTimeLock"
@@ -127,19 +165,13 @@ func (wt WitnessType) String() string {
} }
} }
// WitnessGenerator represents a function which is able to generate the final // WitnessGenerator will return a WitnessGenerator function that an output uses
// witness for a particular public key script. Additionally, if required, this
// function will also return the sigScript for spending nested P2SH witness
// outputs. This function acts as an abstraction layer, hiding the details of
// the underlying script.
type WitnessGenerator func(tx *wire.MsgTx, hc *txscript.TxSigHashes,
inputIndex int) (*Script, error)
// GenWitnessFunc will return a WitnessGenerator function that an output uses
// to generate the witness and optionally the sigScript for a sweep // to generate the witness and optionally the sigScript for a sweep
// transaction. The sigScript will be generated if the witness type warrants // transaction. The sigScript will be generated if the witness type warrants
// one for spending, such as the NestedWitnessKeyHash witness type. // one for spending, such as the NestedWitnessKeyHash witness type.
func (wt WitnessType) GenWitnessFunc(signer Signer, //
// NOTE: This is part of the WitnessType interface.
func (wt StandardWitnessType) WitnessGenerator(signer Signer,
descriptor *SignDescriptor) WitnessGenerator { descriptor *SignDescriptor) WitnessGenerator {
return func(tx *wire.MsgTx, hc *txscript.TxSigHashes, return func(tx *wire.MsgTx, hc *txscript.TxSigHashes,
@@ -262,5 +294,116 @@ func (wt WitnessType) GenWitnessFunc(signer Signer,
return nil, fmt.Errorf("unknown witness type: %v", wt) return nil, fmt.Errorf("unknown witness type: %v", wt)
} }
} }
}
// SizeUpperBound returns the maximum length of the witness of this witness
// type if it would be included in a tx. We also return if the output itself is
// a nested p2sh output, if so then we need to take into account the extra
// sigScript data size.
//
// NOTE: This is part of the WitnessType interface.
func (wt StandardWitnessType) SizeUpperBound() (int, bool, error) {
switch wt {
// Outputs on a remote commitment transaction that pay directly to us.
case CommitSpendNoDelayTweakless:
fallthrough
case WitnessKeyHash:
fallthrough
case CommitmentNoDelay:
return P2WKHWitnessSize, false, nil
// Outputs on a past commitment transaction that pay directly
// to us.
case CommitmentTimeLock:
return ToLocalTimeoutWitnessSize, false, nil
// Outgoing second layer HTLC's that have confirmed within the
// chain, and the output they produced is now mature enough to
// sweep.
case HtlcOfferedTimeoutSecondLevel:
return ToLocalTimeoutWitnessSize, false, nil
// Incoming second layer HTLC's that have confirmed within the
// chain, and the output they produced is now mature enough to
// sweep.
case HtlcAcceptedSuccessSecondLevel:
return ToLocalTimeoutWitnessSize, false, nil
// An HTLC on the commitment transaction of the remote party,
// that has had its absolute timelock expire.
case HtlcOfferedRemoteTimeout:
return AcceptedHtlcTimeoutWitnessSize, false, nil
// An HTLC on the commitment transaction of the remote party,
// that can be swept with the preimage.
case HtlcAcceptedRemoteSuccess:
return OfferedHtlcSuccessWitnessSize, false, nil
// A nested P2SH input that has a p2wkh witness script. We'll mark this
// as nested P2SH so the caller can estimate the weight properly
// including the sigScript.
case NestedWitnessKeyHash:
return P2WKHWitnessSize, true, nil
// The revocation output on a revoked commitment transaction.
case CommitmentRevoke:
return ToLocalPenaltyWitnessSize, false, nil
// The revocation output on a revoked HTLC that we offered to the remote
// party.
case HtlcOfferedRevoke:
return OfferedHtlcPenaltyWitnessSize, false, nil
// The revocation output on a revoked HTLC that was sent to us.
case HtlcAcceptedRevoke:
return AcceptedHtlcPenaltyWitnessSize, false, nil
// The revocation output of a second level output of an HTLC.
case HtlcSecondLevelRevoke:
return ToLocalPenaltyWitnessSize, false, nil
}
return 0, false, fmt.Errorf("unexpected witness type: %v", wt)
}
// AddWeightEstimation adds the estimated size of the witness in bytes to the
// given weight estimator and returns the number of CSVs/CLTVs used by the
// script.
//
// NOTE: This is part of the WitnessType interface.
func (wt StandardWitnessType) AddWeightEstimation(
estimator *TxWeightEstimator) (int, int, error) {
var (
csvCount = 0
cltvCount = 0
)
// For fee estimation purposes, we'll now attempt to obtain an
// upper bound on the weight this input will add when fully
// populated.
size, isNestedP2SH, err := wt.SizeUpperBound()
if err != nil {
return 0, 0, err
}
// If this is a nested P2SH input, then we'll need to factor in
// the additional data push within the sigScript.
if isNestedP2SH {
estimator.AddNestedP2WSHInput(size)
} else {
estimator.AddWitnessInput(size)
}
switch wt {
case CommitmentTimeLock,
HtlcOfferedTimeoutSecondLevel,
HtlcAcceptedSuccessSecondLevel:
csvCount++
case HtlcOfferedRemoteTimeout:
cltvCount++
}
return csvCount, cltvCount, nil
} }
+1 -2
View File
@@ -764,7 +764,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
sweep.DefaultBatchWindowDuration) sweep.DefaultBatchWindowDuration)
sweeperStore, err := sweep.NewSweeperStore( sweeperStore, err := sweep.NewSweeperStore(
chanDB, activeNetParams.GenesisHash, chanDB.DB, activeNetParams.GenesisHash,
) )
if err != nil { if err != nil {
srvrLog.Errorf("unable to create sweeper store: %v", err) srvrLog.Errorf("unable to create sweeper store: %v", err)
@@ -780,7 +780,6 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
return time.NewTimer(sweep.DefaultBatchWindowDuration).C return time.NewTimer(sweep.DefaultBatchWindowDuration).C
}, },
Notifier: cc.chainNotifier, Notifier: cc.chainNotifier,
ChainIO: cc.chainIO,
Store: sweeperStore, Store: sweeperStore,
MaxInputsPerTx: sweep.DefaultMaxInputsPerTx, MaxInputsPerTx: sweep.DefaultMaxInputsPerTx,
MaxSweepAttempts: sweep.DefaultMaxSweepAttempts, MaxSweepAttempts: sweep.DefaultMaxSweepAttempts,
+2 -3
View File
@@ -9,7 +9,6 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/coreos/bbolt" "github.com/coreos/bbolt"
"github.com/lightningnetwork/lnd/channeldb"
) )
var ( var (
@@ -57,11 +56,11 @@ type SweeperStore interface {
} }
type sweeperStore struct { type sweeperStore struct {
db *channeldb.DB db *bbolt.DB
} }
// NewSweeperStore returns a new store instance. // NewSweeperStore returns a new store instance.
func NewSweeperStore(db *channeldb.DB, chainHash *chainhash.Hash) ( func NewSweeperStore(db *bbolt.DB, chainHash *chainhash.Hash) (
SweeperStore, error) { SweeperStore, error) {
err := db.Update(func(tx *bbolt.Tx) error { err := db.Update(func(tx *bbolt.Tx) error {
+1 -1
View File
@@ -53,7 +53,7 @@ func TestStore(t *testing.T) {
testStore(t, func() (SweeperStore, error) { testStore(t, func() (SweeperStore, error) {
var chain chainhash.Hash var chain chainhash.Hash
return NewSweeperStore(cdb, &chain) return NewSweeperStore(cdb.DB, &chain)
}) })
}) })
t.Run("mock", func(t *testing.T) { t.Run("mock", func(t *testing.T) {
+19 -25
View File
@@ -212,9 +212,6 @@ type UtxoSweeperConfig struct {
// certain on-chain events. // certain on-chain events.
Notifier chainntnfs.ChainNotifier Notifier chainntnfs.ChainNotifier
// ChainIO is used to determine the current block height.
ChainIO lnwallet.BlockChainIO
// Store stores the published sweeper txes. // Store stores the published sweeper txes.
Store SweeperStore Store SweeperStore
@@ -323,20 +320,10 @@ func (s *UtxoSweeper) Start() error {
// not change from here on. // not change from here on.
s.relayFeeRate = s.cfg.FeeEstimator.RelayFeePerKW() s.relayFeeRate = s.cfg.FeeEstimator.RelayFeePerKW()
// Register for block epochs to retry sweeping every block. // We need to register for block epochs and retry sweeping every block.
bestHash, bestHeight, err := s.cfg.ChainIO.GetBestBlock() // We should get a notification with the current best block immediately
if err != nil { // if we don't provide any epoch. We'll wait for that in the collector.
return fmt.Errorf("get best block: %v", err) blockEpochs, err := s.cfg.Notifier.RegisterBlockEpochNtfn(nil)
}
log.Debugf("Best height: %v", bestHeight)
blockEpochs, err := s.cfg.Notifier.RegisterBlockEpochNtfn(
&chainntnfs.BlockEpoch{
Height: bestHeight,
Hash: bestHash,
},
)
if err != nil { if err != nil {
return fmt.Errorf("register block epoch ntfn: %v", err) return fmt.Errorf("register block epoch ntfn: %v", err)
} }
@@ -347,10 +334,7 @@ func (s *UtxoSweeper) Start() error {
defer blockEpochs.Cancel() defer blockEpochs.Cancel()
defer s.wg.Done() defer s.wg.Done()
err := s.collector(blockEpochs.Epochs, bestHeight) s.collector(blockEpochs.Epochs)
if err != nil {
log.Errorf("sweeper stopped: %v", err)
}
}() }()
return nil return nil
@@ -445,8 +429,18 @@ func (s *UtxoSweeper) feeRateForPreference(
// collector is the sweeper main loop. It processes new inputs, spend // collector is the sweeper main loop. It processes new inputs, spend
// notifications and counts down to publication of the sweep tx. // notifications and counts down to publication of the sweep tx.
func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch, func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch) {
bestHeight int32) error { // We registered for the block epochs with a nil request. The notifier
// should send us the current best block immediately. So we need to wait
// for it here because we need to know the current best height.
var bestHeight int32
select {
case bestBlock := <-blockEpochs:
bestHeight = bestBlock.Height
case <-s.quit:
return
}
for { for {
select { select {
@@ -622,7 +616,7 @@ func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch,
// sweep. // sweep.
case epoch, ok := <-blockEpochs: case epoch, ok := <-blockEpochs:
if !ok { if !ok {
return nil return
} }
bestHeight = epoch.Height bestHeight = epoch.Height
@@ -635,7 +629,7 @@ func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch,
} }
case <-s.quit: case <-s.quit:
return nil return
} }
} }
} }
+2 -3
View File
@@ -130,9 +130,8 @@ func createSweeperTestContext(t *testing.T) *sweeperTestContext {
ctx.timeoutChan <- c ctx.timeoutChan <- c
return c return c
}, },
Store: store, Store: store,
Signer: &mockSigner{}, Signer: &mockSigner{},
ChainIO: &mockChainIO{},
GenSweepScript: func() ([]byte, error) { GenSweepScript: func() ([]byte, error) {
script := []byte{outputScriptCount} script := []byte{outputScriptCount}
outputScriptCount++ outputScriptCount++
+17 -29
View File
@@ -10,12 +10,12 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
) )
var ( var (
defaultTestTimeout = 5 * time.Second defaultTestTimeout = 5 * time.Second
mockChainIOHeight = int32(100) mockChainHash, _ = chainhash.NewHashFromStr("00aabbccddeeff")
mockChainHeight = int32(100)
) )
type mockSigner struct { type mockSigner struct {
@@ -155,12 +155,22 @@ func (m *MockNotifier) RegisterBlockEpochNtfn(
log.Tracef("Mock block ntfn registered") log.Tracef("Mock block ntfn registered")
m.mutex.Lock() m.mutex.Lock()
epochChan := make(chan *chainntnfs.BlockEpoch, 0) epochChan := make(chan *chainntnfs.BlockEpoch, 1)
bestHeight := int32(0)
if bestBlock != nil { // The real notifier returns a notification with the current block hash
bestHeight = bestBlock.Height // and height immediately if no best block hash or height is specified
// in the request. We want to emulate this behaviour as well for the
// mock.
switch {
case bestBlock == nil:
epochChan <- &chainntnfs.BlockEpoch{
Hash: mockChainHash,
Height: mockChainHeight,
}
m.epochChan[epochChan] = mockChainHeight
default:
m.epochChan[epochChan] = bestBlock.Height
} }
m.epochChan[epochChan] = bestHeight
m.mutex.Unlock() m.mutex.Unlock()
return &chainntnfs.BlockEpochEvent{ return &chainntnfs.BlockEpochEvent{
@@ -235,25 +245,3 @@ func (m *MockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
}, },
}, nil }, nil
} }
type mockChainIO struct{}
var _ lnwallet.BlockChainIO = (*mockChainIO)(nil)
func (m *mockChainIO) GetBestBlock() (*chainhash.Hash, int32, error) {
return nil, mockChainIOHeight, nil
}
func (m *mockChainIO) GetUtxo(op *wire.OutPoint, pkScript []byte,
heightHint uint32, _ <-chan struct{}) (*wire.TxOut, error) {
return nil, nil
}
func (m *mockChainIO) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
return nil, nil
}
func (m *mockChainIO) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
return nil, nil
}
+6 -76
View File
@@ -56,7 +56,7 @@ func generateInputPartitionings(sweepableInputs []input.Input,
// on the signature length, which is not known yet at this point. // on the signature length, which is not known yet at this point.
yields := make(map[wire.OutPoint]int64) yields := make(map[wire.OutPoint]int64)
for _, input := range sweepableInputs { for _, input := range sweepableInputs {
size, _, err := getInputWitnessSizeUpperBound(input) size, _, err := input.WitnessType().SizeUpperBound()
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"failed adding input weight: %v", err) "failed adding input weight: %v", err)
@@ -126,7 +126,7 @@ func getPositiveYieldInputs(sweepableInputs []input.Input, maxInputs int,
for idx, input := range sweepableInputs { for idx, input := range sweepableInputs {
// Can ignore error, because it has already been checked when // Can ignore error, because it has already been checked when
// calculating the yields. // calculating the yields.
size, isNestedP2SH, _ := getInputWitnessSizeUpperBound(input) size, isNestedP2SH, _ := input.WitnessType().SizeUpperBound()
// Keep a running weight estimate of the input set. // Keep a running weight estimate of the input set.
if isNestedP2SH { if isNestedP2SH {
@@ -251,59 +251,6 @@ func createSweepTx(inputs []input.Input, outputPkScript []byte,
return sweepTx, nil return sweepTx, nil
} }
// getInputWitnessSizeUpperBound returns the maximum length of the witness for
// the given input if it would be included in a tx. We also return if the
// output itself is a nested p2sh output, if so then we need to take into
// account the extra sigScript data size.
func getInputWitnessSizeUpperBound(inp input.Input) (int, bool, error) {
switch inp.WitnessType() {
// Outputs on a remote commitment transaction that pay directly to us.
case input.CommitSpendNoDelayTweakless:
fallthrough
case input.WitnessKeyHash:
fallthrough
case input.CommitmentNoDelay:
return input.P2WKHWitnessSize, false, nil
// Outputs on a past commitment transaction that pay directly
// to us.
case input.CommitmentTimeLock:
return input.ToLocalTimeoutWitnessSize, false, nil
// Outgoing second layer HTLC's that have confirmed within the
// chain, and the output they produced is now mature enough to
// sweep.
case input.HtlcOfferedTimeoutSecondLevel:
return input.ToLocalTimeoutWitnessSize, false, nil
// Incoming second layer HTLC's that have confirmed within the
// chain, and the output they produced is now mature enough to
// sweep.
case input.HtlcAcceptedSuccessSecondLevel:
return input.ToLocalTimeoutWitnessSize, false, nil
// An HTLC on the commitment transaction of the remote party,
// that has had its absolute timelock expire.
case input.HtlcOfferedRemoteTimeout:
return input.AcceptedHtlcTimeoutWitnessSize, false, nil
// An HTLC on the commitment transaction of the remote party,
// that can be swept with the preimage.
case input.HtlcAcceptedRemoteSuccess:
return input.OfferedHtlcSuccessWitnessSize, false, nil
// A nested P2SH input that has a p2wkh witness script. We'll mark this
// as nested P2SH so the caller can estimate the weight properly
// including the sigScript.
case input.NestedWitnessKeyHash:
return input.P2WKHWitnessSize, true, nil
}
return 0, false, fmt.Errorf("unexpected witness type: %v",
inp.WitnessType())
}
// getWeightEstimate returns a weight estimate for the given inputs. // getWeightEstimate returns a weight estimate for the given inputs.
// Additionally, it returns counts for the number of csv and cltv inputs. // Additionally, it returns counts for the number of csv and cltv inputs.
func getWeightEstimate(inputs []input.Input) ([]input.Input, int64, int, int) { func getWeightEstimate(inputs []input.Input) ([]input.Input, int64, int, int) {
@@ -328,10 +275,8 @@ func getWeightEstimate(inputs []input.Input) ([]input.Input, int64, int, int) {
for i := range inputs { for i := range inputs {
inp := inputs[i] inp := inputs[i]
// For fee estimation purposes, we'll now attempt to obtain an wt := inp.WitnessType()
// upper bound on the weight this input will add when fully inpCsv, inpCltv, err := wt.AddWeightEstimation(&weightEstimate)
// populated.
size, isNestedP2SH, err := getInputWitnessSizeUpperBound(inp)
if err != nil { if err != nil {
log.Warn(err) log.Warn(err)
@@ -339,23 +284,8 @@ func getWeightEstimate(inputs []input.Input) ([]input.Input, int64, int, int) {
// given. // given.
continue continue
} }
csvCount += inpCsv
// If this is a nested P2SH input, then we'll need to factor in cltvCount += inpCltv
// the additional data push within the sigScript.
if isNestedP2SH {
weightEstimate.AddNestedP2WSHInput(size)
} else {
weightEstimate.AddWitnessInput(size)
}
switch inp.WitnessType() {
case input.CommitmentTimeLock,
input.HtlcOfferedTimeoutSecondLevel,
input.HtlcAcceptedSuccessSecondLevel:
csvCount++
case input.HtlcOfferedRemoteTimeout:
cltvCount++
}
sweepInputs = append(sweepInputs, inp) sweepInputs = append(sweepInputs, inp)
} }
+49
View File
@@ -0,0 +1,49 @@
package sweep
import (
"testing"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
)
var (
witnessTypes = []input.WitnessType{
input.CommitmentTimeLock,
input.HtlcAcceptedSuccessSecondLevel,
input.HtlcOfferedRemoteTimeout,
input.WitnessKeyHash,
}
expectedWeight = int64(1459)
expectedCsv = 2
expectedCltv = 1
)
// TestWeightEstimate tests that the estimated weight and number of CSVs/CLTVs
// used is correct for a transaction that uses inputs with the witness types
// defined in witnessTypes.
func TestWeightEstimate(t *testing.T) {
t.Parallel()
var inputs []input.Input
for _, witnessType := range witnessTypes {
inputs = append(inputs, input.NewBaseInput(
&wire.OutPoint{}, witnessType,
&input.SignDescriptor{}, 0,
))
}
_, weight, csv, cltv := getWeightEstimate(inputs)
if weight != expectedWeight {
t.Fatalf("unexpected weight. expected %d but got %d.",
expectedWeight, weight)
}
if csv != expectedCsv {
t.Fatalf("unexpected csv count. expected %d but got %d.",
expectedCsv, csv)
}
if cltv != expectedCltv {
t.Fatalf("unexpected cltv count. expected %d but got %d.",
expectedCltv, cltv)
}
}
+3 -3
View File
@@ -1344,7 +1344,7 @@ type kidOutput struct {
} }
func makeKidOutput(outpoint, originChanPoint *wire.OutPoint, func makeKidOutput(outpoint, originChanPoint *wire.OutPoint,
blocksToMaturity uint32, witnessType input.WitnessType, blocksToMaturity uint32, witnessType input.StandardWitnessType,
signDescriptor *input.SignDescriptor, signDescriptor *input.SignDescriptor,
absoluteMaturity uint32) kidOutput { absoluteMaturity uint32) kidOutput {
@@ -1423,7 +1423,7 @@ func (k *kidOutput) Encode(w io.Writer) error {
return err return err
} }
byteOrder.PutUint16(scratch[:2], uint16(k.WitnessType())) byteOrder.PutUint16(scratch[:2], uint16(k.witnessType))
if _, err := w.Write(scratch[:2]); err != nil { if _, err := w.Write(scratch[:2]); err != nil {
return err return err
} }
@@ -1473,7 +1473,7 @@ func (k *kidOutput) Decode(r io.Reader) error {
if _, err := r.Read(scratch[:2]); err != nil { if _, err := r.Read(scratch[:2]); err != nil {
return err return err
} }
k.witnessType = input.WitnessType(byteOrder.Uint16(scratch[:2])) k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2]))
return input.ReadSignDescriptor(r, &k.signDesc) return input.ReadSignDescriptor(r, &k.signDesc)
} }