Merge pull request #341 from cfromknecht/breach-filter-commit-dust
Breach Arbiter Ignore Dust Commitment Outputs
This commit is contained in:
commit
1d487ea78b
263
breacharbiter.go
263
breacharbiter.go
@ -579,9 +579,24 @@ func (b *breachArbiter) exactRetribution(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): factor in HTLCs
|
// Compute both the total value of funds being swept and the
|
||||||
revokedFunds := breachInfo.revokedOutput.amt
|
// amount of funds that were revoked from the counter party.
|
||||||
totalFunds := revokedFunds + breachInfo.selfOutput.amt
|
var totalFunds, revokedFunds btcutil.Amount
|
||||||
|
for _, input := range breachInfo.breachedOutputs {
|
||||||
|
totalFunds += input.Amount()
|
||||||
|
|
||||||
|
// If the output being revoked is the remote commitment
|
||||||
|
// output or an offered HTLC output, it's amount
|
||||||
|
// contributes to the value of funds being revoked from
|
||||||
|
// the counter party.
|
||||||
|
switch input.WitnessType() {
|
||||||
|
case lnwallet.CommitmentRevoke:
|
||||||
|
revokedFunds += input.Amount()
|
||||||
|
case lnwallet.HtlcOfferedRevoke:
|
||||||
|
revokedFunds += input.Amount()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
brarLog.Infof("Justice for ChannelPoint(%v) has "+
|
brarLog.Infof("Justice for ChannelPoint(%v) has "+
|
||||||
"been served, %v revoked funds (%v total) "+
|
"been served, %v revoked funds (%v total) "+
|
||||||
@ -627,7 +642,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
|
|||||||
|
|
||||||
chanPoint := contract.ChannelPoint()
|
chanPoint := contract.ChannelPoint()
|
||||||
|
|
||||||
brarLog.Debugf("Breach observer for ChannelPoint(%v) started",
|
brarLog.Debugf("Breach observer for ChannelPoint(%v) started ",
|
||||||
chanPoint)
|
chanPoint)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@ -800,6 +815,15 @@ type SpendableOutput interface {
|
|||||||
// construct the corresponding transaction input.
|
// construct the corresponding transaction input.
|
||||||
OutPoint() *wire.OutPoint
|
OutPoint() *wire.OutPoint
|
||||||
|
|
||||||
|
// WitnessType returns an enum specifying the type of witness that must
|
||||||
|
// be generated in order to spend this output.
|
||||||
|
WitnessType() lnwallet.WitnessType
|
||||||
|
|
||||||
|
// SignDesc returns a reference to a spendable output's sign descriptor,
|
||||||
|
// which is used during signing to compute a valid witness that spends
|
||||||
|
// this output.
|
||||||
|
SignDesc() *lnwallet.SignDescriptor
|
||||||
|
|
||||||
// BuildWitness returns a valid witness allowing this output to be
|
// BuildWitness returns a valid witness allowing this output to be
|
||||||
// spent, the witness should be attached to the transaction at the
|
// spent, the witness should be attached to the transaction at the
|
||||||
// location determined by the given `txinIdx`.
|
// location determined by the given `txinIdx`.
|
||||||
@ -820,15 +844,15 @@ type breachedOutput struct {
|
|||||||
witnessFunc lnwallet.WitnessGenerator
|
witnessFunc lnwallet.WitnessGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
// newBreachedOutput 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 newBreachedOutput(outpoint *wire.OutPoint,
|
func makeBreachedOutput(outpoint *wire.OutPoint,
|
||||||
witnessType lnwallet.WitnessType,
|
witnessType lnwallet.WitnessType,
|
||||||
signDescriptor *lnwallet.SignDescriptor) *breachedOutput {
|
signDescriptor *lnwallet.SignDescriptor) breachedOutput {
|
||||||
|
|
||||||
amount := signDescriptor.Output.Value
|
amount := signDescriptor.Output.Value
|
||||||
|
|
||||||
return &breachedOutput{
|
return breachedOutput{
|
||||||
amt: btcutil.Amount(amount),
|
amt: btcutil.Amount(amount),
|
||||||
outpoint: *outpoint,
|
outpoint: *outpoint,
|
||||||
witnessType: witnessType,
|
witnessType: witnessType,
|
||||||
@ -841,12 +865,24 @@ func (bo *breachedOutput) Amount() btcutil.Amount {
|
|||||||
return bo.amt
|
return bo.amt
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutPoint returns the breached outputs identifier that is to be included as a
|
// OutPoint returns the breached output's identifier that is to be included as a
|
||||||
// transaction input.
|
// transaction input.
|
||||||
func (bo *breachedOutput) OutPoint() *wire.OutPoint {
|
func (bo *breachedOutput) OutPoint() *wire.OutPoint {
|
||||||
return &bo.outpoint
|
return &bo.outpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WitnessType returns the type of witness that must be generated to spend the
|
||||||
|
// breached output.
|
||||||
|
func (bo *breachedOutput) WitnessType() lnwallet.WitnessType {
|
||||||
|
return bo.witnessType
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignDesc returns the breached output's SignDescriptor, which is used during
|
||||||
|
// signing to compute the witness.
|
||||||
|
func (bo *breachedOutput) SignDesc() *lnwallet.SignDescriptor {
|
||||||
|
return &bo.signDesc
|
||||||
|
}
|
||||||
|
|
||||||
// BuildWitness computes a valid witness that allows us to spend from the
|
// BuildWitness computes a valid witness that allows us to spend from the
|
||||||
// breached output. It does so by first generating and memoizing the witness
|
// breached output. It does so by first generating and memoizing the witness
|
||||||
// generation function, which parameterized primarily by the witness type and
|
// generation function, which parameterized primarily by the witness type and
|
||||||
@ -861,7 +897,7 @@ func (bo *breachedOutput) BuildWitness(signer lnwallet.Signer,
|
|||||||
// been initialized for this breached output.
|
// been initialized for this breached output.
|
||||||
if bo.witnessFunc == nil {
|
if bo.witnessFunc == nil {
|
||||||
bo.witnessFunc = bo.witnessType.GenWitnessFunc(
|
bo.witnessFunc = bo.witnessType.GenWitnessFunc(
|
||||||
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
|
||||||
@ -894,13 +930,7 @@ type retributionInfo struct {
|
|||||||
capacity btcutil.Amount
|
capacity btcutil.Amount
|
||||||
settledBalance btcutil.Amount
|
settledBalance btcutil.Amount
|
||||||
|
|
||||||
selfOutput *breachedOutput
|
breachedOutputs []breachedOutput
|
||||||
|
|
||||||
revokedOutput *breachedOutput
|
|
||||||
|
|
||||||
htlcOutputs []*breachedOutput
|
|
||||||
|
|
||||||
doneChan chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRetributionInfo constructs a retributionInfo containing all the
|
// newRetributionInfo constructs a retributionInfo containing all the
|
||||||
@ -911,31 +941,48 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
|
|||||||
breachInfo *lnwallet.BreachRetribution,
|
breachInfo *lnwallet.BreachRetribution,
|
||||||
chanInfo *channeldb.ChannelSnapshot) *retributionInfo {
|
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.
|
// Determine the number of second layer HTLCs we will attempt to sweep.
|
||||||
nHtlcs := len(breachInfo.HtlcRetributions)
|
nHtlcs := len(breachInfo.HtlcRetributions)
|
||||||
|
|
||||||
// Lastly, for each of the breached HTLC outputs, assemble the
|
// Initialize a slice to hold the outputs we will attempt to sweep. The
|
||||||
// information we will persist to disk, such that we will be able to
|
// maximum capacity of the slice is set to 2+nHtlcs to handle the case
|
||||||
// deterministically generate a valid witness for each output. This will
|
// where the local, remote, and all HTLCs are not dust outputs. All
|
||||||
// allow the breach arbiter to recover from failures, in the event that
|
// HTLC outputs provided by the wallet are guaranteed to be non-dust,
|
||||||
// it must sign and broadcast the justice transaction.
|
// though the commitment outputs are conditionally added depending on
|
||||||
htlcOutputs := make([]*breachedOutput, nHtlcs)
|
// the nil-ness of their sign descriptors.
|
||||||
|
breachedOutputs := make([]breachedOutput, 0, nHtlcs+2)
|
||||||
|
|
||||||
|
// First, record the breach information for the local channel point if
|
||||||
|
// it is not considered dust, which is signaled by a non-nil sign
|
||||||
|
// descriptor. Here we use CommitmentNoDelay since this output belongs
|
||||||
|
// to us and has no time-based constraints on spending.
|
||||||
|
if breachInfo.LocalOutputSignDesc != nil {
|
||||||
|
localOutput := makeBreachedOutput(
|
||||||
|
&breachInfo.LocalOutpoint,
|
||||||
|
lnwallet.CommitmentNoDelay,
|
||||||
|
breachInfo.LocalOutputSignDesc)
|
||||||
|
|
||||||
|
breachedOutputs = append(breachedOutputs, localOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second, record the same information regarding the remote outpoint,
|
||||||
|
// again if it is not dust, 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.
|
||||||
|
if breachInfo.RemoteOutputSignDesc != nil {
|
||||||
|
remoteOutput := makeBreachedOutput(
|
||||||
|
&breachInfo.RemoteOutpoint,
|
||||||
|
lnwallet.CommitmentRevoke,
|
||||||
|
breachInfo.RemoteOutputSignDesc)
|
||||||
|
|
||||||
|
breachedOutputs = append(breachedOutputs, remoteOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lastly, for each of the breached HTLC outputs, record each as a
|
||||||
|
// breached output with the appropriate witness type based on its
|
||||||
|
// directionality. All HTLC outputs provided by the wallet are assumed
|
||||||
|
// to be non-dust.
|
||||||
for i, breachedHtlc := range breachInfo.HtlcRetributions {
|
for i, breachedHtlc := range breachInfo.HtlcRetributions {
|
||||||
// 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
|
||||||
@ -947,9 +994,12 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
|
|||||||
htlcWitnessType = lnwallet.HtlcOfferedRevoke
|
htlcWitnessType = lnwallet.HtlcOfferedRevoke
|
||||||
}
|
}
|
||||||
|
|
||||||
htlcOutputs[i] = newBreachedOutput(
|
htlcOutput := makeBreachedOutput(
|
||||||
&breachInfo.HtlcRetributions[i].OutPoint, htlcWitnessType,
|
&breachInfo.HtlcRetributions[i].OutPoint,
|
||||||
|
htlcWitnessType,
|
||||||
&breachInfo.HtlcRetributions[i].SignDesc)
|
&breachInfo.HtlcRetributions[i].SignDesc)
|
||||||
|
|
||||||
|
breachedOutputs = append(breachedOutputs, htlcOutput)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(conner): remove dependency on channel snapshot after decoupling
|
// TODO(conner): remove dependency on channel snapshot after decoupling
|
||||||
@ -961,9 +1011,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
|
|||||||
remoteIdentity: &chanInfo.RemoteIdentity,
|
remoteIdentity: &chanInfo.RemoteIdentity,
|
||||||
capacity: chanInfo.Capacity,
|
capacity: chanInfo.Capacity,
|
||||||
settledBalance: chanInfo.LocalBalance.ToSatoshis(),
|
settledBalance: chanInfo.LocalBalance.ToSatoshis(),
|
||||||
selfOutput: selfOutput,
|
breachedOutputs: breachedOutputs,
|
||||||
revokedOutput: revokedOutput,
|
|
||||||
htlcOutputs: htlcOutputs,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -974,43 +1022,71 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
|
|||||||
func (b *breachArbiter) createJusticeTx(
|
func (b *breachArbiter) createJusticeTx(
|
||||||
r *retributionInfo) (*wire.MsgTx, error) {
|
r *retributionInfo) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
// Determine the number of HTLCs to be swept by the justice txn.
|
// We will assemble the breached outputs into a slice of spendable
|
||||||
nHtlcs := len(r.htlcOutputs)
|
// outputs, while simultaneously computing the estimated weight of the
|
||||||
|
// transaction.
|
||||||
|
var (
|
||||||
|
spendableOutputs []SpendableOutput
|
||||||
|
txWeight uint64
|
||||||
|
)
|
||||||
|
|
||||||
// Assemble the breached outputs into a slice of spendable outputs,
|
// Allocate enough space to potentially hold each of the breached
|
||||||
// starting with the self and revoked outputs, then adding any htlc
|
// outputs in the retribution info.
|
||||||
// outputs.
|
spendableOutputs = make([]SpendableOutput, 0, len(r.breachedOutputs))
|
||||||
breachedOutputs := make([]SpendableOutput, 2+nHtlcs)
|
|
||||||
breachedOutputs[0] = r.selfOutput
|
|
||||||
breachedOutputs[1] = r.revokedOutput
|
|
||||||
for i, htlcOutput := range r.htlcOutputs {
|
|
||||||
breachedOutputs[2+i] = htlcOutput
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the transaction weight of the justice transaction, which
|
// The justice transaction we construct will be a segwit transaction
|
||||||
// includes 2 + nHtlcs inputs and one output.
|
// that pays to a p2wkh output. Components such as the version,
|
||||||
var txWeight uint64
|
// nLockTime, and output are included in the BaseSweepTxSize, while the
|
||||||
// Begin with a base txn weight, e.g. version, nLockTime, etc.
|
// WitnessHeaderSize accounts for the two bytes that signal this as a
|
||||||
|
// segwit transaction.
|
||||||
txWeight += 4*lnwallet.BaseSweepTxSize + lnwallet.WitnessHeaderSize
|
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
|
|
||||||
|
|
||||||
// Compute the appropriate weight contributed by each revoked accepted
|
// Next, we iterate over the breached outputs contained in the
|
||||||
// or offered HTLC witnesses and tx inputs.
|
// retribution info. For each, we switch over the witness type such
|
||||||
for _, htlcOutput := range r.htlcOutputs {
|
// that we contribute the appropriate weight for each input and witness,
|
||||||
switch htlcOutput.witnessType {
|
// finally adding to our list of spendable outputs.
|
||||||
|
for i := range r.breachedOutputs {
|
||||||
|
// Grab locally scoped reference to breached output.
|
||||||
|
input := &r.breachedOutputs[i]
|
||||||
|
|
||||||
|
// First, select the appropriate estimated witness weight for
|
||||||
|
// the give witness type of this breached output. If the witness
|
||||||
|
// type is unrecognized, we will omit it from the transaction.
|
||||||
|
var witnessWeight uint64
|
||||||
|
switch input.WitnessType() {
|
||||||
|
case lnwallet.CommitmentNoDelay:
|
||||||
|
witnessWeight = lnwallet.ToLocalPenaltyWitnessSize
|
||||||
|
|
||||||
|
case lnwallet.CommitmentRevoke:
|
||||||
|
witnessWeight = lnwallet.P2WKHWitnessSize
|
||||||
|
|
||||||
case lnwallet.HtlcOfferedRevoke:
|
case lnwallet.HtlcOfferedRevoke:
|
||||||
txWeight += 4*lnwallet.InputSize +
|
witnessWeight = lnwallet.OfferedHtlcPenaltyWitnessSize
|
||||||
lnwallet.OfferedHtlcPenaltyWitnessSize
|
|
||||||
case lnwallet.HtlcAcceptedRevoke:
|
case lnwallet.HtlcAcceptedRevoke:
|
||||||
txWeight += 4*lnwallet.InputSize +
|
witnessWeight = lnwallet.AcceptedHtlcPenaltyWitnessSize
|
||||||
lnwallet.AcceptedHtlcPenaltyWitnessSize
|
|
||||||
}
|
default:
|
||||||
|
brarLog.Warnf("breached output in retribution info "+
|
||||||
|
"contains unexpected witness type: %v",
|
||||||
|
input.WitnessType())
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.sweepSpendableOutputsTxn(txWeight, breachedOutputs...)
|
// Next, each of the outputs in the retribution info will be
|
||||||
|
// used as inputs to the justice transaction. An input is
|
||||||
|
// considered non-witness data, so it is scaled accordingly.
|
||||||
|
txWeight += 4 * lnwallet.InputSize
|
||||||
|
|
||||||
|
// Additionally, we contribute the weight of the witness
|
||||||
|
// directly to the total transaction weight.
|
||||||
|
txWeight += witnessWeight
|
||||||
|
|
||||||
|
// Finally, append this input to our list of spendable outputs.
|
||||||
|
spendableOutputs = append(spendableOutputs, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.sweepSpendableOutputsTxn(txWeight, spendableOutputs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// craftCommitmentSweepTx creates a transaction to sweep the non-delayed output
|
// craftCommitmentSweepTx creates a transaction to sweep the non-delayed output
|
||||||
@ -1024,7 +1100,7 @@ func (b *breachArbiter) createJusticeTx(
|
|||||||
func (b *breachArbiter) craftCommitSweepTx(
|
func (b *breachArbiter) craftCommitSweepTx(
|
||||||
closeInfo *lnwallet.UnilateralCloseSummary) (*wire.MsgTx, error) {
|
closeInfo *lnwallet.UnilateralCloseSummary) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
selfOutput := newBreachedOutput(
|
selfOutput := makeBreachedOutput(
|
||||||
closeInfo.SelfOutPoint,
|
closeInfo.SelfOutPoint,
|
||||||
lnwallet.CommitmentNoDelay,
|
lnwallet.CommitmentNoDelay,
|
||||||
closeInfo.SelfOutputSignDesc,
|
closeInfo.SelfOutputSignDesc,
|
||||||
@ -1038,7 +1114,7 @@ func (b *breachArbiter) craftCommitSweepTx(
|
|||||||
// Add to_local p2wpkh witness and tx input.
|
// Add to_local p2wpkh witness and tx input.
|
||||||
txWeight += 4*lnwallet.InputSize + lnwallet.P2WKHWitnessSize
|
txWeight += 4*lnwallet.InputSize + lnwallet.P2WKHWitnessSize
|
||||||
|
|
||||||
return b.sweepSpendableOutputsTxn(txWeight, selfOutput)
|
return b.sweepSpendableOutputsTxn(txWeight, &selfOutput)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of
|
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of
|
||||||
@ -1272,21 +1348,13 @@ func (ret *retributionInfo) Encode(w io.Writer) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ret.selfOutput.Encode(w); err != nil {
|
nOutputs := len(ret.breachedOutputs)
|
||||||
|
if err := wire.WriteVarInt(w, 0, uint64(nOutputs)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ret.revokedOutput.Encode(w); err != nil {
|
for _, output := range ret.breachedOutputs {
|
||||||
return err
|
if err := output.Encode(w); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
numHtlcOutputs := len(ret.htlcOutputs)
|
|
||||||
if err := wire.WriteVarInt(w, 0, uint64(numHtlcOutputs)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < numHtlcOutputs; i++ {
|
|
||||||
if err := ret.htlcOutputs[i].Encode(w); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1331,26 +1399,15 @@ func (ret *retributionInfo) Decode(r io.Reader) error {
|
|||||||
ret.settledBalance = btcutil.Amount(
|
ret.settledBalance = btcutil.Amount(
|
||||||
binary.BigEndian.Uint64(scratch[:8]))
|
binary.BigEndian.Uint64(scratch[:8]))
|
||||||
|
|
||||||
ret.selfOutput = &breachedOutput{}
|
nOutputsU64, err := wire.ReadVarInt(r, 0)
|
||||||
if err := ret.selfOutput.Decode(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.revokedOutput = &breachedOutput{}
|
|
||||||
if err := ret.revokedOutput.Decode(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
numHtlcOutputsU64, err := wire.ReadVarInt(r, 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
numHtlcOutputs := int(numHtlcOutputsU64)
|
nOutputs := int(nOutputsU64)
|
||||||
|
|
||||||
ret.htlcOutputs = make([]*breachedOutput, numHtlcOutputs)
|
ret.breachedOutputs = make([]breachedOutput, nOutputs)
|
||||||
for i := range ret.htlcOutputs {
|
for i := range ret.breachedOutputs {
|
||||||
ret.htlcOutputs[i] = &breachedOutput{}
|
if err := ret.breachedOutputs[i].Decode(r); err != nil {
|
||||||
if err := ret.htlcOutputs[i].Decode(r); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,116 +78,135 @@ var (
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
breachSignDescs = []lnwallet.SignDescriptor{
|
|
||||||
{
|
|
||||||
SingleTweak: []byte{
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
},
|
|
||||||
WitnessScript: []byte{
|
|
||||||
0x00, 0x14, 0xee, 0x91, 0x41, 0x7e, 0x85, 0x6c, 0xde,
|
|
||||||
0x10, 0xa2, 0x91, 0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2,
|
|
||||||
0xef, 0xb5, 0x71, 0x48,
|
|
||||||
},
|
|
||||||
Output: &wire.TxOut{
|
|
||||||
Value: 5000000000,
|
|
||||||
PkScript: []byte{
|
|
||||||
0x41, // OP_DATA_65
|
|
||||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
|
||||||
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
|
||||||
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
|
||||||
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
|
||||||
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
|
||||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
|
||||||
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
|
||||||
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
|
||||||
0xa6, // 65-byte signature
|
|
||||||
0xac, // OP_CHECKSIG
|
|
||||||
},
|
|
||||||
},
|
|
||||||
HashType: txscript.SigHashAll,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
SingleTweak: []byte{
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
},
|
|
||||||
WitnessScript: []byte{
|
|
||||||
0x00, 0x14, 0xee, 0x91, 0x41, 0x7e, 0x85, 0x6c, 0xde,
|
|
||||||
0x10, 0xa2, 0x91, 0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2,
|
|
||||||
0xef, 0xb5, 0x71, 0x48,
|
|
||||||
},
|
|
||||||
Output: &wire.TxOut{
|
|
||||||
Value: 5000000000,
|
|
||||||
PkScript: []byte{
|
|
||||||
0x41, // OP_DATA_65
|
|
||||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
|
||||||
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
|
||||||
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
|
||||||
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
|
||||||
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
|
||||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
|
||||||
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
|
||||||
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
|
||||||
0xa6, // 65-byte signature
|
|
||||||
0xac, // OP_CHECKSIG
|
|
||||||
},
|
|
||||||
},
|
|
||||||
HashType: txscript.SigHashAll,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
SingleTweak: []byte{
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
0x02, 0x02, 0x02, 0x02, 0x02,
|
|
||||||
},
|
|
||||||
WitnessScript: []byte{
|
|
||||||
0x00, 0x14, 0xee, 0x91, 0x41, 0x7e, 0x85, 0x6c, 0xde,
|
|
||||||
0x10, 0xa2, 0x91, 0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2,
|
|
||||||
0xef, 0xb5, 0x71, 0x48,
|
|
||||||
},
|
|
||||||
Output: &wire.TxOut{
|
|
||||||
Value: 5000000000,
|
|
||||||
PkScript: []byte{
|
|
||||||
0x41, // OP_DATA_65
|
|
||||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
|
||||||
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
|
||||||
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
|
||||||
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
|
||||||
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
|
||||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
|
||||||
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
|
||||||
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
|
||||||
0xa6, // 65-byte signature
|
|
||||||
0xac, // OP_CHECKSIG
|
|
||||||
},
|
|
||||||
},
|
|
||||||
HashType: txscript.SigHashAll,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
breachedOutputs = []breachedOutput{
|
breachedOutputs = []breachedOutput{
|
||||||
{
|
{
|
||||||
amt: btcutil.Amount(1e7),
|
amt: btcutil.Amount(1e7),
|
||||||
outpoint: breachOutPoints[0],
|
outpoint: breachOutPoints[0],
|
||||||
witnessType: lnwallet.CommitmentNoDelay,
|
witnessType: lnwallet.CommitmentNoDelay,
|
||||||
|
signDesc: lnwallet.SignDescriptor{
|
||||||
|
SingleTweak: []byte{
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02,
|
||||||
|
},
|
||||||
|
WitnessScript: []byte{
|
||||||
|
0x00, 0x14, 0xee, 0x91, 0x41, 0x7e,
|
||||||
|
0x85, 0x6c, 0xde, 0x10, 0xa2, 0x91,
|
||||||
|
0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2,
|
||||||
|
0xef, 0xb5, 0x71, 0x48,
|
||||||
|
},
|
||||||
|
Output: &wire.TxOut{
|
||||||
|
Value: 5000000000,
|
||||||
|
PkScript: []byte{
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0,
|
||||||
|
0x9e, 0xb1, 0xc5, 0xfe, 0x29,
|
||||||
|
0x5a, 0xbd, 0xeb, 0x1d, 0xca,
|
||||||
|
0x42, 0x81, 0xbe, 0x98, 0x8e,
|
||||||
|
0x2d, 0xa0, 0xb6, 0xc1, 0xc6,
|
||||||
|
0xa5, 0x9d, 0xc2, 0x26, 0xc2,
|
||||||
|
0x86, 0x24, 0xe1, 0x81, 0x75,
|
||||||
|
0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||||
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3,
|
||||||
|
0x1f, 0x04, 0x78, 0x34, 0xbc,
|
||||||
|
0x06, 0xd6, 0xd6, 0xed, 0xf6,
|
||||||
|
0x20, 0xd1, 0x84, 0x24, 0x1a,
|
||||||
|
0x6a, 0xed, 0x8b, 0x63,
|
||||||
|
0xa6, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HashType: txscript.SigHashAll,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
amt: btcutil.Amount(2e9),
|
amt: btcutil.Amount(2e9),
|
||||||
outpoint: breachOutPoints[1],
|
outpoint: breachOutPoints[1],
|
||||||
witnessType: lnwallet.CommitmentRevoke,
|
witnessType: lnwallet.CommitmentRevoke,
|
||||||
|
signDesc: lnwallet.SignDescriptor{
|
||||||
|
SingleTweak: []byte{
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02,
|
||||||
|
},
|
||||||
|
WitnessScript: []byte{
|
||||||
|
0x00, 0x14, 0xee, 0x91, 0x41, 0x7e,
|
||||||
|
0x85, 0x6c, 0xde, 0x10, 0xa2, 0x91,
|
||||||
|
0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2,
|
||||||
|
0xef, 0xb5, 0x71, 0x48,
|
||||||
|
},
|
||||||
|
Output: &wire.TxOut{
|
||||||
|
Value: 5000000000,
|
||||||
|
PkScript: []byte{
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0,
|
||||||
|
0x9e, 0xb1, 0xc5, 0xfe, 0x29,
|
||||||
|
0x5a, 0xbd, 0xeb, 0x1d, 0xca,
|
||||||
|
0x42, 0x81, 0xbe, 0x98, 0x8e,
|
||||||
|
0x2d, 0xa0, 0xb6, 0xc1, 0xc6,
|
||||||
|
0xa5, 0x9d, 0xc2, 0x26, 0xc2,
|
||||||
|
0x86, 0x24, 0xe1, 0x81, 0x75,
|
||||||
|
0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||||
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3,
|
||||||
|
0x1f, 0x04, 0x78, 0x34, 0xbc,
|
||||||
|
0x06, 0xd6, 0xd6, 0xed, 0xf6,
|
||||||
|
0x20, 0xd1, 0x84, 0x24, 0x1a,
|
||||||
|
0x6a, 0xed, 0x8b, 0x63,
|
||||||
|
0xa6, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HashType: txscript.SigHashAll,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
amt: btcutil.Amount(3e4),
|
amt: btcutil.Amount(3e4),
|
||||||
outpoint: breachOutPoints[2],
|
outpoint: breachOutPoints[2],
|
||||||
witnessType: lnwallet.CommitmentDelayOutput,
|
witnessType: lnwallet.CommitmentDelayOutput,
|
||||||
|
signDesc: lnwallet.SignDescriptor{
|
||||||
|
SingleTweak: []byte{
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||||
|
0x02, 0x02,
|
||||||
|
},
|
||||||
|
WitnessScript: []byte{
|
||||||
|
0x00, 0x14, 0xee, 0x91, 0x41, 0x7e,
|
||||||
|
0x85, 0x6c, 0xde, 0x10, 0xa2, 0x91,
|
||||||
|
0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2,
|
||||||
|
0xef, 0xb5, 0x71, 0x48,
|
||||||
|
},
|
||||||
|
Output: &wire.TxOut{
|
||||||
|
Value: 5000000000,
|
||||||
|
PkScript: []byte{
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0,
|
||||||
|
0x9e, 0xb1, 0xc5, 0xfe, 0x29,
|
||||||
|
0x5a, 0xbd, 0xeb, 0x1d, 0xca,
|
||||||
|
0x42, 0x81, 0xbe, 0x98, 0x8e,
|
||||||
|
0x2d, 0xa0, 0xb6, 0xc1, 0xc6,
|
||||||
|
0xa5, 0x9d, 0xc2, 0x26, 0xc2,
|
||||||
|
0x86, 0x24, 0xe1, 0x81, 0x75,
|
||||||
|
0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||||
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3,
|
||||||
|
0x1f, 0x04, 0x78, 0x34, 0xbc,
|
||||||
|
0x06, 0xd6, 0xd6, 0xed, 0xf6,
|
||||||
|
0x20, 0xd1, 0x84, 0x24, 0x1a,
|
||||||
|
0x6a, 0xed, 0x8b, 0x63,
|
||||||
|
0xa6, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HashType: txscript.SigHashAll,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,9 +222,8 @@ var (
|
|||||||
chanPoint: breachOutPoints[0],
|
chanPoint: breachOutPoints[0],
|
||||||
capacity: btcutil.Amount(1e7),
|
capacity: btcutil.Amount(1e7),
|
||||||
settledBalance: btcutil.Amount(1e7),
|
settledBalance: btcutil.Amount(1e7),
|
||||||
selfOutput: &breachedOutputs[0],
|
// Set to breachedOutputs 0 and 1 in init()
|
||||||
revokedOutput: &breachedOutputs[1],
|
breachedOutputs: []breachedOutput{{}, {}},
|
||||||
htlcOutputs: []*breachedOutput{},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
commitHash: [chainhash.HashSize]byte{
|
commitHash: [chainhash.HashSize]byte{
|
||||||
@ -217,12 +235,8 @@ var (
|
|||||||
chanPoint: breachOutPoints[1],
|
chanPoint: breachOutPoints[1],
|
||||||
capacity: btcutil.Amount(1e7),
|
capacity: btcutil.Amount(1e7),
|
||||||
settledBalance: btcutil.Amount(1e7),
|
settledBalance: btcutil.Amount(1e7),
|
||||||
selfOutput: &breachedOutputs[0],
|
// Set to breachedOutputs 1 and 2 in init()
|
||||||
revokedOutput: &breachedOutputs[1],
|
breachedOutputs: []breachedOutput{{}, {}},
|
||||||
htlcOutputs: []*breachedOutput{
|
|
||||||
&breachedOutputs[1],
|
|
||||||
&breachedOutputs[2],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -238,7 +252,11 @@ func init() {
|
|||||||
for i := range retributions {
|
for i := range retributions {
|
||||||
retInfo := &retributions[i]
|
retInfo := &retributions[i]
|
||||||
retInfo.remoteIdentity = breachedOutputs[i].signDesc.PubKey
|
retInfo.remoteIdentity = breachedOutputs[i].signDesc.PubKey
|
||||||
|
retInfo.breachedOutputs[0] = breachedOutputs[i]
|
||||||
|
retInfo.breachedOutputs[1] = breachedOutputs[i+1]
|
||||||
|
|
||||||
retributionMap[retInfo.chanPoint] = *retInfo
|
retributionMap[retInfo.chanPoint] = *retInfo
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,14 +328,12 @@ func initBreachedOutputs() error {
|
|||||||
bo := &breachedOutputs[i]
|
bo := &breachedOutputs[i]
|
||||||
|
|
||||||
// Parse the sign descriptor's pubkey.
|
// Parse the sign descriptor's pubkey.
|
||||||
sd := &breachSignDescs[i]
|
|
||||||
pubkey, err := btcec.ParsePubKey(breachKeys[i], btcec.S256())
|
pubkey, err := btcec.ParsePubKey(breachKeys[i], btcec.S256())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to parse pubkey: %v",
|
return fmt.Errorf("unable to parse pubkey: %v",
|
||||||
breachKeys[i])
|
breachKeys[i])
|
||||||
}
|
}
|
||||||
sd.PubKey = pubkey
|
bo.signDesc.PubKey = pubkey
|
||||||
bo.signDesc = *sd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -325,7 +341,7 @@ func initBreachedOutputs() error {
|
|||||||
|
|
||||||
// Test that breachedOutput Encode/Decode works.
|
// Test that breachedOutput Encode/Decode works.
|
||||||
func TestBreachedOutputSerialization(t *testing.T) {
|
func TestBreachedOutputSerialization(t *testing.T) {
|
||||||
for i := 0; i < len(breachedOutputs); i++ {
|
for i := range breachedOutputs {
|
||||||
bo := &breachedOutputs[i]
|
bo := &breachedOutputs[i]
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
@ -353,7 +369,7 @@ func TestBreachedOutputSerialization(t *testing.T) {
|
|||||||
|
|
||||||
// Test that retribution Encode/Decode works.
|
// Test that retribution Encode/Decode works.
|
||||||
func TestRetributionSerialization(t *testing.T) {
|
func TestRetributionSerialization(t *testing.T) {
|
||||||
for i := 0; i < len(retributions); i++ {
|
for i := range retributions {
|
||||||
ret := &retributions[i]
|
ret := &retributions[i]
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
@ -381,7 +397,7 @@ func TestRetributionSerialization(t *testing.T) {
|
|||||||
|
|
||||||
// copyRetInfo creates a complete copy of the given retributionInfo.
|
// copyRetInfo creates a complete copy of the given retributionInfo.
|
||||||
func copyRetInfo(retInfo *retributionInfo) *retributionInfo {
|
func copyRetInfo(retInfo *retributionInfo) *retributionInfo {
|
||||||
nHtlcs := len(retInfo.htlcOutputs)
|
nOutputs := len(retInfo.breachedOutputs)
|
||||||
|
|
||||||
ret := &retributionInfo{
|
ret := &retributionInfo{
|
||||||
commitHash: retInfo.commitHash,
|
commitHash: retInfo.commitHash,
|
||||||
@ -389,13 +405,11 @@ func copyRetInfo(retInfo *retributionInfo) *retributionInfo {
|
|||||||
remoteIdentity: retInfo.remoteIdentity,
|
remoteIdentity: retInfo.remoteIdentity,
|
||||||
capacity: retInfo.capacity,
|
capacity: retInfo.capacity,
|
||||||
settledBalance: retInfo.settledBalance,
|
settledBalance: retInfo.settledBalance,
|
||||||
selfOutput: retInfo.selfOutput,
|
breachedOutputs: make([]breachedOutput, nOutputs),
|
||||||
revokedOutput: retInfo.revokedOutput,
|
|
||||||
htlcOutputs: make([]*breachedOutput, nHtlcs),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, htlco := range retInfo.htlcOutputs {
|
for i := range retInfo.breachedOutputs {
|
||||||
ret.htlcOutputs[i] = htlco
|
ret.breachedOutputs[i] = retInfo.breachedOutputs[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
260
lnd_test.go
260
lnd_test.go
@ -2010,6 +2010,258 @@ func testRevokedCloseRetribution(net *networkHarness, t *harnessTest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testRevokedCloseRetributionZeroValueRemoteOutput tests that Alice is able
|
||||||
|
// carry out retribution in the event that she fails in state where the remote
|
||||||
|
// commitment output has zero-value.
|
||||||
|
func testRevokedCloseRetributionZeroValueRemoteOutput(
|
||||||
|
net *networkHarness,
|
||||||
|
t *harnessTest) {
|
||||||
|
|
||||||
|
ctxb := context.Background()
|
||||||
|
const (
|
||||||
|
timeout = time.Duration(time.Second * 10)
|
||||||
|
chanAmt = maxFundingAmount
|
||||||
|
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([]string{"--debughtlc", "--hodlhtlc"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create new nodes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We must let Alice have an open channel before she can send a node
|
||||||
|
// announcement, so we open a channel with 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.
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||||
|
chanPoint := openChannelAndAssert(ctxt, t, net, net.Alice, carol,
|
||||||
|
chanAmt, 0)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
carolPaymentHashes := make([][]byte, numInvoices)
|
||||||
|
for i := 0; i < numInvoices; i++ {
|
||||||
|
preimage := bytes.Repeat([]byte{byte(192 - i)}, 32)
|
||||||
|
invoice := &lnrpc.Invoice{
|
||||||
|
Memo: "testing",
|
||||||
|
RPreimage: preimage,
|
||||||
|
Value: paymentAmt,
|
||||||
|
}
|
||||||
|
resp, err := carol.AddInvoice(ctxb, invoice)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to add invoice: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
carolPaymentHashes[i] = resp.RHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// As we'll be querying the state of Carols's channels frequently we'll
|
||||||
|
// create a closure helper function for the purpose.
|
||||||
|
getCarolChanInfo := func() (*lnrpc.ActiveChannel, error) {
|
||||||
|
req := &lnrpc.ListChannelsRequest{}
|
||||||
|
carolChannelInfo, err := carol.ListChannels(ctxb, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(carolChannelInfo.Channels) != 1 {
|
||||||
|
t.Fatalf("carol should only have a single channel, "+
|
||||||
|
"instead he has %v", len(carolChannelInfo.Channels))
|
||||||
|
}
|
||||||
|
|
||||||
|
return carolChannelInfo.Channels[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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->carol channel before "+
|
||||||
|
"timeout: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open up a payment stream to Alice that we'll use to send payment to
|
||||||
|
// Carol. We also create a small helper function to send payments to
|
||||||
|
// Carol, consuming the payment hashes we generated above.
|
||||||
|
alicePayStream, err := net.Alice.SendPayment(ctxb)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create payment stream for alice: %v", err)
|
||||||
|
}
|
||||||
|
sendPayments := func(start, stop int) error {
|
||||||
|
for i := start; i < stop; i++ {
|
||||||
|
sendReq := &lnrpc.SendRequest{
|
||||||
|
PaymentHash: carolPaymentHashes[i],
|
||||||
|
Dest: carol.PubKey[:],
|
||||||
|
Amt: paymentAmt,
|
||||||
|
}
|
||||||
|
if err := alicePayStream.Send(sendReq); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next query for Carol's channel state, as we sent 0 payments, Carol
|
||||||
|
// should now see her balance as being 0 satoshis.
|
||||||
|
carolChan, err := getCarolChanInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to get carol's channel info: %v", err)
|
||||||
|
}
|
||||||
|
if carolChan.LocalBalance != 0 {
|
||||||
|
t.Fatalf("carol's balance is incorrect, got %v, expected %v",
|
||||||
|
carolChan.LocalBalance, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab Carol's current commitment height (update number), we'll later
|
||||||
|
// revert her to this state after additional updates to force him to
|
||||||
|
// broadcast this soon to be revoked state.
|
||||||
|
carolStateNumPreCopy := carolChan.NumUpdates
|
||||||
|
|
||||||
|
// Create a temporary file to house Carol's database state at this
|
||||||
|
// particular point in history.
|
||||||
|
carolTempDbPath, err := ioutil.TempDir("", "carol-past-state")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create temp db folder: %v", err)
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
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(0, numInvoices); err != nil {
|
||||||
|
t.Fatalf("unable to send payment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
carolChan, err = getCarolChanInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to get carol chan info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we shutdown Carol, copying over the his temporary database state
|
||||||
|
// which has the *prior* channel state over his 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(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.
|
||||||
|
carolChan, err = getCarolChanInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to get carol chan info: %v", err)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
// feel the wrath of Alice's retribution.
|
||||||
|
force := true
|
||||||
|
closeUpdates, _, err := net.CloseChannel(ctxb, carol, chanPoint, force)
|
||||||
|
if err != nil {
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
if err := net.RestartNode(net.Alice, nil); err != nil {
|
||||||
|
t.Fatalf("unable to stop Alice's node: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
breachTXID, err := net.WaitForChannelClose(ctxb, closeUpdates)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error while waiting for channel close: %v", err)
|
||||||
|
}
|
||||||
|
assertTxInBlock(t, block, breachTXID)
|
||||||
|
|
||||||
|
// Query the mempool for Alice's justice transaction, this should be
|
||||||
|
// broadcast as Carol's contract breaching transaction gets confirmed
|
||||||
|
// above.
|
||||||
|
justiceTXID, 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(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
|
||||||
|
// been confirmed yet; when Alice restarts, she should start waiting for
|
||||||
|
// the justice transaction to confirm again.
|
||||||
|
if err := net.RestartNode(net.Alice, nil); err != nil {
|
||||||
|
t.Fatalf("unable to restart Alice's node: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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]
|
||||||
|
|
||||||
|
// The block should have exactly *two* transactions, one of which is
|
||||||
|
// the justice transaction.
|
||||||
|
if len(block.Transactions) != 2 {
|
||||||
|
t.Fatalf("transaction wasn't mined")
|
||||||
|
}
|
||||||
|
justiceSha := block.Transactions[1].TxHash()
|
||||||
|
if !bytes.Equal(justiceTx.Hash()[:], justiceSha[:]) {
|
||||||
|
t.Fatalf("justice tx wasn't mined")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, obtain Alice's channel state, she shouldn't report any
|
||||||
|
// channel as she just successfully brought Carol to justice by sweeping
|
||||||
|
// all the channel funds.
|
||||||
|
req := &lnrpc.ListChannelsRequest{}
|
||||||
|
aliceChanInfo, err := net.Alice.ListChannels(ctxb, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to query for alice's channels: %v", err)
|
||||||
|
}
|
||||||
|
if len(aliceChanInfo.Channels) != 0 {
|
||||||
|
t.Fatalf("alice shouldn't have a channel: %v",
|
||||||
|
spew.Sdump(aliceChanInfo.Channels))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// testRevokedCloseRetributionRemoteHodl tests that Alice properly responds to a
|
// testRevokedCloseRetributionRemoteHodl tests that Alice properly responds to a
|
||||||
// channel breach made by the remote party, specifically in the case that the
|
// channel breach made by the remote party, specifically in the case that the
|
||||||
// remote party breaches before settling extended HTLCs.
|
// remote party breaches before settling extended HTLCs.
|
||||||
@ -2248,7 +2500,7 @@ func testRevokedCloseRetributionRemoteHodl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Query the mempool for Alice's justice transaction, this should be
|
// Query the mempool for Alice's justice transaction, this should be
|
||||||
// broadcast as Bob's contract breaching transaction gets confirmed
|
// broadcast as Carol's contract breaching transaction gets confirmed
|
||||||
// above.
|
// above.
|
||||||
_, err = waitForTxInMempool(net.Miner.Node, 5*time.Second)
|
_, err = waitForTxInMempool(net.Miner.Node, 5*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -3520,11 +3772,13 @@ var testsCases = []*testCase{
|
|||||||
test: testBidirectionalAsyncPayments,
|
test: testBidirectionalAsyncPayments,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// TODO(roasbeef): test always needs to be last as Bob's state
|
|
||||||
// is borked since we trick him into attempting to cheat Alice?
|
|
||||||
name: "revoked uncooperative close retribution",
|
name: "revoked uncooperative close retribution",
|
||||||
test: testRevokedCloseRetribution,
|
test: testRevokedCloseRetribution,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "revoked uncooperative close retribution zero value remote output",
|
||||||
|
test: testRevokedCloseRetributionZeroValueRemoteOutput,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "revoked uncooperative close retribution remote hodl",
|
name: "revoked uncooperative close retribution remote hodl",
|
||||||
test: testRevokedCloseRetributionRemoteHodl,
|
test: testRevokedCloseRetributionRemoteHodl,
|
||||||
|
@ -1063,7 +1063,9 @@ type BreachRetribution struct {
|
|||||||
// LocalOutputSignDesc is a SignDescriptor which is capable of
|
// LocalOutputSignDesc is a SignDescriptor which is capable of
|
||||||
// generating the signature necessary to sweep the output within the
|
// generating the signature necessary to sweep the output within the
|
||||||
// BreachTransaction that pays directly us.
|
// BreachTransaction that pays directly us.
|
||||||
LocalOutputSignDesc SignDescriptor
|
// NOTE: A nil value indicates that the local output is considered dust
|
||||||
|
// according to the remote party's dust limit.
|
||||||
|
LocalOutputSignDesc *SignDescriptor
|
||||||
|
|
||||||
// LocalOutpoint is the outpoint of the output paying to us (the local
|
// LocalOutpoint is the outpoint of the output paying to us (the local
|
||||||
// party) within the breach transaction.
|
// party) within the breach transaction.
|
||||||
@ -1073,7 +1075,9 @@ type BreachRetribution struct {
|
|||||||
// generating the signature required to claim the funds as described
|
// generating the signature required to claim the funds as described
|
||||||
// within the revocation clause of the remote party's commitment
|
// within the revocation clause of the remote party's commitment
|
||||||
// output.
|
// output.
|
||||||
RemoteOutputSignDesc SignDescriptor
|
// NOTE: A nil value indicates that the local output is considered dust
|
||||||
|
// according to the remote party's dust limit.
|
||||||
|
RemoteOutputSignDesc *SignDescriptor
|
||||||
|
|
||||||
// RemoteOutpoint is the output of the output paying to the remote
|
// RemoteOutpoint is the output of the output paying to the remote
|
||||||
// party within the breach transaction.
|
// party within the breach transaction.
|
||||||
@ -1165,6 +1169,53 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Conditionally instantiate a sign descriptor for each of the
|
||||||
|
// commitment outputs. If either is considered dust using the remote
|
||||||
|
// party's dust limit, the respective sign descriptor will be nil.
|
||||||
|
var (
|
||||||
|
localSignDesc *SignDescriptor
|
||||||
|
remoteSignDesc *SignDescriptor
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compute the local and remote balances in satoshis.
|
||||||
|
localAmt := revokedSnapshot.LocalBalance.ToSatoshis()
|
||||||
|
remoteAmt := revokedSnapshot.RemoteBalance.ToSatoshis()
|
||||||
|
|
||||||
|
// If the local balance exceeds the remote party's dust limit,
|
||||||
|
// instantiate the local sign descriptor.
|
||||||
|
if localAmt >= chanState.RemoteChanCfg.DustLimit {
|
||||||
|
// We'll need to reconstruct the single tweak so we can sweep
|
||||||
|
// our non-delayed pay-to-self output self.
|
||||||
|
singleTweak := SingleTweakBytes(commitmentPoint,
|
||||||
|
chanState.LocalChanCfg.PaymentBasePoint)
|
||||||
|
|
||||||
|
localSignDesc = &SignDescriptor{
|
||||||
|
SingleTweak: singleTweak,
|
||||||
|
PubKey: chanState.LocalChanCfg.PaymentBasePoint,
|
||||||
|
WitnessScript: localPkScript,
|
||||||
|
Output: &wire.TxOut{
|
||||||
|
PkScript: localWitnessHash,
|
||||||
|
Value: int64(localAmt),
|
||||||
|
},
|
||||||
|
HashType: txscript.SigHashAll,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similarly, if the remote balance exceeds the remote party's dust
|
||||||
|
// limit, assemble the remote sign descriptor.
|
||||||
|
if remoteAmt >= chanState.RemoteChanCfg.DustLimit {
|
||||||
|
remoteSignDesc = &SignDescriptor{
|
||||||
|
PubKey: chanState.LocalChanCfg.RevocationBasePoint,
|
||||||
|
DoubleTweak: commitmentSecret,
|
||||||
|
WitnessScript: remotePkScript,
|
||||||
|
Output: &wire.TxOut{
|
||||||
|
PkScript: remoteWitnessHash,
|
||||||
|
Value: int64(remoteAmt),
|
||||||
|
},
|
||||||
|
HashType: txscript.SigHashAll,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// With the commitment outputs located, we'll now generate all the
|
// With the commitment outputs located, we'll now generate all the
|
||||||
// retribution structs for each of the HTLC transactions active on the
|
// retribution structs for each of the HTLC transactions active on the
|
||||||
// remote commitment transaction.
|
// remote commitment transaction.
|
||||||
@ -1216,11 +1267,6 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll need to reconstruct the single tweak so we can sweep our
|
|
||||||
// non-delayed pay-to-self output self.
|
|
||||||
singleTweak := SingleTweakBytes(commitmentPoint,
|
|
||||||
chanState.LocalChanCfg.PaymentBasePoint)
|
|
||||||
|
|
||||||
// Finally, with all the necessary data constructed, we can create the
|
// Finally, with all the necessary data constructed, we can create the
|
||||||
// BreachRetribution struct which houses all the data necessary to
|
// BreachRetribution struct which houses all the data necessary to
|
||||||
// swiftly bring justice to the cheating remote party.
|
// swiftly bring justice to the cheating remote party.
|
||||||
@ -1229,27 +1275,9 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
|||||||
RevokedStateNum: stateNum,
|
RevokedStateNum: stateNum,
|
||||||
PendingHTLCs: revokedSnapshot.Htlcs,
|
PendingHTLCs: revokedSnapshot.Htlcs,
|
||||||
LocalOutpoint: localOutpoint,
|
LocalOutpoint: localOutpoint,
|
||||||
LocalOutputSignDesc: SignDescriptor{
|
LocalOutputSignDesc: localSignDesc,
|
||||||
SingleTweak: singleTweak,
|
|
||||||
PubKey: chanState.LocalChanCfg.PaymentBasePoint,
|
|
||||||
WitnessScript: localPkScript,
|
|
||||||
Output: &wire.TxOut{
|
|
||||||
PkScript: localWitnessHash,
|
|
||||||
Value: int64(revokedSnapshot.LocalBalance.ToSatoshis()),
|
|
||||||
},
|
|
||||||
HashType: txscript.SigHashAll,
|
|
||||||
},
|
|
||||||
RemoteOutpoint: remoteOutpoint,
|
RemoteOutpoint: remoteOutpoint,
|
||||||
RemoteOutputSignDesc: SignDescriptor{
|
RemoteOutputSignDesc: remoteSignDesc,
|
||||||
PubKey: chanState.LocalChanCfg.RevocationBasePoint,
|
|
||||||
DoubleTweak: commitmentSecret,
|
|
||||||
WitnessScript: remotePkScript,
|
|
||||||
Output: &wire.TxOut{
|
|
||||||
PkScript: remoteWitnessHash,
|
|
||||||
Value: int64(revokedSnapshot.RemoteBalance.ToSatoshis()),
|
|
||||||
},
|
|
||||||
HashType: txscript.SigHashAll,
|
|
||||||
},
|
|
||||||
HtlcRetributions: htlcRetributions,
|
HtlcRetributions: htlcRetributions,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user