Skip to content

Commit

Permalink
Merge pull request #2515 from cfromknecht/altruist-output-calc
Browse files Browse the repository at this point in the history
watchtower/wtpolicy: altruist output calculation
  • Loading branch information
Roasbeef committed Feb 2, 2019
2 parents 8aecccf + 09e71a1 commit bcc9e30
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 82 deletions.
53 changes: 29 additions & 24 deletions watchtower/lookout/justice_descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/txsort"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
Expand Down Expand Up @@ -166,40 +167,41 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64,
})
}

// Using the total input amount and the transaction's weight, compute
// the sweep and reward amounts. This corresponds to the amount returned
// to the victim and the amount paid to the tower, respectively. To do
// so, the required transaction fee is subtracted from the total, and
// the remaining amount is divided according to the prenegotiated reward
// rate from the client's session info.
sweepAmt, rewardAmt, err := p.SessionInfo.ComputeSweepOutputs(
totalAmt, txWeight,
// Using the session's policy, compute the outputs that should be added
// to the justice transaction. In the case of an altruist sweep, there
// will be a single output paying back to the victim. Otherwise for a
// reward sweep, there will be two outputs, one of which pays back to
// the victim while the other gives a cut to the tower.
outputs, err := p.SessionInfo.Policy.ComputeJusticeTxOuts(
totalAmt, txWeight, p.JusticeKit.SweepAddress[:],
p.SessionInfo.RewardAddress,
)
if err != nil {
return nil, err
}

// TODO(conner): abort/don't add if outputs are dusty
// Attach the computed txouts to the justice transaction.
justiceTxn.TxOut = outputs

// Add the sweep and reward outputs to the justice transaction.
justiceTxn.AddTxOut(&wire.TxOut{
PkScript: p.JusticeKit.SweepAddress[:],
Value: int64(sweepAmt),
})
justiceTxn.AddTxOut(&wire.TxOut{
PkScript: p.SessionInfo.RewardAddress,
Value: int64(rewardAmt),
})

// TODO(conner): apply and handle BIP69 sort
// Apply a BIP69 sort to the resulting transaction.
txsort.InPlaceSort(justiceTxn)

btx := btcutil.NewTx(justiceTxn)
if err := blockchain.CheckTransactionSanity(btx); err != nil {
return nil, err
}

// Since the transaction inputs could have been reordered as a result of the
// BIP69 sort, create an index mapping each prevout to it's new index.
inputIndex := make(map[wire.OutPoint]int)
for i, txIn := range justiceTxn.TxIn {
inputIndex[txIn.PreviousOutPoint] = i
}

// Attach each of the provided witnesses to the transaction.
for i, input := range inputs {
for _, input := range inputs {
// Lookup the input's new post-sort position.
i := inputIndex[input.outPoint]
justiceTxn.TxIn[i].Witness = input.witness

// Validate the reconstructed witnesses to ensure they are valid
Expand Down Expand Up @@ -229,9 +231,6 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) {
weightEstimate input.TxWeightEstimator
)

// Add our reward address to the weight estimate.
weightEstimate.AddP2WKHOutput()

// Add the sweep address's contribution, depending on whether it is a
// p2wkh or p2wsh output.
switch len(p.JusticeKit.SweepAddress) {
Expand All @@ -245,6 +244,12 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) {
return nil, ErrUnknownSweepAddrType
}

// Add our reward address to the weight estimate if the policy's blob
// type specifies a reward output.
if p.SessionInfo.Policy.BlobType.Has(blob.FlagReward) {
weightEstimate.AddP2WKHOutput()
}

// Assemble the breached to-local output from the justice descriptor and
// add it to our weight estimate.
toLocalInput, err := p.commitToLocalInput()
Expand Down
79 changes: 52 additions & 27 deletions watchtower/lookout/justice_descriptor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/txsort"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
Expand Down Expand Up @@ -46,9 +47,39 @@ var (
0x70, 0x4c, 0xff, 0x1e, 0x9c, 0x00, 0x93, 0xbe,
0xe2, 0x2e, 0x68, 0x08, 0x4c, 0xb4, 0x0f, 0x4f,
}

rewardCommitType = blob.TypeFromFlags(
blob.FlagReward, blob.FlagCommitOutputs,
)

altruistCommitType = blob.FlagCommitOutputs.Type()
)

// TestJusticeDescriptor asserts that a JusticeDescriptor is able to produce the
// correct justice transaction for different blob types.
func TestJusticeDescriptor(t *testing.T) {
tests := []struct {
name string
blobType blob.Type
}{
{
name: "reward and commit type",
blobType: rewardCommitType,
},
{
name: "altruist and commit type",
blobType: altruistCommitType,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
testJusticeDescriptor(t, test.blobType)
})
}
}

