lnwallet: introduce populateHtlcIndexes for tracking HTLC indexes in commitments

This commit adds a new method to the commitment struct:
populateHtlcIndexes.  populateHtlcIndexes modifies the set of HTLC's
locked-into the target view to have full indexing information
populated. This information is required as we need to keep track of the
indexes of each HTLC in order to properly write the current state to
disk, and also to locate the PaymentDescriptor corresponding to HTLC
outputs in the commitment transaction.

We also modify toChannelDelta to take not of these new changes, and
access the appropriate index directly.
This commit is contained in:
Olaoluwa Osuntokun 2017-07-30 12:46:55 -07:00
parent 549688d793
commit e56258917e
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2

View File

@ -89,8 +89,6 @@ const (
// payments requested by the wallet/daemon.
type PaymentHash [32]byte
const maxUint16 uint16 = ^uint16(0)
// UpdateType is the exact type of an entry within the shared HTLC log.
type updateType uint8
@ -118,6 +116,7 @@ const (
// settles, or removes an HTLC. PaymentDescriptors encapsulate all necessary
// metadata w.r.t to an HTLC, and additional data pairing a settle message to
// the original added HTLC.
//
// TODO(roasbeef): LogEntry interface??
// * need to separate attrs for cancel/add/settle
type PaymentDescriptor struct {
@ -145,6 +144,27 @@ type PaymentDescriptor struct {
// settles or times out.
ParentIndex uint64
// localOutputIndex is the output index of this HTLc output in the
// commitment transaction of the local node.
//
// NOTE: If the output is dust from the PoV of the local comimtnet
// chain, then this value will be -1.
localOutputIndex int32
// remoteOutputIndex is the output index of this HTLc output in the
// commitment transaction of the remote node.
//
// NOTE: If the output is dust from the PoV of the remote commitment
// chain, then this value will be -1.
remoteOutputIndex int32
// sig is the signature for the second-level HTLC transaction that
// spends the version of this HTLC on the commitment transaction of the
// local node. This signature is generated by the remote node and
// stored by the local node in the case that local node needs to
// broadcast their commitment transaction.
sig *btcec.Signature
// addCommitHeight[Remote|Local] encodes the height of the commitment
// which included this HTLC on either the remote or local commitment
// chain. This value is used to determine when an HTLC is fully
@ -233,21 +253,174 @@ type commitment struct {
// transaction's fee.
feePerKw btcutil.Amount
// htlcs is the set of HTLCs which remain unsettled within this
// commitment.
// outgoingHTLCs is a slice of all the outgoing HTLC's (from our PoV)
// on this commitment transaction.
outgoingHTLCs []PaymentDescriptor
incomingHTLCs []PaymentDescriptor
}
// incomingHTLCs is a slice of all the incoming HTLC's (from our PoV)
// on this commitment transaction.
incomingHTLCs []PaymentDescriptor
// [outgoing|incoming]HTLCIndex is an index that maps an output index
// on the commitment transaction to the payment descriptor that
// represents the HTLC output. Note that these fields are only
// populated if this commitment state belongs to the local node. These
// maps are used when validating any HTLC signatures which are part of
// the local commitment state. We use this map in order to locate the
// details needed to validate an HTLC signature while iterating of the
// outputs int he local commitment view.
outgoignHTLCIndex map[int32]*PaymentDescriptor
incomingHTLCIndex map[int32]*PaymentDescriptor
}
// locateOutputIndex is a small helper function to locate the output index of a
// particular HTLC within the current commitment transaction. The duplicate map
// massed in is to be retained for each output within the commitment
// transition. This ensures that we don't assign multiple HTLC's to the same
// index within the commitment transaction.
func locateOutputIndex(p *PaymentDescriptor, tx *wire.MsgTx, ourCommit bool,
dups map[PaymentHash][]int32) (int32, error) {
// Checks to see if element (e) exists in slice (s).
contains := func(s []int32, e int32) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
// If this their commitment transaction, we'll be trying to locate
// their pkScripts, otherwise we'll be looking for ours. This is
// required as the commitment states are asymmetric in order to ascribe
// blame in the case of a contract breach.
pkScript := p.theirPkScript
if ourCommit {
pkScript = p.ourPkScript
}
for i, txOut := range tx.TxOut {
if bytes.Equal(txOut.PkScript, pkScript) &&
txOut.Value == int64(p.Amount) {
// If this payment hash and index has already been
// found, then we'll continue in order to avoid any
// duplicate indexes.
if contains(dups[p.RHash], int32(i)) {
continue
}
idx := int32(i)
dups[p.RHash] = append(dups[p.RHash], idx)
return idx, nil
}
}
return 0, fmt.Errorf("unable to find htlc: script=%x, value=%v",
pkScript, p.Amount)
}
// populateHtlcIndexes modifies the set of HTLC's locked-into the target view
// to have full indexing information populated. This information is required as
// we need to keep track of the indexes of each HTLC in order to properly write
// the current state to disk, and also to locate the PaymentDescriptor
// corresponding to HTLC outputs in the commitment transaction.
func (c *commitment) populateHtlcIndexes(ourCommitTx bool,
dustLimit btcutil.Amount) error {
// First, we'll set up some state to allow us to locate the output
// index of the all the HTLC's within the commitment transaction. We
// must keep this index so we can validate the HTLC signatures sent to
// us.
dups := make(map[PaymentHash][]int32)
c.outgoignHTLCIndex = make(map[int32]*PaymentDescriptor)
c.incomingHTLCIndex = make(map[int32]*PaymentDescriptor)
// populateIndex is a helper function that populates the necessary
// indexes within the commitment view for a particular HTLC.
populateIndex := func(htlc *PaymentDescriptor, incoming bool) error {
isDust := htlcIsDust(incoming, ourCommitTx, c.feePerKw,
htlc.Amount, dustLimit)
var err error
switch {
// If this is our commitment transaction, and this is a dust
// output then we mark it as such using a -1 index.
case ourCommitTx && isDust:
htlc.localOutputIndex = -1
// If this is the commitment transaction of the remote party,
// and this is a dust output then we mark it as such using a -1
// index.
case !ourCommitTx && isDust:
htlc.remoteOutputIndex = -1
// If this is our commitment transaction, then we'll need to
// locate the output and the index so we can verify an HTLC
// signatures.
case ourCommitTx:
htlc.localOutputIndex, err = locateOutputIndex(htlc, c.txn,
ourCommitTx, dups)
if err != nil {
return err
}
// As this is our commitment transactions, we need to
// keep track of the locations of each output on the
// transaction so we can verify any HTLC signatures
// sent to us after we construct the HTLC view.
if incoming {
c.incomingHTLCIndex[htlc.localOutputIndex] = htlc
} else {
c.outgoignHTLCIndex[htlc.localOutputIndex] = htlc
}
// Otherwise, this is there remote party's commitment
// transaction and we only need to populate the remote output
// index within the HTLC index.
case !ourCommitTx:
htlc.remoteOutputIndex, err = locateOutputIndex(htlc, c.txn,
ourCommitTx, dups)
if err != nil {
return err
}
default:
return fmt.Errorf("invalid commitment configuration")
}
return nil
}
// Finally, we'll need to locate the index within the commitment
// transaction of all the HTLC outputs. This index will be required
// later when we write the commitment state to disk, and also when
// generating signatures for each of the HTLC transactions.
for i := 0; i < len(c.outgoingHTLCs); i++ {
htlc := &c.outgoingHTLCs[i]
if err := populateIndex(htlc, false); err != nil {
return nil
}
}
for i := 0; i < len(c.incomingHTLCs); i++ {
htlc := &c.incomingHTLCs[i]
if err := populateIndex(htlc, true); err != nil {
return nil
}
}
return nil
}
// toChannelDelta converts the target commitment into a format suitable to be
// written to disk after an accepted state transition.
// TODO(roasbeef): properly fill in refund timeouts
func (c *commitment) toChannelDelta(ourCommit bool) (*channeldb.ChannelDelta, error) {
numHtlcs := len(c.outgoingHTLCs) + len(c.incomingHTLCs)
// Save output indexes for RHash values found, so we don't return the
// same output index more than once.
dups := make(map[PaymentHash][]uint16)
delta := &channeldb.ChannelDelta{
LocalBalance: c.ourBalance,
RemoteBalance: c.theirBalance,
@ -257,90 +430,45 @@ func (c *commitment) toChannelDelta(ourCommit bool) (*channeldb.ChannelDelta, er
Htlcs: make([]*channeldb.HTLC, 0, numHtlcs),
}
// Check to see if element (e) exists in slice (s)
contains := func(s []uint16, e uint16) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
// As we also store the output index of the HTLC for continence
// purposes, we create a small helper function to locate the output
// index of a particular HTLC within the current commitment
// transaction.
locateOutputIndex := func(p *PaymentDescriptor) (uint16, error) {
var (
idx uint16
found bool
)
pkScript := p.theirPkScript
if ourCommit {
pkScript = p.ourPkScript
}
for i, txOut := range c.txn.TxOut {
if bytes.Equal(txOut.PkScript, pkScript) &&
txOut.Value == int64(p.Amount) {
if contains(dups[p.RHash], uint16(i)) {
continue
}
found = true
idx = uint16(i)
dups[p.RHash] = append(dups[p.RHash], idx)
break
}
}
if !found {
return 0, fmt.Errorf("unable to find htlc: script=%x, value=%v",
pkScript, p.Amount)
}
return idx, nil
}
var (
index uint16
err error
)
for _, htlc := range c.outgoingHTLCs {
if (ourCommit && htlc.isDustLocal) ||
(!ourCommit && htlc.isDustRemote) {
index = maxUint16
} else if index, err = locateOutputIndex(&htlc); err != nil {
return nil, fmt.Errorf("unable to find outgoing htlc: %v", err)
outputIndex := htlc.localOutputIndex
if !ourCommit {
outputIndex = htlc.remoteOutputIndex
}
h := &channeldb.HTLC{
Incoming: false,
Amt: htlc.Amount,
RHash: htlc.RHash,
RefundTimeout: htlc.Timeout,
RevocationDelay: 0,
OutputIndex: index,
Incoming: false,
Amt: htlc.Amount,
RHash: htlc.RHash,
RefundTimeout: htlc.Timeout,
OutputIndex: outputIndex,
}
if ourCommit && htlc.sig != nil {
h.Signature = htlc.sig.Serialize()
}
delta.Htlcs = append(delta.Htlcs, h)
}
for _, htlc := range c.incomingHTLCs {
if (ourCommit && htlc.isDustLocal) ||
(!ourCommit && htlc.isDustRemote) {
index = maxUint16
} else if index, err = locateOutputIndex(&htlc); err != nil {
return nil, fmt.Errorf("unable to find incoming htlc: %v", err)
outputIndex := htlc.localOutputIndex
if !ourCommit {
outputIndex = htlc.remoteOutputIndex
}
h := &channeldb.HTLC{
Incoming: true,
Amt: htlc.Amount,
RHash: htlc.RHash,
RefundTimeout: htlc.Timeout,
RevocationDelay: 0,
OutputIndex: index,
Incoming: true,
Amt: htlc.Amount,
RHash: htlc.RHash,
RefundTimeout: htlc.Timeout,
OutputIndex: outputIndex,
}
if ourCommit && htlc.sig != nil {
h.Signature = htlc.sig.Serialize()
}
delta.Htlcs = append(delta.Htlcs, h)
}