diff --git a/lnwallet/channel.go b/lnwallet/channel.go index cc358238..1296e0a6 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -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) }