Merge pull request #264 from cfromknecht/sweep-2nd-layer-htlcs

Breach Arbiter Sweep 2nd layer HTLCs
This commit is contained in:
Olaoluwa Osuntokun 2017-09-20 17:18:47 -07:00 committed by GitHub
commit 94b54f0243
10 changed files with 823 additions and 375 deletions

@ -4,7 +4,6 @@ import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"sync"
"sync/atomic"
@ -15,6 +14,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/roasbeef/btcd/blockchain"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/txscript"
@ -30,6 +30,52 @@ import (
// continue from the persisted state.
var retributionBucket = []byte("retribution")
// BreachConfig bundles the required subsystems used by the breach arbiter. An
// instance of BreachConfig is passed to newBreachArbiter during instantiation.
type BreachConfig struct {
// ChainIO is used by the breach arbiter to determine the current height
// of the blockchain, which is required to subscribe for spend
// notifications from Notifier.
ChainIO lnwallet.BlockChainIO
// CloseLink allows the breach arbiter to shutdown any channel links for
// which it detects a breach, ensuring now further activity will
// continue across the link. The method accepts link's channel point and a
// close type to be included in the channel close summary.
CloseLink func(*wire.OutPoint, htlcswitch.ChannelCloseType)
// DB provides access to the user's channels, allowing the breach
// arbiter to determine the current state of a user's channels, and how
// it should respond to channel closure.
DB *channeldb.DB
// Estimator is used by the breach arbiter to determine an appropriate
// fee level when generating, signing, and broadcasting sweep
// transactions.
Estimator lnwallet.FeeEstimator
// GenSweepScript generates the receiving scripts for swept outputs.
GenSweepScript func() ([]byte, error)
// Notifier provides a publish/subscribe interface for event driven
// notifications regarding the confirmation of txids.
Notifier chainntnfs.ChainNotifier
// PublishTransaction facilitates the process of broadcasting a
// transaction to the network.
PublishTransaction func(*wire.MsgTx) error
// Signer is used by the breach arbiter to generate sweep transactions,
// which move coins from previously open channels back to the user's
// wallet.
Signer lnwallet.Signer
// Store is a persistent resource that maintains information regarding
// breached channels. This is used in conjunction with DB to recover
// from crashes, restarts, or other failures.
Store RetributionStore
}
// breachArbiter is a special subsystem which is responsible for watching and
// acting on the detection of any attempted uncooperative channel breaches by
// channel counterparties. This file essentially acts as deterrence code for
@ -39,14 +85,7 @@ var retributionBucket = []byte("retribution")
// counterparties.
// TODO(roasbeef): closures in config for subsystem pointers to decouple?
type breachArbiter struct {
wallet *lnwallet.LightningWallet
db *channeldb.DB
notifier chainntnfs.ChainNotifier
chainIO lnwallet.BlockChainIO
estimator lnwallet.FeeEstimator
htlcSwitch *htlcswitch.Switch
retributionStore RetributionStore
cfg *BreachConfig
// breachObservers is a map which tracks all the active breach
// observers we're currently managing. The key of the map is the
@ -81,19 +120,9 @@ type breachArbiter struct {
// newBreachArbiter creates a new instance of a breachArbiter initialized with
// its dependent objects.
func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB,
notifier chainntnfs.ChainNotifier, h *htlcswitch.Switch,
chain lnwallet.BlockChainIO, fe lnwallet.FeeEstimator) *breachArbiter {
func newBreachArbiter(cfg *BreachConfig) *breachArbiter {
return &breachArbiter{
wallet: wallet,
db: db,
notifier: notifier,
chainIO: chain,
htlcSwitch: h,
estimator: fe,
retributionStore: newRetributionStore(db),
cfg: cfg,
breachObservers: make(map[wire.OutPoint]chan struct{}),
breachedContracts: make(chan *retributionInfo),
@ -119,7 +148,7 @@ func (b *breachArbiter) Start() error {
// breach is reflected in channeldb.
breachRetInfos := make(map[wire.OutPoint]retributionInfo)
closeSummaries := make(map[wire.OutPoint]channeldb.ChannelCloseSummary)
err := b.retributionStore.ForAll(func(ret *retributionInfo) error {
err := b.cfg.Store.ForAll(func(ret *retributionInfo) error {
// Extract emitted retribution information.
breachRetInfos[ret.chanPoint] = *ret
@ -129,7 +158,7 @@ func (b *breachArbiter) Start() error {
closeSummary := channeldb.ChannelCloseSummary{
ChanPoint: ret.chanPoint,
ClosingTXID: ret.commitHash,
RemotePub: &ret.remoteIdentity,
RemotePub: ret.remoteIdentity,
Capacity: ret.capacity,
SettledBalance: ret.settledBalance,
CloseType: channeldb.BreachClose,
@ -146,7 +175,7 @@ func (b *breachArbiter) Start() error {
// We need to query that database state for all currently active
// channels, each of these channels will need a goroutine assigned to
// it to watch for channel breaches.
activeChannels, err := b.db.FetchAllChannels()
activeChannels, err := b.cfg.DB.FetchAllChannels()
if err != nil && err != channeldb.ErrNoActiveChannels {
brarLog.Errorf("unable to fetch active channels: %v", err)
return err
@ -167,11 +196,11 @@ func (b *breachArbiter) Start() error {
// channels can be discarded, as their fate will be placed in the hands
// of an exactRetribution task spawned later.
//
// NOTE Spawning of the exactRetribution task is intentionally postponed
// until after this step in order to ensure that the all breached
// channels are reflected as closed in channeldb and consistent with
// what is checkpointed by the breach arbiter. Instead of treating the
// breached-and-closed and breached-but-still-active channels as
// NOTE: Spawning of the exactRetribution task is intentionally
// postponed until after this step in order to ensure that the all
// breached channels are reflected as closed in channeldb and consistent
// with what is checkpointed by the breach arbiter. Instead of treating
// the breached-and-closed and breached-but-still-active channels as
// separate sets of channels, we first ensure that all
// breached-but-still-active channels are promoted to
// breached-and-closed during restart, allowing us to treat them as a
@ -183,8 +212,8 @@ func (b *breachArbiter) Start() error {
channelsToWatch := make([]*lnwallet.LightningChannel, 0, nActive)
for _, chanState := range activeChannels {
// Initialize active channel from persisted channel state.
channel, err := lnwallet.NewLightningChannel(nil, b.notifier,
b.estimator, chanState)
channel, err := lnwallet.NewLightningChannel(nil,
b.cfg.Notifier, b.cfg.Estimator, chanState)
if err != nil {
brarLog.Errorf("unable to load channel from "+
"disk: %v", err)
@ -203,10 +232,8 @@ func (b *breachArbiter) Start() error {
// notify the HTLC switch that this link should be
// closed, and that all activity on the link should
// cease.
b.htlcSwitch.CloseLink(
&chanState.FundingOutpoint,
htlcswitch.CloseBreach,
)
b.cfg.CloseLink(&chanState.FundingOutpoint,
htlcswitch.CloseBreach)
// Ensure channeldb is consistent with the persisted
// breach.
@ -229,18 +256,25 @@ func (b *breachArbiter) Start() error {
}
// TODO(roasbeef): instead use closure height of channel
_, currentHeight, err := b.chainIO.GetBestBlock()
_, currentHeight, err := b.cfg.ChainIO.GetBestBlock()
if err != nil {
return err
}
// Additionally, we'll also want to watch any pending close or force
// close transactions to we can properly mark them as resolved in the
// database.
if err := b.watchForPendingCloseConfs(currentHeight); err != nil {
return err
}
// Spawn the exactRetribution tasks to monitor and resolve any breaches
// that were loaded from the retribution store.
for chanPoint, closeSummary := range closeSummaries {
// Register for a notification when the breach transaction is
// confirmed on chain.
breachTXID := closeSummary.ClosingTXID
confChan, err := b.notifier.RegisterConfirmationsNtfn(
confChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn(
&breachTXID, 1, uint32(currentHeight))
if err != nil {
brarLog.Errorf("unable to register for conf updates "+
@ -259,10 +293,13 @@ func (b *breachArbiter) Start() error {
b.wg.Add(1)
go b.contractObserver(channelsToWatch)
// Additionally, we'll also want to retrieve any pending close or force
// close transactions to we can properly mark them as resolved in the
// database.
pendingCloseChans, err := b.db.FetchClosedChannels(true)
return nil
}
// watchForPendingCloseConfs dispatches confirmation notification subscribers
// that mark any pending channels as fully closed when signaled.
func (b *breachArbiter) watchForPendingCloseConfs(currentHeight int32) error {
pendingCloseChans, err := b.cfg.DB.FetchClosedChannels(true)
if err != nil {
brarLog.Errorf("unable to fetch closing channels: %v", err)
return err
@ -281,9 +318,8 @@ func (b *breachArbiter) Start() error {
pendingClose.ChanPoint)
closeTXID := pendingClose.ClosingTXID
confNtfn, err := b.notifier.RegisterConfirmationsNtfn(
&closeTXID, 1, uint32(currentHeight),
)
confNtfn, err := b.cfg.Notifier.RegisterConfirmationsNtfn(
&closeTXID, 1, uint32(currentHeight))
if err != nil {
return err
}
@ -309,9 +345,9 @@ func (b *breachArbiter) Start() error {
// UnilateralCloseSummary on disk so can
// possibly sweep output here
err := b.db.MarkChanFullyClosed(&chanPoint)
err := b.cfg.DB.MarkChanFullyClosed(&chanPoint)
if err != nil {
brarLog.Errorf("unable to mark chan "+
brarLog.Errorf("unable to mark channel"+
" as closed: %v", err)
}
@ -373,10 +409,10 @@ out:
for {
select {
case breachInfo := <-b.breachedContracts:
_, currentHeight, err := b.chainIO.GetBestBlock()
_, currentHeight, err := b.cfg.ChainIO.GetBestBlock()
if err != nil {
brarLog.Errorf(
"unable to get best height: %v", err)
brarLog.Errorf("unable to get best height: %v",
err)
}
// A new channel contract has just been breached! We
@ -385,9 +421,8 @@ out:
// transaction) has been confirmed in the chain to
// ensure we're not dealing with a moving target.
breachTXID := &breachInfo.commitHash
confChan, err := b.notifier.RegisterConfirmationsNtfn(
breachTXID, 1, uint32(currentHeight),
)
cfChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn(
breachTXID, 1, uint32(currentHeight))
if err != nil {
brarLog.Errorf("unable to register for conf "+
"updates for txid: %v, err: %v",
@ -405,7 +440,7 @@ out:
// retribution after the breach transaction has been
// confirmed.
b.wg.Add(1)
go b.exactRetribution(confChan, breachInfo)
go b.exactRetribution(cfChan, breachInfo)
delete(b.breachObservers, breachInfo.chanPoint)
@ -511,7 +546,7 @@ func (b *breachArbiter) exactRetribution(
return spew.Sdump(justiceTx)
}))
_, currentHeight, err := b.chainIO.GetBestBlock()
_, currentHeight, err := b.cfg.ChainIO.GetBestBlock()
if err != nil {
brarLog.Errorf("unable to get current height: %v", err)
return
@ -519,7 +554,7 @@ func (b *breachArbiter) exactRetribution(
// Finally, broadcast the transaction, finalizing the channels'
// retribution against the cheating counterparty.
if err := b.wallet.PublishTransaction(justiceTx); err != nil {
if err := b.cfg.PublishTransaction(justiceTx); err != nil {
brarLog.Errorf("unable to broadcast "+
"justice tx: %v", err)
return
@ -530,8 +565,8 @@ func (b *breachArbiter) exactRetribution(
// notify the caller that initiated the retribution workflow that the
// deed has been done.
justiceTXID := justiceTx.TxHash()
confChan, err = b.notifier.RegisterConfirmationsNtfn(&justiceTXID, 1,
uint32(currentHeight))
confChan, err = b.cfg.Notifier.RegisterConfirmationsNtfn(
&justiceTXID, 1, uint32(currentHeight))
if err != nil {
brarLog.Errorf("unable to register for conf for txid: %v",
justiceTXID)
@ -554,14 +589,14 @@ func (b *breachArbiter) exactRetribution(
revokedFunds, totalFunds)
// With the channel closed, mark it in the database as such.
err := b.db.MarkChanFullyClosed(&breachInfo.chanPoint)
err := b.cfg.DB.MarkChanFullyClosed(&breachInfo.chanPoint)
if err != nil {
brarLog.Errorf("unable to mark chan as closed: %v", err)
}
// Justice has been carried out; we can safely delete the
// retribution info from the database.
err = b.retributionStore.Remove(&breachInfo.chanPoint)
err = b.cfg.Store.Remove(&breachInfo.chanPoint)
if err != nil {
brarLog.Errorf("unable to remove retribution "+
"from the db: %v", err)
@ -572,8 +607,6 @@ func (b *breachArbiter) exactRetribution(
// TODO(roasbeef): close other active channels with offending
// peer
close(breachInfo.doneChan)
return
case <-b.quit:
return
@ -621,15 +654,16 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
// Next, we'll launch a goroutine to wait until the closing
// transaction has been confirmed so we can mark the contract
// as resolved in the database. This go routine is _not_
// tracked by the breach aribter's wait group since the callback
// may not be executed before shutdown, potentially leading to
// a deadlock.
// as resolved in the database. This go routine is _not_ tracked
// by the breach arbiter's wait group since the callback may not
// be executed before shutdown, potentially leading to a
// deadlocks as the arbiter may not be able to finish shutting
// down.
//
// TODO(roasbeef): also notify utxoNursery, might've had
// outbound HTLC's in flight
go waitForChanToClose(uint32(closeInfo.SpendingHeight),
b.notifier, nil, chanPoint, closeInfo.SpenderTxHash,
b.cfg.Notifier, nil, chanPoint, closeInfo.SpenderTxHash,
func() {
// As we just detected a channel was closed via
// a unilateral commitment broadcast by the
@ -650,9 +684,11 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
goto close
}
err = b.wallet.PublishTransaction(
sweepTx,
)
brarLog.Infof("Sweeping breached "+
"outputs with: %v",
spew.Sdump(sweepTx))
err = b.cfg.PublishTransaction(sweepTx)
if err != nil {
brarLog.Errorf("unable to "+
"broadcast tx: %v", err)
@ -664,7 +700,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
"is fully closed, updating DB",
chanPoint)
err := b.db.MarkChanFullyClosed(chanPoint)
err := b.cfg.DB.MarkChanFullyClosed(chanPoint)
if err != nil {
brarLog.Errorf("unable to mark chan "+
"as closed: %v", err)
@ -684,83 +720,46 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
// breached in order to ensure any incoming or outgoing
// multi-hop HTLCs aren't sent over this link, nor any other
// links associated with this peer.
b.htlcSwitch.CloseLink(chanPoint, htlcswitch.CloseBreach)
chanInfo := contract.StateSnapshot()
b.cfg.CloseLink(chanPoint, htlcswitch.CloseBreach)
// TODO(roasbeef): need to handle case of remote broadcast
// mid-local initiated state-transition, possible
// false-positive?
// First we generate the witness generation function which will
// be used to sweep the output only we can satisfy on the
// commitment transaction. This output is just a regular p2wkh
// output.
localSignDesc := breachInfo.LocalOutputSignDesc
localWitness := func(tx *wire.MsgTx, hc *txscript.TxSigHashes,
inputIndex int) ([][]byte, error) {
// Obtain a snapshot of the final channel state, which can be
// used to reclose a breached channel in the event of a failure.
chanInfo := contract.StateSnapshot()
desc := localSignDesc
desc.SigHashes = hc
desc.InputIndex = inputIndex
return lnwallet.CommitSpendNoDelay(
b.wallet.Cfg.Signer, &desc, tx)
}
// Next we create the witness generation function that will be
// used to sweep the cheating counterparty's output by taking
// advantage of the revocation clause within the output's
// witness script.
remoteSignDesc := breachInfo.RemoteOutputSignDesc
remoteWitness := func(tx *wire.MsgTx, hc *txscript.TxSigHashes,
inputIndex int) ([][]byte, error) {
desc := breachInfo.RemoteOutputSignDesc
desc.SigHashes = hc
desc.InputIndex = inputIndex
return lnwallet.CommitSpendRevoke(
b.wallet.Cfg.Signer, &desc, tx)
}
// Assemble the retribution information that parameterizes the
// construction of transactions required to correct the breach.
// TODO(roasbeef): populate htlc breaches
retInfo := &retributionInfo{
commitHash: breachInfo.BreachTransaction.TxHash(),
chanPoint: *chanPoint,
remoteIdentity: chanInfo.RemoteIdentity,
capacity: chanInfo.Capacity,
settledBalance: chanInfo.LocalBalance.ToSatoshis(),
selfOutput: &breachedOutput{
amt: btcutil.Amount(localSignDesc.Output.Value),
outpoint: breachInfo.LocalOutpoint,
signDescriptor: localSignDesc,
witnessType: lnwallet.CommitmentNoDelay,
witnessFunc: localWitness,
},
revokedOutput: &breachedOutput{
amt: btcutil.Amount(remoteSignDesc.Output.Value),
outpoint: breachInfo.RemoteOutpoint,
signDescriptor: remoteSignDesc,
witnessType: lnwallet.CommitmentRevoke,
witnessFunc: remoteWitness,
},
htlcOutputs: []*breachedOutput{},
doneChan: make(chan struct{}),
}
// Using the breach information provided by the wallet and the
// channel snapshot, construct the retribution information that
// will be persisted to disk.
retInfo := newRetributionInfo(chanPoint, breachInfo, chanInfo)
// Persist the pending retribution state to disk.
if err := b.retributionStore.Add(retInfo); err != nil {
brarLog.Errorf("unable to persist "+
"retribution info to db: %v", err)
if err := b.cfg.Store.Add(retInfo); err != nil {
brarLog.Errorf("unable to persist retribution info "+
"to db: %v", err)
}
// TODO(conner): move responsibility of channel closure into
// lnwallet. Have breach arbiter ACK after writing to disk, then
// have wallet mark channel as closed. This allows the wallet to
// attempt to retransmit the breach info if the either arbiter
// or the wallet goes down before completing the hand off.
// Now that the breach arbiter has persisted the information,
// we can go ahead and mark the channel as closed in the
// channeldb. This step is done after persisting the
// retribution information so that a failure between these steps
// will cause an attempt to monitor the still-open channel.
// However, since the retribution information was persisted
// before, the arbiter will recognize that the channel should be
// closed, and proceed to mark it as such after a restart, and
// forgo monitoring it for breaches.
// Construct the breached channel's close summary marking the
// channel using the snapshot from before, and marking this as a
// BreachClose.
closeInfo := &channeldb.ChannelCloseSummary{
ChanPoint: *chanPoint,
ClosingTXID: breachInfo.BreachTransaction.TxHash(),
@ -770,6 +769,10 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
CloseType: channeldb.BreachClose,
IsPending: true,
}
// Next, persist the channel close to disk. Upon restart, the
// arbiter will recognize that this channel has been breached
// and marked close, and fast track its path to justice.
if err := contract.DeleteState(closeInfo); err != nil {
brarLog.Errorf("unable to delete channel state: %v",
err)
@ -787,20 +790,90 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
}
}
// SpendableOutput an interface which can be used by the breach arbiter to
// construct a transaction spending from outputs we control.
type SpendableOutput interface {
// Amount returns the number of satoshis contained within the output.
Amount() btcutil.Amount
// Outpoint returns the reference to the output being spent, used to
// construct the corresponding transaction input.
OutPoint() *wire.OutPoint
// BuildWitness returns a valid witness allowing this output to be
// spent, the witness should be attached to the transaction at the
// location determined by the given `txinIdx`.
BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
txinIdx int) ([][]byte, error)
}
// breachedOutput contains all the information needed to sweep a breached
// output. A breached output is an output that we are now entitled to due to a
// revoked commitment transaction being broadcast.
type breachedOutput struct {
amt btcutil.Amount
outpoint wire.OutPoint
signDescriptor lnwallet.SignDescriptor
witnessType lnwallet.WitnessType
witnessFunc lnwallet.WitnessGenerator
signDesc lnwallet.SignDescriptor
twoStageClaim bool
witnessFunc lnwallet.WitnessGenerator
}
// newBreachedOutput assembles a new breachedOutput that can be used by the
// breach arbiter to construct a justice or sweep transaction.
func newBreachedOutput(outpoint *wire.OutPoint,
witnessType lnwallet.WitnessType,
signDescriptor *lnwallet.SignDescriptor) *breachedOutput {
amount := signDescriptor.Output.Value
return &breachedOutput{
amt: btcutil.Amount(amount),
outpoint: *outpoint,
witnessType: witnessType,
signDesc: *signDescriptor,
}
}
// Amount returns the number of satoshis contained in the breached output.
func (bo *breachedOutput) Amount() btcutil.Amount {
return bo.amt
}
// OutPoint returns the breached outputs identifier that is to be included as a
// transaction input.
func (bo *breachedOutput) OutPoint() *wire.OutPoint {
return &bo.outpoint
}
// BuildWitness computes a valid witness that allows us to spend from the
// breached output. It does so by first generating and memoizing the witness
// generation function, which parameterized primarily by the witness type and
// sign descriptor. The method then returns the witness computed by invoking
// this function on the first and subsequent calls.
func (bo *breachedOutput) BuildWitness(signer lnwallet.Signer,
txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
txinIdx int) ([][]byte, error) {
// First, we ensure that the witness generation function has
// been initialized for this breached output.
if bo.witnessFunc == nil {
bo.witnessFunc = bo.witnessType.GenWitnessFunc(
signer, &bo.signDesc)
}
// Now that we have ensured that the witness generation function has
// been initialized, we can proceed to execute it and generate the
// witness for this particular breached output.
return bo.witnessFunc(txn, hashCache, txinIdx)
}
// Add compile-time constraint ensuring breachedOutput implements
// SpendableOutput.
var _ SpendableOutput = (*breachedOutput)(nil)
// retributionInfo encapsulates all the data needed to sweep all the contested
// funds within a channel whose contract has been breached by the prior
// counterparty. This struct is used to create the justice transaction which
@ -810,11 +883,14 @@ type retributionInfo struct {
commitHash chainhash.Hash
chanPoint wire.OutPoint
// TODO(conner): remove the following group of fields after decoupling
// the breach arbiter from the wallet.
// Fields copied from channel snapshot when a breach is detected. This
// is necessary for deterministically constructing the channel close
// summary in the event that the breach arbiter crashes before closing
// the channel.
remoteIdentity btcec.PublicKey
remoteIdentity *btcec.PublicKey
capacity btcutil.Amount
settledBalance btcutil.Amount
@ -827,6 +903,70 @@ type retributionInfo struct {
doneChan chan struct{}
}
// newRetributionInfo constructs a retributionInfo containing all the
// information required by the breach arbiter to recover funds from breached
// channels. The information is primarily populated using the BreachRetribution
// delivered by the wallet when it detects a channel breach.
func newRetributionInfo(chanPoint *wire.OutPoint,
breachInfo *lnwallet.BreachRetribution,
chanInfo *channeldb.ChannelSnapshot) *retributionInfo {
// First, record the breach information and witness type for the local
// channel point. This will allow us to completely generate a valid
// witness in the event of failures, as it will be persisted in the
// retribution store. Here we use CommitmentNoDelay since this output
// belongs to us and has no time-based constraints on spending.
selfOutput := newBreachedOutput(&breachInfo.LocalOutpoint,
lnwallet.CommitmentNoDelay, &breachInfo.LocalOutputSignDesc)
// Second, record the same information and witness type regarding the
// remote outpoint, which belongs to the party who tried to steal our
// money! Here we set witnessType of the breachedOutput to
// CommitmentRevoke, since we will be using a revoke key, withdrawing
// the funds from the commitment transaction immediately.
revokedOutput := newBreachedOutput(&breachInfo.RemoteOutpoint,
lnwallet.CommitmentRevoke, &breachInfo.RemoteOutputSignDesc)
// Determine the number of second layer HTLCs we will attempt to sweep.
nHtlcs := len(breachInfo.HtlcRetributions)
// Lastly, for each of the breached HTLC outputs, assemble the
// information we will persist to disk, such that we will be able to
// deterministically generate a valid witness for each output. This will
// allow the breach arbiter to recover from failures, in the event that
// it must sign and broadcast the justice transaction.
htlcOutputs := make([]*breachedOutput, nHtlcs)
for i, breachedHtlc := range breachInfo.HtlcRetributions {
// Using the breachedHtlc's incoming flag, determine the
// appropriate witness type that needs to be generated in order
// to sweep the HTLC output.
var htlcWitnessType lnwallet.WitnessType
if breachedHtlc.IsIncoming {
htlcWitnessType = lnwallet.HtlcAcceptedRevoke
} else {
htlcWitnessType = lnwallet.HtlcOfferedRevoke
}
htlcOutputs[i] = newBreachedOutput(
&breachInfo.HtlcRetributions[i].OutPoint, htlcWitnessType,
&breachInfo.HtlcRetributions[i].SignDesc)
}
// TODO(conner): remove dependency on channel snapshot after decoupling
// channel closure from the breach arbiter.
return &retributionInfo{
commitHash: breachInfo.BreachTransaction.TxHash(),
chanPoint: *chanPoint,
remoteIdentity: &chanInfo.RemoteIdentity,
capacity: chanInfo.Capacity,
settledBalance: chanInfo.LocalBalance.ToSatoshis(),
selfOutput: selfOutput,
revokedOutput: revokedOutput,
htlcOutputs: htlcOutputs,
}
}
// createJusticeTx creates a transaction which exacts "justice" by sweeping ALL
// the funds within the channel which we are now entitled to due to a breach of
// the channel's contract by the counterparty. This function returns a *fully*
@ -834,66 +974,43 @@ type retributionInfo struct {
func (b *breachArbiter) createJusticeTx(
r *retributionInfo) (*wire.MsgTx, error) {
// First, we obtain a new public key script from the wallet which we'll
// sweep the funds to.
// TODO(roasbeef): possibly create many outputs to minimize change in
// the future?
pkScriptOfJustice, err := newSweepPkScript(b.wallet)
if err != nil {
return nil, err
// Determine the number of HTLCs to be swept by the justice txn.
nHtlcs := len(r.htlcOutputs)
// Assemble the breached outputs into a slice of spendable outputs,
// starting with the self and revoked outputs, then adding any htlc
// outputs.
breachedOutputs := make([]SpendableOutput, 2+nHtlcs)
breachedOutputs[0] = r.selfOutput
breachedOutputs[1] = r.revokedOutput
for i, htlcOutput := range r.htlcOutputs {
breachedOutputs[2+i] = htlcOutput
}
r.selfOutput.witnessFunc = r.selfOutput.witnessType.GenWitnessFunc(
&b.wallet.Cfg.Signer, &r.selfOutput.signDescriptor)
// Compute the transaction weight of the justice transaction, which
// includes 2 + nHtlcs inputs and one output.
var txWeight uint64
// Begin with a base txn weight, e.g. version, nLockTime, etc.
txWeight += 4*lnwallet.BaseSweepTxSize + lnwallet.WitnessHeaderSize
// Add to_local revoke script and tx input.
txWeight += 4*lnwallet.InputSize + lnwallet.ToLocalPenaltyWitnessSize
// Add to_remote p2wpkh witness and tx input.
txWeight += 4*lnwallet.InputSize + lnwallet.P2WKHWitnessSize
r.revokedOutput.witnessFunc = r.revokedOutput.witnessType.GenWitnessFunc(
&b.wallet.Cfg.Signer, &r.revokedOutput.signDescriptor)
for i := range r.htlcOutputs {
r.htlcOutputs[i].witnessFunc = r.htlcOutputs[i].witnessType.GenWitnessFunc(
&b.wallet.Cfg.Signer, &r.htlcOutputs[i].signDescriptor)
// Compute the appropriate weight contributed by each revoked accepted
// or offered HTLC witnesses and tx inputs.
for _, htlcOutput := range r.htlcOutputs {
switch htlcOutput.witnessType {
case lnwallet.HtlcOfferedRevoke:
txWeight += 4*lnwallet.InputSize +
lnwallet.OfferedHtlcPenaltyWitnessSize
case lnwallet.HtlcAcceptedRevoke:
txWeight += 4*lnwallet.InputSize +
lnwallet.AcceptedHtlcPenaltyWitnessSize
}
}
// Before creating the actual TxOut, we'll need to calculate the proper
// fee to attach to the transaction to ensure a timely confirmation.
// TODO(roasbeef): remove hard-coded fee
totalAmt := r.selfOutput.amt + r.revokedOutput.amt
sweepedAmt := int64(totalAmt - 5000)
// With the fee calculated, we can now create the justice transaction
// using the information gathered above.
justiceTx := wire.NewMsgTx(2)
justiceTx.AddTxOut(&wire.TxOut{
PkScript: pkScriptOfJustice,
Value: sweepedAmt,
})
justiceTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: r.selfOutput.outpoint,
})
justiceTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: r.revokedOutput.outpoint,
})
hashCache := txscript.NewTxSigHashes(justiceTx)
// Finally, using the witness generation functions attached to the
// retribution information, we'll populate the inputs with fully valid
// witnesses for both commitment outputs, and all the pending HTLCs at
// this state in the channel's history.
// TODO(roasbeef): handle the 2-layer HTLCs
localWitness, err := r.selfOutput.witnessFunc(justiceTx, hashCache, 0)
if err != nil {
return nil, err
}
justiceTx.TxIn[0].Witness = localWitness
remoteWitness, err := r.revokedOutput.witnessFunc(justiceTx, hashCache, 1)
if err != nil {
return nil, err
}
justiceTx.TxIn[1].Witness = remoteWitness
return justiceTx, nil
return b.sweepSpendableOutputsTxn(txWeight, breachedOutputs...)
}
// craftCommitmentSweepTx creates a transaction to sweep the non-delayed output
@ -907,61 +1024,108 @@ func (b *breachArbiter) createJusticeTx(
func (b *breachArbiter) craftCommitSweepTx(
closeInfo *lnwallet.UnilateralCloseSummary) (*wire.MsgTx, error) {
// First, we'll fetch a fresh script that we can use to sweep the funds
// under the control of the wallet.
sweepPkScript, err := newSweepPkScript(b.wallet)
selfOutput := newBreachedOutput(
closeInfo.SelfOutPoint,
lnwallet.CommitmentNoDelay,
closeInfo.SelfOutputSignDesc,
)
// Compute the transaction weight of the commit sweep transaction, which
// includes a single input and output.
var txWeight uint64
// Begin with a base txn weight, e.g. version, nLockTime, etc.
txWeight += 4*lnwallet.BaseSweepTxSize + lnwallet.WitnessHeaderSize
// Add to_local p2wpkh witness and tx input.
txWeight += 4*lnwallet.InputSize + lnwallet.P2WKHWitnessSize
return b.sweepSpendableOutputsTxn(txWeight, selfOutput)
}
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of
// spendable outputs by sweeping the funds into a single p2wkh output.
func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight uint64,
inputs ...SpendableOutput) (*wire.MsgTx, error) {
// First, we obtain a new public key script from the wallet which we'll
// sweep the funds to.
// TODO(roasbeef): possibly create many outputs to minimize change in
// the future?
pkScript, err := b.cfg.GenSweepScript()
if err != nil {
return nil, err
}
// TODO(roasbeef): use proper fees
outputAmt := closeInfo.SelfOutputSignDesc.Output.Value
sweepAmt := int64(outputAmt - 5000)
if sweepAmt <= 0 {
// TODO(roasbeef): add output to special pool, can be swept
// when: funding a channel, sweeping time locked outputs, or
// delivering
// justice after a channel breach
return nil, fmt.Errorf("output to small to sweep in isolation")
// Compute the total amount contained in the inputs.
var totalAmt btcutil.Amount
for _, input := range inputs {
totalAmt += input.Amount()
}
// With the amount we're sweeping computed, we can now creating the
// sweep transaction itself.
sweepTx := wire.NewMsgTx(1)
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *closeInfo.SelfOutPoint,
})
sweepTx.AddTxOut(&wire.TxOut{
PkScript: sweepPkScript,
Value: int64(sweepAmt),
feePerWeight := b.cfg.Estimator.EstimateFeePerWeight(1)
txFee := btcutil.Amount(txWeight * feePerWeight)
sweepAmt := int64(totalAmt - txFee)
// With the fee calculated, we can now create the transaction using the
// information gathered above and the provided retribution information.
txn := wire.NewMsgTx(2)
// We begin by adding the output to which our funds will be deposited.
txn.AddTxOut(&wire.TxOut{
PkScript: pkScript,
Value: sweepAmt,
})
// Next, we'll generate the signature required to satisfy the p2wkh
// witness program.
signDesc := closeInfo.SelfOutputSignDesc
signDesc.SigHashes = txscript.NewTxSigHashes(sweepTx)
signDesc.InputIndex = 0
sweepSig, err := b.wallet.Cfg.Signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
// Next, we add all of the spendable outputs as inputs to the
// transaction.
for _, input := range inputs {
txn.AddTxIn(&wire.TxIn{
PreviousOutPoint: *input.OutPoint(),
})
}
// Before signing the transaction, check to ensure that it meets some
// basic validity requirements.
btx := btcutil.NewTx(txn)
if err := blockchain.CheckTransactionSanity(btx); err != nil {
return nil, err
}
// Finally, we'll manually craft the witness. The witness here is the
// exact same as a regular p2wkh witness, but we'll need to ensure that
// we use the tweaked public key as the last item in the witness stack
// which was originally used to created the pkScript we're spending.
witness := make([][]byte, 2)
witness[0] = append(sweepSig, byte(txscript.SigHashAll))
witness[1] = lnwallet.TweakPubKeyWithTweak(
signDesc.PubKey, signDesc.SingleTweak,
).SerializeCompressed()
// Create a sighash cache to improve the performance of hashing and
// signing SigHashAll inputs.
hashCache := txscript.NewTxSigHashes(txn)
sweepTx.TxIn[0].Witness = witness
// Create a closure that encapsulates the process of initializing a
// particular output's witness generation function, computing the
// witness, and attaching it to the transaction. This function accepts
// an integer index representing the intended txin index, and the
// breached output from which it will spend.
addWitness := func(idx int, so SpendableOutput) error {
// First, we construct a valid witness for this outpoint and
// transaction using the SpendableOutput's witness generation
// function.
witness, err := so.BuildWitness(b.cfg.Signer, txn, hashCache,
idx)
if err != nil {
return err
}
brarLog.Infof("Sweeping commitment output with: %v", spew.Sdump(sweepTx))
// Then, we add the witness to the transaction at the
// appropriate txin index.
txn.TxIn[idx].Witness = witness
return sweepTx, nil
return nil
}
// Finally, generate a witness for each output and attach it to the
// transaction.
for i, input := range inputs {
if err := addWitness(i, input); err != nil {
return nil, err
}
}
return txn, nil
}
// RetributionStore provides an interface for managing a persistent map from
@ -1154,7 +1318,7 @@ func (ret *retributionInfo) Decode(r io.Reader) error {
if err != nil {
return err
}
ret.remoteIdentity = *remoteIdentity
ret.remoteIdentity = remoteIdentity
if _, err := io.ReadFull(r, scratch[:8]); err != nil {
return err
@ -1184,7 +1348,7 @@ func (ret *retributionInfo) Decode(r io.Reader) error {
numHtlcOutputs := int(numHtlcOutputsU64)
ret.htlcOutputs = make([]*breachedOutput, numHtlcOutputs)
for i := 0; i < numHtlcOutputs; i++ {
for i := range ret.htlcOutputs {
ret.htlcOutputs[i] = &breachedOutput{}
if err := ret.htlcOutputs[i].Decode(r); err != nil {
return err
@ -1207,8 +1371,7 @@ func (bo *breachedOutput) Encode(w io.Writer) error {
return err
}
if err := lnwallet.WriteSignDescriptor(
w, &bo.signDescriptor); err != nil {
if err := lnwallet.WriteSignDescriptor(w, &bo.signDesc); err != nil {
return err
}
@ -1217,15 +1380,6 @@ func (bo *breachedOutput) Encode(w io.Writer) error {
return err
}
if bo.twoStageClaim {
scratch[0] = 1
} else {
scratch[0] = 0
}
if _, err := w.Write(scratch[:1]); err != nil {
return err
}
return nil
}
@ -1242,8 +1396,7 @@ func (bo *breachedOutput) Decode(r io.Reader) error {
return err
}
if err := lnwallet.ReadSignDescriptor(
r, &bo.signDescriptor); err != nil {
if err := lnwallet.ReadSignDescriptor(r, &bo.signDesc); err != nil {
return err
}
@ -1253,14 +1406,5 @@ func (bo *breachedOutput) Decode(r io.Reader) error {
bo.witnessType = lnwallet.WitnessType(
binary.BigEndian.Uint16(scratch[:2]))
if _, err := io.ReadFull(r, scratch[:1]); err != nil {
return err
}
if scratch[0] == 1 {
bo.twoStageClaim = true
} else {
bo.twoStageClaim = false
}
return nil
}

@ -176,21 +176,18 @@ var (
amt: btcutil.Amount(1e7),
outpoint: breachOutPoints[0],
witnessType: lnwallet.CommitmentNoDelay,
twoStageClaim: true,
},
{
amt: btcutil.Amount(2e9),
outpoint: breachOutPoints[1],
witnessType: lnwallet.CommitmentRevoke,
twoStageClaim: false,
},
{
amt: btcutil.Amount(3e4),
outpoint: breachOutPoints[2],
witnessType: lnwallet.CommitmentDelayOutput,
twoStageClaim: false,
},
}
@ -240,7 +237,7 @@ func init() {
// channel point.
for i := range retributions {
retInfo := &retributions[i]
retInfo.remoteIdentity = *breachedOutputs[i].signDescriptor.PubKey
retInfo.remoteIdentity = breachedOutputs[i].signDesc.PubKey
retributionMap[retInfo.chanPoint] = *retInfo
}
}
@ -320,7 +317,7 @@ func initBreachedOutputs() error {
breachKeys[i])
}
sd.PubKey = pubkey
bo.signDescriptor = *sd
bo.signDesc = *sd
}
return nil
@ -395,7 +392,6 @@ func copyRetInfo(retInfo *retributionInfo) *retributionInfo {
selfOutput: retInfo.selfOutput,
revokedOutput: retInfo.revokedOutput,
htlcOutputs: make([]*breachedOutput, nHtlcs),
doneChan: retInfo.doneChan,
}
for i, htlco := range retInfo.htlcOutputs {
@ -776,8 +772,8 @@ restartCheck:
foundSet[ret.chanPoint] = struct{}{}
} else {
return fmt.Errorf("unkwown retribution "+
"retrieved from db: %v", ret)
return fmt.Errorf("unkwown retribution retrieved "+
"from db: %v", ret)
}
return nil

@ -2010,10 +2010,10 @@ func testRevokedCloseRetribution(net *networkHarness, t *harnessTest) {
}
}
// testRevokedCloseRetributinPostBreachConf tests that Alice is able carry out
// retribution in the event that she fails immediately after receiving a
// confirmation of Carol's breach txn.
func testRevokedCloseRetributionPostBreachConf(
// testRevokedCloseRetributionRemoteHodl tests that Alice properly responds to a
// channel breach made by the remote party, specifically in the case that the
// remote party breaches before settling extended HTLCs.
func testRevokedCloseRetributionRemoteHodl(
net *networkHarness,
t *harnessTest) {
@ -2021,33 +2021,34 @@ func testRevokedCloseRetributionPostBreachConf(
const (
timeout = time.Duration(time.Second * 10)
chanAmt = maxFundingAmount
pushAmt = 20000
paymentAmt = 10000
numInvoices = 6
)
// Since we'd like to test some multi-hop failure scenarios, we'll
// introduce another node into our test network: Carol.
carol, err := net.NewNode(nil)
// Since this test will result in the counterparty being left in a weird
// state, we will introduce another node into our test network: Carol.
carol, err := net.NewNode([]string{"--debughtlc", "--hodlhtlc"})
if err != nil {
t.Fatalf("unable to create new nodes: %v", err)
}
// We must let Dave have an open channel before he can send a node
// announcement, so we open a channel with Carol,
// We must let Alice communicate with Carol before they are able to
// open channel, so we connect Alice and Carol,
if err := net.ConnectNodes(ctxb, net.Alice, carol); err != nil {
t.Fatalf("unable to connect alice to carol: %v", err)
}
// In order to test Alice's response to an uncooperative channel
// closure by Carol, we'll first open up a channel between them with a
// 0.5 BTC value.
// maxFundingAmount (2^24) satoshis value.
ctxt, _ := context.WithTimeout(ctxb, timeout)
chanPoint := openChannelAndAssert(ctxt, t, net, net.Alice, carol,
chanAmt, 0)
chanAmt, pushAmt)
// With the channel open, we'll create a few invoices for Caro that
// With the channel open, we'll create a few invoices for Carol that
// Alice will pay to in order to advance the state of the channel.
bobPaymentHashes := make([][]byte, numInvoices)
carolPaymentHashes := make([][]byte, numInvoices)
for i := 0; i < numInvoices; i++ {
preimage := bytes.Repeat([]byte{byte(192 - i)}, 32)
invoice := &lnrpc.Invoice{
@ -2060,30 +2061,57 @@ func testRevokedCloseRetributionPostBreachConf(
t.Fatalf("unable to add invoice: %v", err)
}
bobPaymentHashes[i] = resp.RHash
carolPaymentHashes[i] = resp.RHash
}
// As we'll be querying the state of bob's channels frequently we'll
// As we'll be querying the state of Carol's channels frequently we'll
// create a closure helper function for the purpose.
getCarolChanInfo := func() (*lnrpc.ActiveChannel, error) {
req := &lnrpc.ListChannelsRequest{}
bobChannelInfo, err := carol.ListChannels(ctxb, req)
carolChannelInfo, err := carol.ListChannels(ctxb, req)
if err != nil {
return nil, err
}
if len(bobChannelInfo.Channels) != 1 {
t.Fatalf("bob should only have a single channel, instead he has %v",
len(bobChannelInfo.Channels))
if len(carolChannelInfo.Channels) != 1 {
t.Fatalf("carol should only have a single channel, instead he has %v",
len(carolChannelInfo.Channels))
}
return bobChannelInfo.Channels[0], nil
return carolChannelInfo.Channels[0], nil
}
// We'll introduce a closure to validate that Carol's current balance
// matches the given expected amount.
checkCarolBalance := func(expectedAmt int64) {
carolChan, err := getCarolChanInfo()
if err != nil {
t.Fatalf("unable to get carol's channel info: %v", err)
}
if carolChan.LocalBalance != expectedAmt {
t.Fatalf("carol's balance is incorrect, "+
"got %v, expected %v", carolChan.LocalBalance,
expectedAmt)
}
}
// We'll introduce another closure to validate that Carol's current
// number of updates is at least as large as the provided minimum
// number.
checkCarolNumUpdatesAtleast := func(minimum uint64) {
carolChan, err := getCarolChanInfo()
if err != nil {
t.Fatalf("unable to get carol's channel info: %v", err)
}
if carolChan.NumUpdates < minimum {
t.Fatalf("carol's numupdates is incorrect, want %v "+
"to be atleast %v", carolChan.NumUpdates,
minimum)
}
}
// Wait for Alice to receive the channel edge from the funding manager.
ctxt, _ = context.WithTimeout(ctxb, timeout)
err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("alice didn't see the alice->bob channel before "+
t.Fatalf("alice didn't see the alice->carol channel before "+
"timeout: %v", err)
}
@ -2094,16 +2122,26 @@ func testRevokedCloseRetributionPostBreachConf(
if err != nil {
t.Fatalf("unable to create payment stream for alice: %v", err)
}
sendPayments := func(start, stop int) error {
sendPayments := func(start, stop int, isHodl bool) error {
for i := start; i < stop; i++ {
sendReq := &lnrpc.SendRequest{
PaymentHash: bobPaymentHashes[i],
PaymentHash: carolPaymentHashes[i],
Dest: carol.PubKey[:],
Amt: paymentAmt,
}
if err := alicePayStream.Send(sendReq); err != nil {
return err
}
// If the remote peer is in hodl mode, we should not
// attempt to receive a message, otherwise the test will
// block.
if isHodl {
continue
}
// Otherwise, the peer is not in hodl mode, and we will
// expect a response.
if resp, err := alicePayStream.Recv(); err != nil {
t.Fatalf("payment stream has been closed: %v", err)
} else if resp.PaymentError != "" {
@ -2114,80 +2152,94 @@ func testRevokedCloseRetributionPostBreachConf(
return nil
}
// Ensure that carol's balance starts with the amount we pushed to her.
checkCarolBalance(pushAmt)
// Send payments from Alice to Carol using 3 of Carol's payment hashes
// generated above.
if err := sendPayments(0, numInvoices/2); err != nil {
if err := sendPayments(0, numInvoices/2, true); err != nil {
t.Fatalf("unable to send payment: %v", err)
}
time.Sleep(time.Millisecond * 200)
// Next query for Carol's channel state, as we sent 3 payments of 10k
// satoshis each, Carol should now see his balance as being 30k satoshis.
time.Sleep(time.Millisecond * 200)
bobChan, err := getCarolChanInfo()
// satoshis each, however Carol should now see her balance as being
// equal to the push amount in satoshis since she has not settled.
carolChan, err := getCarolChanInfo()
if err != nil {
t.Fatalf("unable to get bob's channel info: %v", err)
t.Fatalf("unable to get carol's channel info: %v", err)
}
if bobChan.LocalBalance != 30000 {
t.Fatalf("bob's balance is incorrect, got %v, expected %v",
bobChan.LocalBalance, 30000)
}
// Grab Carol's current commitment height (update number), we'll later
// revert him to this state after additional updates to force him to
// revert her to this state after additional updates to force her to
// broadcast this soon to be revoked state.
bobStateNumPreCopy := bobChan.NumUpdates
carolStateNumPreCopy := carolChan.NumUpdates
// Ensure that carol's balance still reflects the original amount we
// pushed to her.
checkCarolBalance(pushAmt)
// Since Carol has not settled, she should only see at least one update
// to her channel.
checkCarolNumUpdatesAtleast(1)
// Create a temporary file to house Carol's database state at this
// particular point in history.
bobTempDbPath, err := ioutil.TempDir("", "bob-past-state")
carolTempDbPath, err := ioutil.TempDir("", "carol-past-state")
if err != nil {
t.Fatalf("unable to create temp db folder: %v", err)
}
bobTempDbFile := filepath.Join(bobTempDbPath, "channel.db")
defer os.Remove(bobTempDbPath)
carolTempDbFile := filepath.Join(carolTempDbPath, "channel.db")
defer os.Remove(carolTempDbPath)
// With the temporary file created, copy Carol's current state into the
// temporary file we created above. Later after more updates, we'll
// restore this state.
bobDbPath := filepath.Join(carol.cfg.DataDir, "simnet/bitcoin/channel.db")
if err := copyFile(bobTempDbFile, bobDbPath); err != nil {
carolDbPath := filepath.Join(carol.cfg.DataDir, "simnet/bitcoin/channel.db")
if err := copyFile(carolTempDbFile, carolDbPath); err != nil {
t.Fatalf("unable to copy database files: %v", err)
}
// Finally, send payments from Alice to Carol, consuming Carol's remaining
// payment hashes.
if err := sendPayments(numInvoices/2, numInvoices); err != nil {
if err := sendPayments(numInvoices/2, numInvoices, true); err != nil {
t.Fatalf("unable to send payment: %v", err)
}
time.Sleep(200 * time.Millisecond)
bobChan, err = getCarolChanInfo()
if err != nil {
t.Fatalf("unable to get bob chan info: %v", err)
}
// Ensure that carol's balance still shows the amount we originally
// pushed to her, and that at least one more update has occurred.
checkCarolBalance(pushAmt)
checkCarolNumUpdatesAtleast(carolStateNumPreCopy + 1)
// Now we shutdown Carol, copying over the his temporary database state
// which has the *prior* channel state over his current most up to date
// Now we shutdown Carol, copying over the her temporary database state
// which has the *prior* channel state over her current most up to date
// state. With this, we essentially force Carol to travel back in time
// within the channel's history.
if err = net.RestartNode(carol, func() error {
return os.Rename(bobTempDbFile, bobDbPath)
return os.Rename(carolTempDbFile, carolDbPath)
}); err != nil {
t.Fatalf("unable to restart node: %v", err)
}
// Now query for Carol's channel state, it should show that he's at a
// state number in the past, not the *latest* state.
bobChan, err = getCarolChanInfo()
time.Sleep(200 * time.Millisecond)
// Ensure that Carol's view of the channel is consistent with the
// state of the channel just before it was snapshotted.
checkCarolBalance(pushAmt)
checkCarolNumUpdatesAtleast(1)
// Now query for Carol's channel state, it should show that she's at a
// state number in the past, *not* the latest state.
carolChan, err = getCarolChanInfo()
if err != nil {
t.Fatalf("unable to get bob chan info: %v", err)
t.Fatalf("unable to get carol chan info: %v", err)
}
if bobChan.NumUpdates != bobStateNumPreCopy {
t.Fatalf("db copy failed: %v", bobChan.NumUpdates)
if carolChan.NumUpdates != carolStateNumPreCopy {
t.Fatalf("db copy failed: %v", carolChan.NumUpdates)
}
// Now force Carol to execute a *force* channel closure by unilaterally
// broadcasting his current channel state. This is actually the
// commitment transaction of a prior *revoked* state, so he'll soon
// broadcasting her current channel state. This is actually the
// commitment transaction of a prior *revoked* state, so she'll soon
// feel the wrath of Alice's retribution.
force := true
closeUpdates, _, err := net.CloseChannel(ctxb, carol, chanPoint, force)
@ -2195,18 +2247,29 @@ func testRevokedCloseRetributionPostBreachConf(
t.Fatalf("unable to close channel: %v", err)
}
// Finally, generate a single block, wait for the final close status
// update, then ensure that the closing transaction was included in the
// block.
// Query the mempool for Alice's justice transaction, this should be
// broadcast as Bob's contract breaching transaction gets confirmed
// above.
_, err = waitForTxInMempool(net.Miner.Node, 5*time.Second)
if err != nil {
t.Fatalf("unable to find Alice's justice tx in mempool: %v", err)
}
time.Sleep(200 * time.Millisecond)
// Generate a single block to mine the breach transaction.
block := mineBlocks(t, net, 1)[0]
// Here, Alice receives a confirmation of Carol's breach transaction. We
// restart Alice to ensure that she is persisting her retribution state and
// continues exacting justice after her node restarts.
// Wait so Alice receives a confirmation of Carol's breach transaction.
time.Sleep(200 * time.Millisecond)
// We restart Alice to ensure that she is persisting her retribution
// state and continues exacting justice after her node restarts.
if err := net.RestartNode(net.Alice, nil); err != nil {
t.Fatalf("unable to stop Alice's node: %v", err)
}
// Finally, Wait for the final close status update, then ensure that the
// closing transaction was included in the block.
breachTXID, err := net.WaitForChannelClose(ctxb, closeUpdates)
if err != nil {
t.Fatalf("error while waiting for channel close: %v", err)
@ -2222,20 +2285,6 @@ func testRevokedCloseRetributionPostBreachConf(
}
time.Sleep(100 * time.Millisecond)
// Query for the mempool transaction found above. Then assert that all
// the inputs of this transaction are spending outputs generated by
// Carol's breach transaction above.
justiceTx, err := net.Miner.Node.GetRawTransaction(justiceTXID)
if err != nil {
t.Fatalf("unable to query for justice tx: %v", err)
}
for _, txIn := range justiceTx.MsgTx().TxIn {
if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) {
t.Fatalf("justice tx not spending commitment utxo "+
"instead is: %v", txIn.PreviousOutPoint)
}
}
// We restart Alice here to ensure that she persists her retribution state
// and successfully continues exacting retribution after restarting. At
// this point, Alice has broadcast the justice transaction, but it hasn't
@ -2245,6 +2294,28 @@ func testRevokedCloseRetributionPostBreachConf(
t.Fatalf("unable to restart Alice's node: %v", err)
}
// Query for the mempool transaction found above. Then assert that (1)
// the justice tx has the appropriate number of inputs, and (2) all
// the inputs of this transaction are spending outputs generated by
// Carol's breach transaction above.
justiceTx, err := net.Miner.Node.GetRawTransaction(justiceTXID)
if err != nil {
t.Fatalf("unable to query for justice tx: %v", err)
}
exNumInputs := 2 + numInvoices/2
if len(justiceTx.MsgTx().TxIn) != exNumInputs {
t.Fatalf("justice tx should have exactly 2 commitment inputs"+
"and %v htlc inputs, expected %v in total, got %v",
numInvoices/2, exNumInputs,
len(justiceTx.MsgTx().TxIn))
}
for _, txIn := range justiceTx.MsgTx().TxIn {
if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) {
t.Fatalf("justice tx not spending commitment utxo "+
"instead is: %v", txIn.PreviousOutPoint)
}
}
// Now mine a block, this transaction should include Alice's justice
// transaction which was just accepted into the mempool.
block = mineBlocks(t, net, 1)[0]
@ -3459,8 +3530,8 @@ var testsCases = []*testCase{
test: testRevokedCloseRetribution,
},
{
name: "revoked uncooperative close retribution post breach conf",
test: testRevokedCloseRetributionPostBreachConf,
name: "revoked uncooperative close retribution remote hodl",
test: testRevokedCloseRetributionRemoteHodl,
},
}

@ -1032,6 +1032,12 @@ type HtlcRetribution struct {
// OutPoint is the target outpoint of this HTLC pointing to the
// breached commitment transaction.
OutPoint wire.OutPoint
// IsIncoming is a boolean flag that indicates whether or not this
// HTLC was accepted from the counterparty. A false value indicates that
// this HTLC was offered by us. This flag is used determine the exact
// witness type should be used to sweep the output.
IsIncoming bool
}
// BreachRetribution contains all the data necessary to bring a channel
@ -1162,7 +1168,7 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// With the commitment outputs located, we'll now generate all the
// retribution structs for each of the HTLC transactions active on the
// remote commitment transaction.
htlcRetributions := make([]HtlcRetribution, len(chanState.Htlcs))
htlcRetributions := make([]HtlcRetribution, len(revokedSnapshot.Htlcs))
for i, htlc := range revokedSnapshot.Htlcs {
var (
htlcScript []byte
@ -1206,6 +1212,7 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
Hash: commitHash,
Index: uint32(htlc.OutputIndex),
},
IsIncoming: htlc.Incoming,
}
}

@ -297,6 +297,24 @@ func senderHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor,
return witnessStack, nil
}
// SenderHtlcSpendRevoke constructs a valid witness allowing the receiver of an
// HTLC to claim the output with knowledge of the revocation private key in the
// scenario that the sender of the HTLC broadcasts a previously revoked
// commitment transaction. This method first derives the appropriate revocation
// key, and requires that the provided SignDescriptor has a local revocation
// basepoint and commitment secret in the PubKey and DoubleTweak fields,
// respectively.
func SenderHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
// Derive the revocation key using the local revocation base point and
// commitment point.
revokeKey := DeriveRevocationPubkey(signDesc.PubKey,
signDesc.DoubleTweak.PubKey())
return senderHtlcSpendRevoke(signer, signDesc, revokeKey, sweepTx)
}
// senderHtlcSpendRedeem constructs a valid witness allowing the receiver of an
// HTLC to redeem the pending output in the scenario that the sender broadcasts
// their version of the commitment transaction. A valid spend requires
@ -528,6 +546,24 @@ func receiverHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor,
return witnessStack, nil
}
// ReceiverHtlcSpendRevoke constructs a valid witness allowing the sender of an
// HTLC within a previously revoked commitment transaction to re-claim the
// pending funds in the case that the receiver broadcasts this revoked
// commitment transaction. This method first derives the appropriate revocation
// key, and requires that the provided SignDescriptor has a local revocation
// basepoint and commitment secret in the PubKey and DoubleTweak fields,
// respectively.
func ReceiverHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
// Derive the revocation key using the local revocation base point and
// commitment point.
revokeKey := DeriveRevocationPubkey(signDesc.PubKey,
signDesc.DoubleTweak.PubKey())
return receiverHtlcSpendRevoke(signer, signDesc, revokeKey, sweepTx)
}
// receiverHtlcSpendTimeout constructs a valid witness allowing the sender of
// an HTLC to recover the pending funds after an absolute timeout in the
// scenario that the receiver of the HTLC broadcasts their version of the

@ -166,7 +166,7 @@ func ReadSignDescriptor(r io.Reader, sd *SignDescriptor) error {
return ErrTweakOverdose
}
witnessScript, err := wire.ReadVarBytes(r, 0, 100, "witnessScript")
witnessScript, err := wire.ReadVarBytes(r, 0, 500, "witnessScript")
if err != nil {
return err
}

@ -18,12 +18,31 @@ const (
// - WitnessScriptSHA256: 32 bytes
P2WSHSize = 1 + 1 + 32
// P2WKHOutputSize 31 bytes
// - value: 8 bytes
// - var_int: 1 byte (pkscript_length)
// - pkscript (p2wpkh): 22 bytes
P2WKHOutputSize = 8 + 1 + 22
// P2WSHOutputSize 43 bytes
// - value: 8 bytes
// - var_int: 1 byte (pkscript_length)
// - pkscript (p2wsh): 34 bytes
P2WSHOutputSize = 8 + 1 + 34
// P2WPKHSize 22 bytes
// - OP_0: 1 byte
// - OP_DATA: 1 byte (PublicKeyHASH160 length)
// - PublicKeyHASH160: 20 bytes
P2WPKHSize = 1 + 1 + 20
// P2WKHWitnessSize 108 bytes
// - OP_DATA: 1 byte (signature length)
// - signature
// - OP_DATA: 1 byte (pubkey length)
// - pubkey
P2WKHWitnessSize = 1 + 73 + 1 + 33
// MultiSigSize 71 bytes
// - OP_2: 1 byte
// - OP_DATA: 1 byte (pubKeyAlice length)
@ -45,7 +64,7 @@ const (
// - WitnessScript (MultiSig)
WitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + MultiSigSize
// FundingInputSize 41 bytes
// InputSize 41 bytes
// - PreviousOutPoint:
// - Hash: 32 bytes
// - Index: 4 bytes
@ -57,7 +76,12 @@ const (
// we separate the calculation of ordinary data
// from witness data.
// - Sequence: 4 bytes
FundingInputSize = 32 + 4 + 1 + 4
InputSize = 32 + 4 + 1 + 4
// FundingInputSize represents the size of an input to a funding
// transaction, and is equivalent to the size of a standard segwit input
// as calculated above.
FundingInputSize = InputSize
// CommitmentDelayOutput 43 bytes
// - Value: 8 bytes
@ -82,7 +106,19 @@ const (
// - Marker: 1 byte
WitnessHeaderSize = 1 + 1
// BaseCommitmentTxSize 125 43 * num-htlc-outputs bytes
// BaseSweepTxSize 42 + 41 * num-swept-inputs bytes
// - Version: 4 bytes
// - WitnessHeader <---- part of the witness data
// - CountTxIn: 2 byte
// - TxIn: 41 * num-swept-inputs bytes
// ....SweptInputs....
// - CountTxOut: 1 byte
// - TxOut: 31 bytes
// P2WPKHOutput: 31 bytes
// - LockTime: 4 bytes
BaseSweepTxSize = 4 + 2 + 1 + P2WKHOutputSize + 4
// BaseCommitmentTxSize 125 + 43 * num-htlc-outputs bytes
// - Version: 4 bytes
// - WitnessHeader <---- part of the witness data
// - CountTxIn: 1 byte
@ -119,7 +155,134 @@ const (
// of a contract breach, the punishment transaction is able to sweep
// all the HTLC's yet still remain below the widely used standard
// weight limits.
MaxHTLCNumber = 967
MaxHTLCNumber = 966
// ToLocalPenaltyScriptSize 83 bytes
// - OP_IF: 1 byte
// - OP_DATA: 1 byte (revocationkey length)
// - revocationkey: 33 bytes
// - OP_CHECKSIG: 1 byte
// - OP_ELSE: 1 byte
// - OP_DATA: 1 byte (localkey length)
// - localkey: 33 bytes
// - OP_CHECKSIG_VERIFY: 1 byte
// - OP_DATA: 1 byte (delay length)
// - delay: 8 bytes
// -OP_CHECKSEQUENCEVERIFY: 1 byte
// - OP_ENDIF: 1 byte
ToLocalPenaltyScriptSize = 1 + 1 + 33 + 1 + 1 + 1 + 33 + 1 + 1 + 8 + 1 + 1
// ToLocalPenaltyWitnessSize 160 bytes
// - number_of_witness_elements: 1 byte
// - revocation_sig_length: 1 byte
// - revocation_sig: 73 bytes
// - one_length: 1 byte
// - witness_script_length: 1 byte
// - witness_script (to_local_script)
ToLocalPenaltyWitnessSize = 1 + 1 + 73 + 1 + 1 + ToLocalPenaltyScriptSize
// AcceptedHtlcPenaltyScriptSize 139 bytes
// - OP_DUP: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(SHA256(revocationkey)) length)
// - RIPEMD160(SHA256(revocationkey)): 20 bytes
// - OP_EQUAL: 1 byte
// - OP_IF: 1 byte
// - OP_CHECKSIG: 1 byte
// - OP_ELSE: 1 byte
// - OP_DATA: 1 byte (remotekey length)
// - remotekey: 33 bytes
// - OP_SWAP: 1 byte
// - OP_SIZE: 1 byte
// - 32: 1 byte
// - OP_EQUAL: 1 byte
// - OP_IF: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(payment_hash) length)
// - RIPEMD160(payment_hash): 20 bytes
// - OP_EQUALVERIFY: 1 byte
// - 2: 1 byte
// - OP_SWAP: 1 byte
// - OP_DATA: 1 byte (localkey length)
// - localkey: 33 bytes
// - 2: 1 byte
// - OP_CHECKMULTISIG: 1 byte
// - OP_ELSE: 1 byte
// - OP_DROP: 1 byte
// - OP_DATA: 1 byte (cltv_expiry length)
// - cltv_expiry: 4 bytes
// - OP_CHECKLOCKTIMEVERIFY: 1 byte
// - OP_DROP: 1 byte
// - OP_CHECKSIG: 1 byte
// - OP_ENDIF: 1 byte
// - OP_ENDIF: 1 byte
AcceptedHtlcPenaltyScriptSize = 3*1 + 20 + 5*1 + 33 + 7*1 + 20 + 4*1 +
33 + 5*1 + 4 + 5*1
// AcceptedHtlcPenaltyWitnessSize 249 bytes
// - number_of_witness_elements: 1 byte
// - revocation_sig_length: 1 byte
// - revocation_sig: 73 bytes
// - revocation_key_length: 1 byte
// - revocation_key: 33 bytes
// - witness_script_length: 1 byte
// - witness_script (accepted_htlc_script)
AcceptedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 +
AcceptedHtlcPenaltyScriptSize
// OfferedHtlcScriptSize 133 bytes
// - OP_DUP: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(SHA256(revocationkey)) length)
// - RIPEMD160(SHA256(revocationkey)): 20 bytes
// - OP_EQUAL: 1 byte
// - OP_IF: 1 byte
// - OP_CHECKSIG: 1 byte
// - OP_ELSE: 1 byte
// - OP_DATA: 1 byte (remotekey length)
// - remotekey: 33 bytes
// - OP_SWAP: 1 byte
// - OP_SIZE: 1 byte
// - OP_DATA: 1 byte (32 length)
// - 32: 1 byte
// - OP_EQUAL: 1 byte
// - OP_NOTIF: 1 byte
// - OP_DROP: 1 byte
// - 2: 1 byte
// - OP_SWAP: 1 byte
// - OP_DATA: 1 byte (localkey length)
// - localkey: 33 bytes
// - 2: 1 byte
// - OP_CHECKMULTISIG: 1 byte
// - OP_ELSE: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(payment_hash) length)
// - RIPEMD160(payment_hash): 20 bytes
// - OP_EQUALVERIFY: 1 byte
// - OP_CHECKSIG: 1 byte
// - OP_ENDIF: 1 byte
// - OP_ENDIF: 1 byte
OfferedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 10*1 + 33 + 5*1 + 20 + 4*1
// OfferedHtlcWitnessSize 243 bytes
// - number_of_witness_elements: 1 byte
// - revocation_sig_length: 1 byte
// - revocation_sig: 73 bytes
// - revocation_key_length: 1 byte
// - revocation_key: 33 bytes
// - witness_script_length: 1 byte
// - witness_script (offered_htlc_script)
OfferedHtlcWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + OfferedHtlcScriptSize
// OfferedHtlcPenaltyWitnessSize 243 bytes
// - number_of_witness_elements: 1 byte
// - revocation_sig_length: 1 byte
// - revocation_sig: 73 bytes
// - revocation_key_length: 1 byte
// - revocation_key: 33 bytes
// - witness_script_length: 1 byte
// - witness_script (offered_htlc_script)
OfferedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 1 + OfferedHtlcScriptSize
)
// estimateCommitTxWeight estimate commitment transaction weight depending on

@ -25,6 +25,14 @@ const (
// of a malicious counterparty's who broadcasts a revoked commitment
// transaction.
CommitmentRevoke WitnessType = 2
// HtlcOfferedRevoke is a witness that allows us to sweep an HTLC
// output that we offered to the counterparty.
HtlcOfferedRevoke WitnessType = 3
// HtlcAcceptedRevoke is a witness that allows us to sweep an HTLC
// output that we accepted from the counterparty.
HtlcAcceptedRevoke WitnessType = 4
)
// WitnessGenerator represents a function which is able to generate the final
@ -35,7 +43,7 @@ type WitnessGenerator func(tx *wire.MsgTx, hc *txscript.TxSigHashes,
// GenWitnessFunc will return a WitnessGenerator function that an output
// uses to generate the witness for a sweep transaction.
func (wt WitnessType) GenWitnessFunc(signer *Signer,
func (wt WitnessType) GenWitnessFunc(signer Signer,
descriptor *SignDescriptor) WitnessGenerator {
return func(tx *wire.MsgTx, hc *txscript.TxSigHashes,
@ -47,11 +55,15 @@ func (wt WitnessType) GenWitnessFunc(signer *Signer,
switch wt {
case CommitmentTimeLock:
return CommitSpendTimeout(*signer, desc, tx)
return CommitSpendTimeout(signer, desc, tx)
case CommitmentNoDelay:
return CommitSpendNoDelay(*signer, desc, tx)
return CommitSpendNoDelay(signer, desc, tx)
case CommitmentRevoke:
return CommitSpendRevoke(*signer, desc, tx)
return CommitSpendRevoke(signer, desc, tx)
case HtlcOfferedRevoke:
return ReceiverHtlcSpendRevoke(signer, desc, tx)
case HtlcAcceptedRevoke:
return SenderHtlcSpendRevoke(signer, desc, tx)
default:
return nil, fmt.Errorf("unknown witness type: %v", wt)
}

@ -23,6 +23,7 @@ import (
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/connmgr"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
"github.com/go-errors/errors"
@ -288,8 +289,27 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
return nil, err
}
s.breachArbiter = newBreachArbiter(cc.wallet, chanDB, cc.chainNotifier,
s.htlcSwitch, s.cc.chainIO, s.cc.feeEstimator)
// Construct a closure that wraps the htlcswitch's CloseLink method.
closeLink := func(chanPoint *wire.OutPoint,
closureType htlcswitch.ChannelCloseType) {
// TODO(conner): Properly respect the update and error channels
// returned by CloseLink.
s.htlcSwitch.CloseLink(chanPoint, closureType)
}
s.breachArbiter = newBreachArbiter(&BreachConfig{
Signer: cc.wallet.Cfg.Signer,
DB: chanDB,
PublishTransaction: cc.wallet.PublishTransaction,
Notifier: cc.chainNotifier,
ChainIO: s.cc.chainIO,
Estimator: s.cc.feeEstimator,
CloseLink: closeLink,
Store: newRetributionStore(chanDB),
GenSweepScript: func() ([]byte, error) {
return newSweepPkScript(cc.wallet)
},
})
// Create the connection manager which will be responsible for
// maintaining persistent outbound connections and also accepting new

@ -786,8 +786,7 @@ func fetchGraduatingOutputs(db *channeldb.DB, wallet *lnwallet.LightningWallet,
// output or not.
for _, kgtnOutput := range kgtnOutputs {
kgtnOutput.witnessFunc = kgtnOutput.witnessType.GenWitnessFunc(
&wallet.Cfg.Signer, kgtnOutput.signDescriptor,
)
wallet.Cfg.Signer, kgtnOutput.signDescriptor)
}
utxnLog.Infof("New block: height=%v, sweeping %v mature outputs",