mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
lnwallet: revamp restoring channel state from disk after a restart
In this commit we complexly revamp the process of restoring all channel state back into memory after a restart. We’ll now properly do the following: restore the pending “dangling” commit of the remote party into the in-memory commitment chain, re-populate all active HTLC’s back into their respective update logs with the proper indexes/counters, and properly restore the current commitment of the remote party back in memory.
This commit is contained in:
parent
4b71e87b77
commit
9c015a5824
@ -488,80 +488,263 @@ func (c *commitment) populateHtlcIndexes() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// toChannelDelta converts the target commitment into a format suitable to be
|
// toDiskCommit converts the target commitment into a format suitable to be
|
||||||
// written to disk after an accepted state transition.
|
// written to disk after an accepted state transition.
|
||||||
func (c *commitment) toChannelDelta(ourCommit bool) (*channeldb.ChannelDelta, error) {
|
func (c *commitment) toDiskCommit(ourCommit bool) *channeldb.ChannelCommitment {
|
||||||
var ourMessageIndex uint64
|
numHtlcs := len(c.outgoingHTLCs) + len(c.incomingHTLCs)
|
||||||
var theirMessageIndex uint64
|
|
||||||
|
|
||||||
if ourCommit {
|
commit := &channeldb.ChannelCommitment{
|
||||||
ourMessageIndex = c.ourMessageIndex
|
CommitHeight: c.height,
|
||||||
theirMessageIndex = c.theirMessageIndex
|
LocalLogIndex: c.ourMessageIndex,
|
||||||
} else {
|
LocalHtlcIndex: c.ourHtlcIndex,
|
||||||
ourMessageIndex = c.theirMessageIndex
|
RemoteLogIndex: c.theirMessageIndex,
|
||||||
theirMessageIndex = c.ourMessageIndex
|
RemoteHtlcIndex: c.theirHtlcIndex,
|
||||||
|
LocalBalance: c.ourBalance,
|
||||||
|
RemoteBalance: c.theirBalance,
|
||||||
|
CommitFee: c.fee,
|
||||||
|
FeePerKw: c.feePerKw,
|
||||||
|
CommitTx: c.txn,
|
||||||
|
CommitSig: c.sig,
|
||||||
|
Htlcs: make([]channeldb.HTLC, 0, numHtlcs),
|
||||||
}
|
}
|
||||||
|
|
||||||
return &channeldb.ChannelDelta{
|
for _, htlc := range c.outgoingHTLCs {
|
||||||
OurMessageIndex: ourMessageIndex,
|
|
||||||
TheirMessageIndex: theirMessageIndex,
|
|
||||||
LocalBalance: c.ourBalance,
|
|
||||||
RemoteBalance: c.theirBalance,
|
|
||||||
UpdateNum: c.height,
|
|
||||||
CommitFee: c.fee,
|
|
||||||
FeePerKw: c.feePerKw,
|
|
||||||
Htlcs: c.htlcs(ourCommit),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// htlcs...
|
|
||||||
func (c *commitment) htlcs(ourCommit bool) []*channeldb.HTLC {
|
|
||||||
numHtlcs := len(c.outgoingHTLCs) + len(c.incomingHTLCs)
|
|
||||||
htlcs := make([]*channeldb.HTLC, 0, numHtlcs)
|
|
||||||
|
|
||||||
pdToHtlc := func(incoming bool, htlc PaymentDescriptor) *channeldb.HTLC {
|
|
||||||
outputIndex := htlc.localOutputIndex
|
outputIndex := htlc.localOutputIndex
|
||||||
if !ourCommit {
|
if !ourCommit {
|
||||||
outputIndex = htlc.remoteOutputIndex
|
outputIndex = htlc.remoteOutputIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
h := &channeldb.HTLC{
|
h := channeldb.HTLC{
|
||||||
Incoming: incoming,
|
RHash: htlc.RHash,
|
||||||
Amt: htlc.Amount,
|
Amt: htlc.Amount,
|
||||||
RHash: htlc.RHash,
|
RefundTimeout: htlc.Timeout,
|
||||||
RefundTimeout: htlc.Timeout,
|
OutputIndex: outputIndex,
|
||||||
OutputIndex: outputIndex,
|
HtlcIndex: htlc.HtlcIndex,
|
||||||
OnionBlob: htlc.OnionBlob,
|
LogIndex: htlc.LogIndex,
|
||||||
AddLocalInclusionHeight: htlc.addCommitHeightLocal,
|
Incoming: false,
|
||||||
AddRemoteInclusionHeight: htlc.addCommitHeightRemote,
|
|
||||||
DescriptorIndex: htlc.Index,
|
|
||||||
}
|
}
|
||||||
|
h.OnionBlob = make([]byte, len(htlc.OnionBlob))
|
||||||
//if incoming {
|
copy(h.OnionBlob[:], htlc.OnionBlob)
|
||||||
// fmt.Println("save, receiver:",
|
|
||||||
// "remote:", h.AddRemoteInclusionHeight,
|
|
||||||
// "local:", h.AddLocalInclusionHeight,
|
|
||||||
// "index:", h.DescriptorIndex)
|
|
||||||
//} else {
|
|
||||||
// fmt.Println("save, sender:",
|
|
||||||
// "remote:", h.AddRemoteInclusionHeight,
|
|
||||||
// "local:", h.AddLocalInclusionHeight,
|
|
||||||
// "index:", h.DescriptorIndex)
|
|
||||||
//}
|
|
||||||
|
|
||||||
if ourCommit && htlc.sig != nil {
|
if ourCommit && htlc.sig != nil {
|
||||||
h.Signature = htlc.sig.Serialize()
|
h.Signature = htlc.sig.Serialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
return h
|
commit.Htlcs = append(commit.Htlcs, h)
|
||||||
}
|
|
||||||
|
|
||||||
for _, htlc := range c.outgoingHTLCs {
|
|
||||||
htlcs = append(htlcs, pdToHtlc(false, htlc))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, htlc := range c.incomingHTLCs {
|
for _, htlc := range c.incomingHTLCs {
|
||||||
htlcs = append(htlcs, pdToHtlc(true, htlc))
|
outputIndex := htlc.localOutputIndex
|
||||||
|
if !ourCommit {
|
||||||
|
outputIndex = htlc.remoteOutputIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
h := channeldb.HTLC{
|
||||||
|
RHash: htlc.RHash,
|
||||||
|
Amt: htlc.Amount,
|
||||||
|
RefundTimeout: htlc.Timeout,
|
||||||
|
OutputIndex: outputIndex,
|
||||||
|
HtlcIndex: htlc.HtlcIndex,
|
||||||
|
LogIndex: htlc.LogIndex,
|
||||||
|
Incoming: true,
|
||||||
|
}
|
||||||
|
h.OnionBlob = make([]byte, len(htlc.OnionBlob))
|
||||||
|
copy(h.OnionBlob[:], htlc.OnionBlob)
|
||||||
|
|
||||||
|
if ourCommit && htlc.sig != nil {
|
||||||
|
h.Signature = htlc.sig.Serialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
commit.Htlcs = append(commit.Htlcs, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
return commit
|
||||||
|
}
|
||||||
|
|
||||||
|
// diskHtlcToPayDesc converts an HTLC previously written to disk within a
|
||||||
|
// commitment state to the form required to manipulate in memory within the
|
||||||
|
// commitment struct and updateLog. This function is used when we need to
|
||||||
|
// restore commitment state written do disk back into memory once we need to
|
||||||
|
// restart a channel session.
|
||||||
|
func (lc *LightningChannel) diskHtlcToPayDesc(feeRate btcutil.Amount,
|
||||||
|
commitHeight uint64, isPendingCommit bool, htlc *channeldb.HTLC,
|
||||||
|
localCommitKeys, remoteCommitKeys *commitmentKeyRing) (PaymentDescriptor, error) {
|
||||||
|
|
||||||
|
// The proper pkScripts for this PaymentDescriptor must be
|
||||||
|
// generated so we can easily locate them within the commitment
|
||||||
|
// transaction in the future.
|
||||||
|
var (
|
||||||
|
ourP2WSH, theirP2WSH []byte
|
||||||
|
ourWitnessScript, theirWitnessScript []byte
|
||||||
|
pd PaymentDescriptor
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// If the either outputs is dust from the local or remote node's
|
||||||
|
// perspective, then we don't need to generate the scripts as we only
|
||||||
|
// generate them in order to locate the outputs within the commitment
|
||||||
|
// transaction. As we'll mark dust with a special output index in the
|
||||||
|
// on-disk state snapshot.
|
||||||
|
isDustLocal := htlcIsDust(htlc.Incoming, true, feeRate,
|
||||||
|
htlc.Amt.ToSatoshis(), lc.channelState.LocalChanCfg.DustLimit)
|
||||||
|
if !isDustLocal && localCommitKeys != nil {
|
||||||
|
ourP2WSH, ourWitnessScript, err = genHtlcScript(
|
||||||
|
htlc.Incoming, true, htlc.RefundTimeout, htlc.RHash,
|
||||||
|
localCommitKeys)
|
||||||
|
if err != nil {
|
||||||
|
return pd, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isDustRemote := htlcIsDust(htlc.Incoming, false, feeRate,
|
||||||
|
htlc.Amt.ToSatoshis(), lc.channelState.RemoteChanCfg.DustLimit)
|
||||||
|
if !isDustRemote && remoteCommitKeys != nil {
|
||||||
|
theirP2WSH, theirWitnessScript, err = genHtlcScript(
|
||||||
|
htlc.Incoming, false, htlc.RefundTimeout, htlc.RHash,
|
||||||
|
remoteCommitKeys)
|
||||||
|
if err != nil {
|
||||||
|
return pd, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the scripts reconstructed (depending on if this is our commit
|
||||||
|
// vs theirs or a pending commit for the remote party), we can now
|
||||||
|
// re-create the original payment descriptor.
|
||||||
|
pd = PaymentDescriptor{
|
||||||
|
RHash: htlc.RHash,
|
||||||
|
Timeout: htlc.RefundTimeout,
|
||||||
|
Amount: htlc.Amt,
|
||||||
|
EntryType: Add,
|
||||||
|
HtlcIndex: htlc.HtlcIndex,
|
||||||
|
LogIndex: htlc.LogIndex,
|
||||||
|
OnionBlob: htlc.OnionBlob,
|
||||||
|
ourPkScript: ourP2WSH,
|
||||||
|
ourWitnessScript: ourWitnessScript,
|
||||||
|
theirPkScript: theirP2WSH,
|
||||||
|
theirWitnessScript: theirWitnessScript,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a pending commit, then the HTLC was only included in the
|
||||||
|
// commitment of the remote party, so we only set that commit height.
|
||||||
|
// Otherwise, we'll set the commit height for both chains as the HTLC
|
||||||
|
// was written to dis after it was fully locked in.
|
||||||
|
if isPendingCommit {
|
||||||
|
pd.addCommitHeightRemote = commitHeight
|
||||||
|
} else {
|
||||||
|
pd.addCommitHeightRemote = commitHeight
|
||||||
|
pd.addCommitHeightLocal = commitHeight
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return pd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractPayDescs will convert all HTLC's present within a disk commit state
|
||||||
|
// to a set of incoming and outgoing payment descriptors. Once reconstructed,
|
||||||
|
// these payment descriptors can be re-inserted into the in-memory updateLog
|
||||||
|
// for each side.
|
||||||
|
func (lc *LightningChannel) extractPayDescs(commitHeight uint64,
|
||||||
|
isPendingCommit bool, feeRate btcutil.Amount,
|
||||||
|
htlcs []channeldb.HTLC, localCommitKeys *commitmentKeyRing,
|
||||||
|
remoteCommitKeys *commitmentKeyRing) ([]PaymentDescriptor, []PaymentDescriptor, error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
incomingHtlcs []PaymentDescriptor
|
||||||
|
outgoingHtlcs []PaymentDescriptor
|
||||||
|
)
|
||||||
|
|
||||||
|
// For each included HTLC within this commitment state, we'll convert
|
||||||
|
// the disk format into our in memory PaymentDescriptor format,
|
||||||
|
// partitioning based on if we offered or received the HTLC.
|
||||||
|
for _, htlc := range htlcs {
|
||||||
|
// TODO(roasbeef): set isForwarded to false for all? need to
|
||||||
|
// persist state w.r.t to if forwarded or not, or can
|
||||||
|
// inadvertently trigger replays
|
||||||
|
|
||||||
|
payDesc, err := lc.diskHtlcToPayDesc(
|
||||||
|
feeRate, commitHeight, isPendingCommit, &htlc,
|
||||||
|
localCommitKeys, remoteCommitKeys,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return incomingHtlcs, outgoingHtlcs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if htlc.Incoming {
|
||||||
|
incomingHtlcs = append(incomingHtlcs, payDesc)
|
||||||
|
} else {
|
||||||
|
outgoingHtlcs = append(outgoingHtlcs, payDesc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return incomingHtlcs, outgoingHtlcs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// diskCommitToMemCommit converts tthe on-disk commitment format to our
|
||||||
|
// in-memory commitment format which is needed in order to properly resume
|
||||||
|
// channel operations after a restart.
|
||||||
|
func (lc *LightningChannel) diskCommitToMemCommit(isLocal, isPendingCommit bool,
|
||||||
|
diskCommit *channeldb.ChannelCommitment,
|
||||||
|
localCommitPoint, remoteCommitPoint *btcec.PublicKey) (*commitment, error) {
|
||||||
|
|
||||||
|
// First, we'll need to re-derive the commitment key ring for each
|
||||||
|
// party used within this particular state. If this is a pending commit
|
||||||
|
// (we extended but weren't able to complete the commitment dance
|
||||||
|
// before shutdown), then the localCommitPoint won't be set as we
|
||||||
|
// haven't yet received a responding commitment from the remote party.
|
||||||
|
var localCommitKeys, remoteCommitKeys *commitmentKeyRing
|
||||||
|
if localCommitPoint != nil {
|
||||||
|
localCommitKeys = deriveCommitmentKeys(localCommitPoint, true,
|
||||||
|
lc.localChanCfg, lc.remoteChanCfg)
|
||||||
|
}
|
||||||
|
if remoteCommitPoint != nil {
|
||||||
|
remoteCommitKeys = deriveCommitmentKeys(remoteCommitPoint, false,
|
||||||
|
lc.localChanCfg, lc.remoteChanCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the key rings re-created, we'll now convert all the on-disk
|
||||||
|
// HTLC"s into PaymentDescriptor's so we can re-insert them into our
|
||||||
|
// update log.
|
||||||
|
incomingHtlcs, outgoingHtlcs, err := lc.extractPayDescs(
|
||||||
|
diskCommit.CommitHeight, isPendingCommit,
|
||||||
|
diskCommit.FeePerKw, diskCommit.Htlcs,
|
||||||
|
localCommitKeys, remoteCommitKeys,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the necessary items generated, we'll now re-construct the
|
||||||
|
// commitment state as it was originally present in memory.
|
||||||
|
commit := &commitment{
|
||||||
|
height: diskCommit.CommitHeight,
|
||||||
|
isOurs: isLocal,
|
||||||
|
ourBalance: diskCommit.LocalBalance,
|
||||||
|
theirBalance: diskCommit.RemoteBalance,
|
||||||
|
ourMessageIndex: diskCommit.LocalLogIndex,
|
||||||
|
ourHtlcIndex: diskCommit.LocalHtlcIndex,
|
||||||
|
theirMessageIndex: diskCommit.RemoteLogIndex,
|
||||||
|
theirHtlcIndex: diskCommit.RemoteHtlcIndex,
|
||||||
|
txn: diskCommit.CommitTx,
|
||||||
|
sig: diskCommit.CommitSig,
|
||||||
|
fee: diskCommit.CommitFee,
|
||||||
|
feePerKw: diskCommit.FeePerKw,
|
||||||
|
incomingHTLCs: incomingHtlcs,
|
||||||
|
outgoingHTLCs: outgoingHtlcs,
|
||||||
|
}
|
||||||
|
if isLocal {
|
||||||
|
commit.dustLimit = lc.channelState.LocalChanCfg.DustLimit
|
||||||
|
} else {
|
||||||
|
commit.dustLimit = lc.channelState.RemoteChanCfg.DustLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, we'll re-populate the HTLC index for this state so we can
|
||||||
|
// properly locate each HTLC within the commitment transaction.
|
||||||
|
if err := commit.populateHtlcIndexes(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return commit, nil
|
||||||
|
}
|
||||||
|
|
||||||
// commitmentKeyRing holds all derived keys needed to construct commitment and
|
// commitmentKeyRing holds all derived keys needed to construct commitment and
|
||||||
// HTLC transactions. The keys are derived differently depending whether the
|
// HTLC transactions. The keys are derived differently depending whether the
|
||||||
// commitment transaction is ours or the remote peer's. Private keys associated
|
// commitment transaction is ours or the remote peer's. Private keys associated
|
||||||
@ -850,8 +1033,6 @@ func compactLogs(ourLog, theirLog *updateLog,
|
|||||||
// This window size is encoded within InitialRevocationWindow. Before the start
|
// This window size is encoded within InitialRevocationWindow. Before the start
|
||||||
// of a session, both side should send out revocation messages with nil
|
// of a session, both side should send out revocation messages with nil
|
||||||
// preimages in order to populate their revocation window for the remote party.
|
// preimages in order to populate their revocation window for the remote party.
|
||||||
// Ths method .ExtendRevocationWindow() is used to extend the revocation window
|
|
||||||
// by a single revocation.
|
|
||||||
//
|
//
|
||||||
// The state machine has for main methods:
|
// The state machine has for main methods:
|
||||||
// * .SignNextCommitment()
|
// * .SignNextCommitment()
|
||||||
@ -982,10 +1163,6 @@ type LightningChannel struct {
|
|||||||
// channel.
|
// channel.
|
||||||
RemoteFundingKey *btcec.PublicKey
|
RemoteFundingKey *btcec.PublicKey
|
||||||
|
|
||||||
// availableLocalBalance represent the amount of available money which
|
|
||||||
// might be processed by this channel at the specific point of time.
|
|
||||||
availableLocalBalance lnwire.MilliSatoshi
|
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
@ -1022,6 +1199,18 @@ func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localCommit := state.LocalCommitment
|
||||||
|
remoteCommit := state.RemoteCommitment
|
||||||
|
|
||||||
|
// First, initialize the update logs with their current counter values
|
||||||
|
// from the local and remote commitments.
|
||||||
|
localUpdateLog := newUpdateLog(
|
||||||
|
localCommit.LocalLogIndex, localCommit.LocalHtlcIndex,
|
||||||
|
)
|
||||||
|
remoteUpdateLog := newUpdateLog(
|
||||||
|
remoteCommit.RemoteLogIndex, remoteCommit.RemoteHtlcIndex,
|
||||||
|
)
|
||||||
|
|
||||||
lc := &LightningChannel{
|
lc := &LightningChannel{
|
||||||
// TODO(roasbeef): tune num sig workers?
|
// TODO(roasbeef): tune num sig workers?
|
||||||
sigPool: newSigPool(runtime.NumCPU(), signer),
|
sigPool: newSigPool(runtime.NumCPU(), signer),
|
||||||
@ -1029,14 +1218,14 @@ func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier,
|
|||||||
channelEvents: events,
|
channelEvents: events,
|
||||||
feeEstimator: fe,
|
feeEstimator: fe,
|
||||||
stateHintObfuscator: stateHint,
|
stateHintObfuscator: stateHint,
|
||||||
currentHeight: state.NumUpdates,
|
currentHeight: localCommit.CommitHeight,
|
||||||
remoteCommitChain: newCommitmentChain(state.NumUpdates),
|
remoteCommitChain: newCommitmentChain(remoteCommit.CommitHeight),
|
||||||
localCommitChain: newCommitmentChain(state.NumUpdates),
|
localCommitChain: newCommitmentChain(localCommit.CommitHeight),
|
||||||
channelState: state,
|
channelState: state,
|
||||||
localChanCfg: &state.LocalChanCfg,
|
localChanCfg: &state.LocalChanCfg,
|
||||||
remoteChanCfg: &state.RemoteChanCfg,
|
remoteChanCfg: &state.RemoteChanCfg,
|
||||||
localUpdateLog: newUpdateLog(state.OurMessageIndex, state.OurMessageIndex),
|
localUpdateLog: localUpdateLog,
|
||||||
remoteUpdateLog: newUpdateLog(state.TheirMessageIndex, state.TheirMessageIndex),
|
remoteUpdateLog: remoteUpdateLog,
|
||||||
rHashMap: make(map[PaymentHash][]*PaymentDescriptor),
|
rHashMap: make(map[PaymentHash][]*PaymentDescriptor),
|
||||||
Capacity: state.Capacity,
|
Capacity: state.Capacity,
|
||||||
FundingWitnessScript: multiSigScript,
|
FundingWitnessScript: multiSigScript,
|
||||||
@ -1049,85 +1238,15 @@ func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier,
|
|||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize both of our chains using current un-revoked commitment
|
// With the main channel struct reconstructed, we'll now restore the
|
||||||
// for each side.
|
// commitment state in memory and also the update logs themselves.
|
||||||
lc.localCommitChain.addCommitment(&commitment{
|
err = lc.restoreCommitState(
|
||||||
height: lc.currentHeight,
|
&localCommit, &remoteCommit, localUpdateLog, remoteUpdateLog,
|
||||||
ourBalance: state.LocalBalance,
|
|
||||||
ourMessageIndex: state.OurMessageIndex,
|
|
||||||
theirBalance: state.RemoteBalance,
|
|
||||||
theirMessageIndex: state.TheirMessageIndex,
|
|
||||||
fee: state.CommitFee,
|
|
||||||
feePerKw: state.FeePerKw,
|
|
||||||
})
|
|
||||||
|
|
||||||
fmt.Println("local commit restored:", "our:", state.OurMessageIndex,
|
|
||||||
"their:", state.TheirMessageIndex)
|
|
||||||
|
|
||||||
walletLog.Debugf("ChannelPoint(%v), starting local commitment: %v",
|
|
||||||
state.FundingOutpoint, newLogClosure(func() string {
|
|
||||||
return spew.Sdump(lc.localCommitChain.tail())
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
// To obtain the proper height for the remote node's commitment state,
|
|
||||||
// we'll need to fetch the tail end of their revocation log from the
|
|
||||||
// database.
|
|
||||||
logTail, err := state.RevocationLogTail()
|
|
||||||
if err != nil && err != channeldb.ErrNoActiveChannels &&
|
|
||||||
err != channeldb.ErrNoPastDeltas {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteCommitment := &commitment{}
|
|
||||||
if logTail == nil {
|
|
||||||
remoteCommitment.ourBalance = state.LocalBalance
|
|
||||||
remoteCommitment.ourMessageIndex = state.OurMessageIndex
|
|
||||||
remoteCommitment.theirBalance = state.RemoteBalance
|
|
||||||
remoteCommitment.theirMessageIndex = state.TheirMessageIndex
|
|
||||||
remoteCommitment.fee = state.CommitFee
|
|
||||||
remoteCommitment.feePerKw = state.FeePerKw
|
|
||||||
remoteCommitment.height = 0
|
|
||||||
} else {
|
|
||||||
remoteCommitment.ourBalance = state.LocalBalance
|
|
||||||
remoteCommitment.ourMessageIndex = logTail.OurMessageIndex
|
|
||||||
remoteCommitment.theirBalance = state.RemoteBalance
|
|
||||||
remoteCommitment.theirMessageIndex = logTail.TheirMessageIndex
|
|
||||||
remoteCommitment.fee = state.CommitFee
|
|
||||||
remoteCommitment.feePerKw = state.FeePerKw
|
|
||||||
remoteCommitment.height = logTail.UpdateNum + 1
|
|
||||||
}
|
|
||||||
lc.remoteCommitChain.addCommitment(remoteCommitment)
|
|
||||||
|
|
||||||
commitDiff, err := channeldb.FetchCommitDiff(lc.channelState.Db,
|
|
||||||
&lc.channelState.FundingOutpoint)
|
|
||||||
if err == nil {
|
|
||||||
lc.remoteCommitChain.addCommitment(&commitment{
|
|
||||||
height: commitDiff.PendingHeight,
|
|
||||||
ourBalance: commitDiff.PendingCommitment.LocalBalance,
|
|
||||||
theirBalance: commitDiff.PendingCommitment.RemoteBalance,
|
|
||||||
ourMessageIndex: commitDiff.PendingCommitment.OurMessageIndex,
|
|
||||||
theirMessageIndex: commitDiff.PendingCommitment.TheirMessageIndex,
|
|
||||||
fee: commitDiff.PendingCommitment.CommitFee,
|
|
||||||
feePerKw: commitDiff.PendingCommitment.FeePerKw,
|
|
||||||
})
|
|
||||||
|
|
||||||
fmt.Println("commit diff:", commitDiff.PendingCommitment.OurMessageIndex,
|
|
||||||
commitDiff.PendingCommitment.TheirMessageIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
walletLog.Debugf("ChannelPoint(%v), starting remote commitment: %v",
|
|
||||||
state.FundingOutpoint, newLogClosure(func() string {
|
|
||||||
return spew.Sdump(lc.remoteCommitChain.tail())
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
// If we're restarting from a channel with history, then restore the
|
|
||||||
// update in-memory update logs to that of the prior state.
|
|
||||||
if lc.currentHeight != 0 {
|
|
||||||
lc.restoreStateLogs()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the sign descriptor which we'll be using very frequently to
|
// Create the sign descriptor which we'll be using very frequently to
|
||||||
// request a signature for the 2-of-2 multi-sig from the signer in
|
// request a signature for the 2-of-2 multi-sig from the signer in
|
||||||
// order to complete channel state transitions.
|
// order to complete channel state transitions.
|
||||||
@ -1154,7 +1273,7 @@ func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier,
|
|||||||
if lc.channelEvents != nil {
|
if lc.channelEvents != nil {
|
||||||
// Register for a notification to be dispatched if the funding
|
// Register for a notification to be dispatched if the funding
|
||||||
// outpoint has been spent. This indicates that either us or
|
// outpoint has been spent. This indicates that either us or
|
||||||
// the remote party has broadcasted a commitment transaction
|
// the remote party has broadcast a commitment transaction
|
||||||
// on-chain.
|
// on-chain.
|
||||||
fundingOut := &lc.fundingTxIn.PreviousOutPoint
|
fundingOut := &lc.fundingTxIn.PreviousOutPoint
|
||||||
|
|
||||||
@ -1210,6 +1329,301 @@ func (lc *LightningChannel) Stop() {
|
|||||||
lc.wg.Wait()
|
lc.wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logUpdateToPayDesc converts a LogUpdate into a matching PaymentDescriptor
|
||||||
|
// entry that can be re-inserted into the update log. This method is used when
|
||||||
|
// we extended a state to the remote party, but the connection was obstructed
|
||||||
|
// before we could finish the commitment dance. In this case, we need to
|
||||||
|
// re-insert the original entries back into the update log so we can resume as
|
||||||
|
// if nothing happened.
|
||||||
|
func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
|
||||||
|
remoteUpdateLog *updateLog, commitHeight uint64,
|
||||||
|
feeRate btcutil.Amount, remoteCommitKeys *commitmentKeyRing,
|
||||||
|
remoteDustLimit btcutil.Amount) (*PaymentDescriptor, error) {
|
||||||
|
|
||||||
|
// Depending on the type of update message we'll map that to a distinct
|
||||||
|
// PaymentDescriptor instance.
|
||||||
|
var pd *PaymentDescriptor
|
||||||
|
|
||||||
|
switch wireMsg := logUpdate.UpdateMsg.(type) {
|
||||||
|
|
||||||
|
// For offered HTLC's, we'll map that to a PaymentDescriptor with the
|
||||||
|
// type Add, ensuring we restore the necessary fields. From the PoV of
|
||||||
|
// the commitment chain, this HTLC was included int he remote chain,
|
||||||
|
// but not the local chain.
|
||||||
|
case *lnwire.UpdateAddHTLC:
|
||||||
|
// First, we'll map all the relevant fields in the
|
||||||
|
// UpdateAddHTLC message to their corresponding fields in the
|
||||||
|
// PaymentDescriptor struct. We also set addCommitHeightRemote
|
||||||
|
// as we've included this HTLC in our local commitment chain
|
||||||
|
// for the remote party.
|
||||||
|
pd = &PaymentDescriptor{
|
||||||
|
RHash: wireMsg.PaymentHash,
|
||||||
|
Timeout: wireMsg.Expiry,
|
||||||
|
Amount: wireMsg.Amount,
|
||||||
|
EntryType: Add,
|
||||||
|
HtlcIndex: wireMsg.ID,
|
||||||
|
LogIndex: logUpdate.LogIndex,
|
||||||
|
addCommitHeightRemote: commitHeight,
|
||||||
|
}
|
||||||
|
copy(pd.OnionBlob[:], wireMsg.OnionBlob[:])
|
||||||
|
|
||||||
|
isDustRemote := htlcIsDust(false, false, feeRate,
|
||||||
|
wireMsg.Amount.ToSatoshis(), remoteDustLimit)
|
||||||
|
if !isDustRemote {
|
||||||
|
theirP2WSH, theirWitnessScript, err := genHtlcScript(
|
||||||
|
false, false, wireMsg.Expiry, wireMsg.PaymentHash,
|
||||||
|
remoteCommitKeys)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pd.theirPkScript = theirP2WSH
|
||||||
|
pd.theirWitnessScript = theirWitnessScript
|
||||||
|
}
|
||||||
|
|
||||||
|
// For HTLC's we we're offered we'll fetch the original offered HTLc
|
||||||
|
// from the remote party's update log so we can retrieve the same
|
||||||
|
// PaymentDescriptor that SettleHTLC would produce.
|
||||||
|
case *lnwire.UpdateFufillHTLC:
|
||||||
|
ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID)
|
||||||
|
|
||||||
|
pd = &PaymentDescriptor{
|
||||||
|
Amount: ogHTLC.Amount,
|
||||||
|
RPreimage: wireMsg.PaymentPreimage,
|
||||||
|
LogIndex: logUpdate.LogIndex,
|
||||||
|
ParentIndex: ogHTLC.HtlcIndex,
|
||||||
|
EntryType: Settle,
|
||||||
|
removeCommitHeightRemote: commitHeight,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we sent a failure for a prior incoming HTLC, then we'll consult
|
||||||
|
// the update log of the remote party so we can retrieve the
|
||||||
|
// information of the original HTLC we're failing. We also set the
|
||||||
|
// removal height for the remote commitment.
|
||||||
|
case *lnwire.UpdateFailHTLC:
|
||||||
|
ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID)
|
||||||
|
|
||||||
|
pd = &PaymentDescriptor{
|
||||||
|
Amount: ogHTLC.Amount,
|
||||||
|
RHash: ogHTLC.RHash,
|
||||||
|
ParentIndex: ogHTLC.HtlcIndex,
|
||||||
|
LogIndex: logUpdate.LogIndex,
|
||||||
|
EntryType: Fail,
|
||||||
|
FailReason: wireMsg.Reason[:],
|
||||||
|
removeCommitHeightRemote: commitHeight,
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTLC fails due to malformed onion blobs are treated the exact same
|
||||||
|
// way as regular HTLC fails.
|
||||||
|
case *lnwire.UpdateFailMalformedHTLC:
|
||||||
|
ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID)
|
||||||
|
// TODO(roasbeef): err if nil?
|
||||||
|
|
||||||
|
pd = &PaymentDescriptor{
|
||||||
|
Amount: ogHTLC.Amount,
|
||||||
|
RHash: ogHTLC.RHash,
|
||||||
|
ParentIndex: ogHTLC.HtlcIndex,
|
||||||
|
LogIndex: logUpdate.LogIndex,
|
||||||
|
EntryType: MalformedFail,
|
||||||
|
FailCode: wireMsg.FailureCode,
|
||||||
|
ShaOnionBlob: wireMsg.ShaOnionBlob,
|
||||||
|
removeCommitHeightRemote: commitHeight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// restoreCommitState will restore the local commitment chain and updateLog
|
||||||
|
// state to a consistent in-memory representation of the passed dis commitment.
|
||||||
|
// This method is to be used upon reconnection to our channel counter party.
|
||||||
|
// Once the connection has been established, we'll prepare our in memory state
|
||||||
|
// to re-sync states with the remote party, and also verify/extend new proposed
|
||||||
|
// commitment states.
|
||||||
|
func (lc *LightningChannel) restoreCommitState(
|
||||||
|
localCommitState, remoteCommitState *channeldb.ChannelCommitment,
|
||||||
|
localUpdateLog, remoteUpdateLog *updateLog) error {
|
||||||
|
|
||||||
|
// In order to reconstruct the pkScripts on each of the pending HTLC
|
||||||
|
// outputs (if any) we'll need to regenerate the current revocation for
|
||||||
|
// this current un-revoked state as well as retrieve the current
|
||||||
|
// revocation for the remote party.
|
||||||
|
ourRevPreImage, err := lc.channelState.RevocationProducer.AtIndex(
|
||||||
|
lc.currentHeight,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localCommitPoint := ComputeCommitmentPoint(ourRevPreImage[:])
|
||||||
|
remoteCommitPoint := lc.channelState.RemoteCurrentRevocation
|
||||||
|
|
||||||
|
// With the revocation state reconstructed, we can now convert the disk
|
||||||
|
// commitment into our in-memory commitment format, inserting it into
|
||||||
|
// the local commitment chain.
|
||||||
|
localCommit, err := lc.diskCommitToMemCommit(
|
||||||
|
true, false, localCommitState, localCommitPoint,
|
||||||
|
remoteCommitPoint,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lc.localCommitChain.addCommitment(localCommit)
|
||||||
|
|
||||||
|
walletLog.Debugf("ChannelPoint(%v), starting local commitment: %v",
|
||||||
|
lc.channelState.FundingOutpoint, newLogClosure(func() string {
|
||||||
|
return spew.Sdump(lc.localCommitChain.tail())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// We'll also do the same for the remote commitment chain.
|
||||||
|
remoteCommit, err := lc.diskCommitToMemCommit(
|
||||||
|
false, false, remoteCommitState, localCommitPoint,
|
||||||
|
remoteCommitPoint,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lc.remoteCommitChain.addCommitment(remoteCommit)
|
||||||
|
|
||||||
|
walletLog.Debugf("ChannelPoint(%v), starting remote commitment: %v",
|
||||||
|
lc.channelState.FundingOutpoint, newLogClosure(func() string {
|
||||||
|
return spew.Sdump(lc.remoteCommitChain.tail())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
pendingRemoteCommit *commitment
|
||||||
|
pendingRemoteCommitDiff *channeldb.CommitDiff
|
||||||
|
pendingRemoteKeyChain *commitmentKeyRing
|
||||||
|
)
|
||||||
|
|
||||||
|
// Next, we'll check to see if we have an un-acked commitment state we
|
||||||
|
// extended to the remote party but which was never ACK'd.
|
||||||
|
pendingRemoteCommitDiff, err = lc.channelState.RemoteCommitChainTip()
|
||||||
|
if err != nil && err != channeldb.ErrNoPendingCommit {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if pendingRemoteCommitDiff != nil {
|
||||||
|
// If we have a pending remote commitment, then we'll also
|
||||||
|
// reconstruct the original commitment for that state,
|
||||||
|
// inserting it into the remote party's commitment chain. We
|
||||||
|
// don't pass our commit point as we don't have the
|
||||||
|
// corresponding state for the local commitment chain.
|
||||||
|
pendingCommitPoint := lc.channelState.RemoteNextRevocation
|
||||||
|
pendingRemoteCommit, err = lc.diskCommitToMemCommit(
|
||||||
|
false, true, &pendingRemoteCommitDiff.Commitment,
|
||||||
|
nil, pendingCommitPoint,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lc.remoteCommitChain.addCommitment(pendingRemoteCommit)
|
||||||
|
|
||||||
|
// We'll also re-create the set of commitment keys needed to
|
||||||
|
// fully re-derive the state.
|
||||||
|
pendingRemoteKeyChain = deriveCommitmentKeys(
|
||||||
|
pendingCommitPoint, false, lc.localChanCfg,
|
||||||
|
lc.remoteChanCfg,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, with the commitment states restored, we'll now restore the
|
||||||
|
// state logs based on the current local+remote commit, and any pending
|
||||||
|
// remote commit that exists.
|
||||||
|
err = lc.restoreStateLogs(localCommit, remoteCommit, pendingRemoteCommit,
|
||||||
|
pendingRemoteCommitDiff, pendingRemoteKeyChain,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// restoreStateLogs runs through the current locked-in HTLCs from the point of
|
||||||
|
// view of the channel and insert corresponding log entries (both local and
|
||||||
|
// remote) for each HTLC read from disk. This method is required to sync the
|
||||||
|
// in-memory state of the state machine with that read from persistent storage.
|
||||||
|
func (lc *LightningChannel) restoreStateLogs(
|
||||||
|
localCommitment, remoteCommitment, pendingRemoteCommit *commitment,
|
||||||
|
pendingRemoteCommitDiff *channeldb.CommitDiff,
|
||||||
|
pendingRemoteKeys *commitmentKeyRing) error {
|
||||||
|
|
||||||
|
// For each HTLC within the local commitment, we add it to the relevant
|
||||||
|
// update logc based on if it's incoming vs outgoing. For any incoming
|
||||||
|
// HTLC's, we also re-add it to the rHashMap so we can quickly look it
|
||||||
|
// up.
|
||||||
|
for i := range localCommitment.incomingHTLCs {
|
||||||
|
htlc := localCommitment.incomingHTLCs[i]
|
||||||
|
lc.remoteUpdateLog.restoreHtlc(&htlc)
|
||||||
|
|
||||||
|
lc.rHashMap[htlc.RHash] = append(lc.rHashMap[htlc.RHash], &htlc)
|
||||||
|
}
|
||||||
|
for i := range localCommitment.outgoingHTLCs {
|
||||||
|
htlc := localCommitment.outgoingHTLCs[i]
|
||||||
|
lc.localUpdateLog.restoreHtlc(&htlc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll also do the same for the HTLC"s within the remote commitment
|
||||||
|
// party. We also insert these HTLC's as it's possible our state has
|
||||||
|
// diverged slightly in the case of a congruent update from both sides.
|
||||||
|
// The restoreHtlc method will de-dup the HTLC's to handle this case.
|
||||||
|
for i := range remoteCommitment.incomingHTLCs {
|
||||||
|
htlc := remoteCommitment.incomingHTLCs[i]
|
||||||
|
lc.remoteUpdateLog.restoreHtlc(&htlc)
|
||||||
|
}
|
||||||
|
for i := range remoteCommitment.outgoingHTLCs {
|
||||||
|
htlc := remoteCommitment.outgoingHTLCs[i]
|
||||||
|
lc.localUpdateLog.restoreHtlc(&htlc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't have a dangling (un-acked) commit for the remote party,
|
||||||
|
// then we can exit here.
|
||||||
|
if pendingRemoteCommit == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we do have a dangling commitment for the remote party, then we'll
|
||||||
|
// also restore into the log any incoming HTLC's offered by them. Any
|
||||||
|
// outgoing HTLC's that were initially committed in this new state will
|
||||||
|
// be restored below.
|
||||||
|
for i := range pendingRemoteCommit.incomingHTLCs {
|
||||||
|
htlc := pendingRemoteCommit.incomingHTLCs[i]
|
||||||
|
lc.remoteUpdateLog.restoreHtlc(&htlc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll also update the log counters to match the latest known
|
||||||
|
// counters in this dangling commitment. Otherwise, our updateLog would
|
||||||
|
// have dated counters as it was initially created using their lowest
|
||||||
|
// unrevoked commitment.
|
||||||
|
lc.remoteUpdateLog.logIndex = pendingRemoteCommit.theirMessageIndex
|
||||||
|
lc.remoteUpdateLog.htlcCounter = pendingRemoteCommit.theirHtlcIndex
|
||||||
|
|
||||||
|
pendingCommit := pendingRemoteCommitDiff.Commitment
|
||||||
|
pendingHeight := pendingCommit.CommitHeight
|
||||||
|
|
||||||
|
// If we did have a dangling commit, then we'll examine which updates
|
||||||
|
// we included in that state and re-insert them into our update log.
|
||||||
|
for _, logUpdate := range pendingRemoteCommitDiff.LogUpdates {
|
||||||
|
payDesc, err := lc.logUpdateToPayDesc(
|
||||||
|
&logUpdate, lc.remoteUpdateLog, pendingHeight,
|
||||||
|
pendingCommit.FeePerKw, pendingRemoteKeys,
|
||||||
|
lc.channelState.RemoteChanCfg.DustLimit,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if payDesc.EntryType == Add {
|
||||||
|
lc.localUpdateLog.appendHtlc(payDesc)
|
||||||
|
} else {
|
||||||
|
lc.localUpdateLog.appendUpdate(payDesc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// HtlcRetribution contains all the items necessary to seep a revoked HTLC
|
// HtlcRetribution contains all the items necessary to seep a revoked HTLC
|
||||||
// transaction from a revoked commitment transaction broadcast by the remot
|
// transaction from a revoked commitment transaction broadcast by the remot
|
||||||
// party.
|
// party.
|
||||||
@ -1725,110 +2139,6 @@ func htlcIsDust(incoming, ourCommit bool,
|
|||||||
return (htlcAmt - htlcFee) < dustLimit
|
return (htlcAmt - htlcFee) < dustLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
// restoreStateLogs runs through the current locked-in HTLCs from the point of
|
|
||||||
// view of the channel and insert corresponding log entries (both local and
|
|
||||||
// remote) for each HTLC read from disk. This method is required to sync the
|
|
||||||
// in-memory state of the state machine with that read from persistent storage.
|
|
||||||
func (lc *LightningChannel) restoreStateLogs() error {
|
|
||||||
// Obtain the local and remote channel configurations. These house all
|
|
||||||
// the relevant public keys and points we'll need in order to restore
|
|
||||||
// the state log.
|
|
||||||
localChanCfg := lc.localChanCfg
|
|
||||||
remoteChanCfg := lc.remoteChanCfg
|
|
||||||
|
|
||||||
// In order to reconstruct the pkScripts on each of the pending HTLC
|
|
||||||
// outputs (if any) we'll need to regenerate the current revocation for
|
|
||||||
// this current un-revoked state.
|
|
||||||
ourRevPreImage, err := lc.channelState.RevocationProducer.AtIndex(lc.currentHeight)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the commitment secret recovered, we'll generate the revocation
|
|
||||||
// used on the *local* commitment transaction. This is computed using
|
|
||||||
// the point derived from the commitment secret at the remote party's
|
|
||||||
// revocation based.
|
|
||||||
localCommitPoint := ComputeCommitmentPoint(ourRevPreImage[:])
|
|
||||||
localCommitKeys := deriveCommitmentKeys(localCommitPoint, true,
|
|
||||||
localChanCfg, remoteChanCfg)
|
|
||||||
|
|
||||||
remoteCommitPoint := lc.channelState.RemoteCurrentRevocation
|
|
||||||
remoteCommitKeys := deriveCommitmentKeys(remoteCommitPoint, false,
|
|
||||||
localChanCfg, remoteChanCfg)
|
|
||||||
|
|
||||||
// Grab the current fee rate as we'll need this to determine if the
|
|
||||||
// prior HTLC's were considered dust or not at this particular
|
|
||||||
// commitment state.
|
|
||||||
feeRate := lc.channelState.FeePerKw
|
|
||||||
|
|
||||||
// TODO(roasbeef): partition entries added based on our current review
|
|
||||||
// an our view of them from the log?
|
|
||||||
for _, htlc := range lc.channelState.Htlcs {
|
|
||||||
// TODO(roasbeef): set isForwarded to false for all? need to
|
|
||||||
// persist state w.r.t to if forwarded or not, or can
|
|
||||||
// inadvertently trigger replays
|
|
||||||
|
|
||||||
// The proper pkScripts for this PaymentDescriptor must be
|
|
||||||
// generated so we can easily locate them within the commitment
|
|
||||||
// transaction in the future.
|
|
||||||
var ourP2WSH, theirP2WSH, ourWitnessScript, theirWitnessScript []byte
|
|
||||||
|
|
||||||
// If the either outputs is dust from the local or remote
|
|
||||||
// node's perspective, then we don't need to generate the
|
|
||||||
// scripts as we only generate them in order to locate the
|
|
||||||
// outputs within the commitment transaction. As we'll mark
|
|
||||||
// dust with a special output index in the on-disk state
|
|
||||||
// snapshot.
|
|
||||||
isDustLocal := htlcIsDust(htlc.Incoming, true, feeRate,
|
|
||||||
htlc.Amt.ToSatoshis(), localChanCfg.DustLimit)
|
|
||||||
isDustRemote := htlcIsDust(htlc.Incoming, false, feeRate,
|
|
||||||
htlc.Amt.ToSatoshis(), remoteChanCfg.DustLimit)
|
|
||||||
if !isDustLocal {
|
|
||||||
ourP2WSH, ourWitnessScript, err = lc.genHtlcScript(
|
|
||||||
htlc.Incoming, true, htlc.RefundTimeout, htlc.RHash,
|
|
||||||
localCommitKeys)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isDustRemote {
|
|
||||||
theirP2WSH, theirWitnessScript, err = lc.genHtlcScript(
|
|
||||||
htlc.Incoming, false, htlc.RefundTimeout, htlc.RHash,
|
|
||||||
remoteCommitKeys)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pd := &PaymentDescriptor{
|
|
||||||
RHash: htlc.RHash,
|
|
||||||
Timeout: htlc.RefundTimeout,
|
|
||||||
Amount: htlc.Amt,
|
|
||||||
EntryType: Add,
|
|
||||||
Index: htlc.DescriptorIndex,
|
|
||||||
addCommitHeightRemote: htlc.AddRemoteInclusionHeight,
|
|
||||||
addCommitHeightLocal: htlc.AddLocalInclusionHeight,
|
|
||||||
OnionBlob: htlc.OnionBlob,
|
|
||||||
ourPkScript: ourP2WSH,
|
|
||||||
ourWitnessScript: ourWitnessScript,
|
|
||||||
theirPkScript: theirP2WSH,
|
|
||||||
theirWitnessScript: theirWitnessScript,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !htlc.Incoming {
|
|
||||||
pd.HtlcIndex = ourCounter
|
|
||||||
lc.localUpdateLog.appendHtlc(pd)
|
|
||||||
} else {
|
|
||||||
pd.HtlcIndex = theirCounter
|
|
||||||
lc.remoteUpdateLog.appendHtlc(pd)
|
|
||||||
|
|
||||||
lc.rHashMap[pd.RHash] = append(lc.rHashMap[pd.RHash], pd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// htlcView represents the "active" HTLCs at a particular point within the
|
// htlcView represents the "active" HTLCs at a particular point within the
|
||||||
// history of the HTLC update log.
|
// history of the HTLC update log.
|
||||||
type htlcView struct {
|
type htlcView struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user