func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
const (
localAmount = btcutil.Amount(100000)
remoteAmount = btcutil.Amount(200000)
Expand Down Expand Up @@ -113,33 +144,27 @@ func TestJusticeDescriptor(t *testing.T) {

// Compute the weight estimate for our justice transaction.
var weightEstimate input.TxWeightEstimator
weightEstimate.AddP2WKHOutput()
weightEstimate.AddP2WKHOutput()
weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize)
weightEstimate.AddWitnessInput(input.P2WKHWitnessSize)
weightEstimate.AddP2WKHOutput()
if blobType.Has(blob.FlagReward) {
weightEstimate.AddP2WKHOutput()
}
txWeight := weightEstimate.Weight()

// Create a session info so that simulate agreement of the sweep
// parameters that should be used in constructing the justice
// transaction.
policy := wtpolicy.Policy{
BlobType: blobType,
SweepFeeRate: 2000,
RewardRate: 900000,
}
sessionInfo := &wtdb.SessionInfo{
Policy: wtpolicy.Policy{
SweepFeeRate: 2000,
RewardRate: 900000,
},
Policy: policy,
RewardAddress: makeAddrSlice(22),
}

// Given the total input amount and the weight estimate, compute the
// amount that should be swept for the victim and the amount taken as a
// reward by the watchtower.
sweepAmt, rewardAmt, err := sessionInfo.ComputeSweepOutputs(
totalAmount, int64(txWeight),
)
if err != nil {
t.Fatalf("unable to compute sweep outputs: %v", err)
}

// Begin to assemble the justice kit, starting with the sweep address,
// pubkeys, and csv delay.
justiceKit := &blob.JusticeKit{
Expand Down Expand Up @@ -170,20 +195,20 @@ func TestJusticeDescriptor(t *testing.T) {
},
},
},
TxOut: []*wire.TxOut{
{

Value: int64(sweepAmt),
PkScript: justiceKit.SweepAddress,
},
{
}

Value: int64(rewardAmt),
PkScript: sessionInfo.RewardAddress,
},
},
outputs, err := policy.ComputeJusticeTxOuts(
totalAmount, int64(txWeight), justiceKit.SweepAddress,
sessionInfo.RewardAddress,
)
if err != nil {
t.Fatalf("unable to compute justice txouts: %v", err)
}

// Attach the txouts and BIP69 sort the resulting transaction.
justiceTxn.TxOut = outputs
txsort.InPlaceSort(justiceTxn)

hashCache := txscript.NewTxSigHashes(justiceTxn)

// Create the sign descriptor used to sign for the to-local input.
Expand Down
31 changes: 0 additions & 31 deletions watchtower/wtdb/session_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package wtdb
import (
"errors"

"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
)

Expand Down Expand Up @@ -35,11 +34,6 @@ var (
// number larger than the session's max number of updates.
ErrSessionConsumed = errors.New("all session updates have been " +
"consumed")

// ErrFeeExceedsInputs signals that the total input value of breaching
// commitment txn is insufficient to cover the fees required to sweep
// it.
ErrFeeExceedsInputs = errors.New("sweep fee exceeds input values")
)

// SessionInfo holds the negotiated session parameters for single session id,
Expand Down Expand Up @@ -98,31 +92,6 @@ func (s *SessionInfo) AcceptUpdateSequence(seqNum, lastApplied uint16) error {
return nil
}

// ComputeSweepOutputs splits the total funds in a breaching commitment
// transaction between the victim and the tower, according to the sweep fee rate
// and reward rate. The fees are first subtracted from the overall total, before
// splitting the remaining balance amongst the victim and tower.
func (s *SessionInfo) ComputeSweepOutputs(totalAmt btcutil.Amount,
txVSize int64) (btcutil.Amount, btcutil.Amount, error) {

txFee := s.Policy.SweepFeeRate.FeeForWeight(txVSize)
if txFee > totalAmt {
return 0, 0, ErrFeeExceedsInputs
}

totalAmt -= txFee

// Apply the reward rate to the remaining total, specified in millionths
// of the available balance.
rewardRate := btcutil.Amount(s.Policy.RewardRate)
rewardAmt := (totalAmt*rewardRate + 999999) / 1000000
sweepAmt := totalAmt - rewardAmt

// TODO(conner): check dustiness

return sweepAmt, rewardAmt, nil
}

// Match is returned in response to a database query for a breach hints
// contained in a particular block. The match encapsulates all data required to
// properly decrypt a client's encrypted blob, and pursue action on behalf of
Expand Down
Loading

0 comments on commit bcc9e30

Please sign in to comment.