Merge pull request #6347 from yyforyongyu/5388-rm-revc-log
lnwallet+channeldb: store minimal info in revocation log bucket
This commit is contained in:
33 files changed
+3635
-562
No files matched your search
+324
-142
@@ -96,6 +96,17 @@ var (
|
||||
// both parties can retrieve their funds.
|
||||
ErrCommitSyncRemoteDataLoss = fmt.Errorf("possible remote commitment " +
|
||||
"state data loss")
|
||||
|
||||
// ErrNoRevocationLogFound is returned when both the returned logs are
|
||||
// nil from querying the revocation log bucket. In theory this should
|
||||
// never happen as the query will return `ErrLogEntryNotFound`, yet
|
||||
// we'd still perform a sanity check to make sure at least one of the
|
||||
// logs is non-nil.
|
||||
ErrNoRevocationLogFound = errors.New("no revocation log found")
|
||||
|
||||
// ErrOutputIndexOutOfRange is returned when an output index is greater
|
||||
// than or equal to the length of a given transaction's outputs.
|
||||
ErrOutputIndexOutOfRange = errors.New("output index is out of range")
|
||||
)
|
||||
|
||||
// ErrCommitSyncLocalDataLoss is returned in the case that we receive a valid
|
||||
@@ -1036,26 +1047,29 @@ type updateLog struct {
|
||||
logIndex uint64
|
||||
|
||||
// htlcCounter is a monotonically increasing integer that tracks the
|
||||
// total number of offered HTLC's by the owner of this update log. We
|
||||
// use a distinct index for this purpose, as update's that remove
|
||||
// entries from the log will be indexed using this counter.
|
||||
// total number of offered HTLC's by the owner of this update log,
|
||||
// hence the `Add` update type. We use a distinct index for this
|
||||
// purpose, as update's that remove entries from the log will be
|
||||
// indexed using this counter.
|
||||
htlcCounter uint64
|
||||
|
||||
// List is the updatelog itself, we embed this value so updateLog has
|
||||
// access to all the method of a list.List.
|
||||
*list.List
|
||||
|
||||
// updateIndex is an index that maps a particular entries index to the
|
||||
// list element within the list.List above.
|
||||
// updateIndex maps a `logIndex` to a particular update entry. It
|
||||
// deals with the four update types:
|
||||
// `Fail|MalformedFail|Settle|FeeUpdate`
|
||||
updateIndex map[uint64]*list.Element
|
||||
|
||||
// offerIndex is an index that maps the counter for offered HTLC's to
|
||||
// their list element within the main list.List.
|
||||
// htlcIndex maps a `htlcCounter` to an offered HTLC entry, hence the
|
||||
// `Add` update.
|
||||
htlcIndex map[uint64]*list.Element
|
||||
|
||||
// modifiedHtlcs is a set that keeps track of all the current modified
|
||||
// htlcs. A modified HTLC is one that's present in the log, and has as
|
||||
// a pending fail or settle that's attempting to consume it.
|
||||
// htlcs, hence update types `Fail|MalformedFail|Settle`. A modified
|
||||
// HTLC is one that's present in the log, and has as a pending fail or
|
||||
// settle that's attempting to consume it.
|
||||
modifiedHtlcs map[uint64]struct{}
|
||||
}
|
||||
|
||||
@@ -2210,10 +2224,10 @@ type HtlcRetribution struct {
|
||||
// transaction. The BreachRetribution is then sent over the ContractBreach
|
||||
// channel in order to allow the subscriber of the channel to dispatch justice.
|
||||
type BreachRetribution struct {
|
||||
// BreachTransaction is the transaction which breached the channel
|
||||
// BreachTxHash is the transaction hash which breached the channel
|
||||
// contract by spending from the funding multi-sig with a revoked
|
||||
// commitment transaction.
|
||||
BreachTransaction *wire.MsgTx
|
||||
BreachTxHash chainhash.Hash
|
||||
|
||||
// BreachHeight records the block height confirming the breach
|
||||
// transaction, used as a height hint when registering for
|
||||
@@ -2228,13 +2242,9 @@ type BreachRetribution struct {
|
||||
// RevokedStateNum is the revoked state number which was broadcast.
|
||||
RevokedStateNum uint64
|
||||
|
||||
// PendingHTLCs is a slice of the HTLCs which were pending at this
|
||||
// point within the channel's history transcript.
|
||||
PendingHTLCs []channeldb.HTLC
|
||||
|
||||
// LocalOutputSignDesc is a SignDescriptor which is capable of
|
||||
// generating the signature necessary to sweep the output within the
|
||||
// BreachTransaction that pays directly us.
|
||||
// breach transaction that pays directly us.
|
||||
//
|
||||
// NOTE: A nil value indicates that the local output is considered dust
|
||||
// according to the remote party's dust limit.
|
||||
@@ -2279,16 +2289,22 @@ type BreachRetribution struct {
|
||||
// passed channel, at a particular revoked state number, and one which targets
|
||||
// the passed commitment transaction.
|
||||
func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
breachHeight uint32) (*BreachRetribution, error) {
|
||||
breachHeight uint32, spendTx *wire.MsgTx) (*BreachRetribution, error) {
|
||||
|
||||
// Query the on-disk revocation log for the snapshot which was recorded
|
||||
// at this particular state num.
|
||||
revokedSnapshot, err := chanState.FindPreviousState(stateNum)
|
||||
// at this particular state num. Based on whether a legacy revocation
|
||||
// log is returned or not, we will process them differently.
|
||||
revokedLog, revokedLogLegacy, err := chanState.FindPreviousState(
|
||||
stateNum,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commitHash := revokedSnapshot.CommitTx.TxHash()
|
||||
// Sanity check that at least one of the logs is returned.
|
||||
if revokedLog == nil && revokedLogLegacy == nil {
|
||||
return nil, ErrNoRevocationLogFound
|
||||
}
|
||||
|
||||
// With the state number broadcast known, we can now derive/restore the
|
||||
// proper revocation preimage necessary to sweep the remote party's
|
||||
@@ -2311,22 +2327,14 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
// Next, reconstruct the scripts as they were present at this state
|
||||
// number so we can have the proper witness script to sign and include
|
||||
// within the final witness.
|
||||
theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
|
||||
isRemoteInitiator := !chanState.IsInitiator
|
||||
var leaseExpiry uint32
|
||||
if chanState.ChanType.HasLeaseExpiration() {
|
||||
leaseExpiry = chanState.ThawHeight
|
||||
}
|
||||
theirScript, err := CommitScriptToSelf(
|
||||
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
|
||||
keyRing.RevocationKey, theirDelay, leaseExpiry,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Since it is the remote breach we are reconstructing, the output going
|
||||
// to us will be a to-remote script with our local params.
|
||||
// Since it is the remote breach we are reconstructing, the output
|
||||
// going to us will be a to-remote script with our local params.
|
||||
isRemoteInitiator := !chanState.IsInitiator
|
||||
ourScript, ourDelay, err := CommitScriptToRemote(
|
||||
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
|
||||
leaseExpiry,
|
||||
@@ -2335,15 +2343,248 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// In order to fully populate the breach retribution struct, we'll need
|
||||
// to find the exact index of the commitment outputs.
|
||||
theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
|
||||
theirScript, err := CommitScriptToSelf(
|
||||
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
|
||||
keyRing.RevocationKey, theirDelay, leaseExpiry,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Define an empty breach retribution that will be overwritten based on
|
||||
// different version of the revocation log found.
|
||||
var br *BreachRetribution
|
||||
|
||||
// Define our and their amounts, that will be overwritten below.
|
||||
var ourAmt, theirAmt int64
|
||||
|
||||
// If the returned *RevocationLog is non-nil, use it to derive the info
|
||||
// we need.
|
||||
if revokedLog != nil {
|
||||
br, ourAmt, theirAmt, err = createBreachRetribution(
|
||||
revokedLog, spendTx, chanState, keyRing,
|
||||
commitmentSecret, leaseExpiry,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// The returned revocation log is in legacy format, which is a
|
||||
// *ChannelCommitment.
|
||||
//
|
||||
// NOTE: this branch is kept for compatibility such that for
|
||||
// old nodes which refuse to migrate the legacy revocation log
|
||||
// data can still function. This branch can be deleted once we
|
||||
// are confident that no legacy format is in use.
|
||||
br, ourAmt, theirAmt, err = createBreachRetributionLegacy(
|
||||
revokedLogLegacy, chanState, keyRing, commitmentSecret,
|
||||
ourScript, theirScript, leaseExpiry,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// If our balance exceeds the remote party's dust limit, instantiate
|
||||
// the sign descriptor for our output.
|
||||
if ourAmt >= int64(chanState.RemoteChanCfg.DustLimit) {
|
||||
br.LocalOutputSignDesc = &input.SignDescriptor{
|
||||
SingleTweak: keyRing.LocalCommitKeyTweak,
|
||||
KeyDesc: chanState.LocalChanCfg.PaymentBasePoint,
|
||||
WitnessScript: ourScript.WitnessScript,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: ourScript.PkScript,
|
||||
Value: ourAmt,
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
}
|
||||
}
|
||||
|
||||
// Similarly, if their balance exceeds the remote party's dust limit,
|
||||
// assemble the sign descriptor for their output, which we can sweep.
|
||||
if theirAmt >= int64(chanState.RemoteChanCfg.DustLimit) {
|
||||
br.RemoteOutputSignDesc = &input.SignDescriptor{
|
||||
KeyDesc: chanState.LocalChanCfg.
|
||||
RevocationBasePoint,
|
||||
DoubleTweak: commitmentSecret,
|
||||
WitnessScript: theirScript.WitnessScript,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: theirScript.PkScript,
|
||||
Value: theirAmt,
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, with all the necessary data constructed, we can pad the
|
||||
// BreachRetribution struct which houses all the data necessary to
|
||||
// swiftly bring justice to the cheating remote party.
|
||||
br.BreachHeight = breachHeight
|
||||
br.RevokedStateNum = stateNum
|
||||
br.LocalDelay = ourDelay
|
||||
br.RemoteDelay = theirDelay
|
||||
|
||||
return br, nil
|
||||
}
|
||||
|
||||
// createHtlcRetribution is a helper function to construct an HtlcRetribution
|
||||
// based on the passed params.
|
||||
func createHtlcRetribution(chanState *channeldb.OpenChannel,
|
||||
keyRing *CommitmentKeyRing, commitHash chainhash.Hash,
|
||||
commitmentSecret *btcec.PrivateKey, leaseExpiry uint32,
|
||||
htlc *channeldb.HTLCEntry) (HtlcRetribution, error) {
|
||||
|
||||
var emptyRetribution HtlcRetribution
|
||||
|
||||
theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
|
||||
isRemoteInitiator := !chanState.IsInitiator
|
||||
|
||||
// We'll generate the original second level witness script now, as
|
||||
// we'll need it if we're revoking an HTLC output on the remote
|
||||
// commitment transaction, and *they* go to the second level.
|
||||
secondLevelScript, err := SecondLevelHtlcScript(
|
||||
chanState.ChanType, isRemoteInitiator,
|
||||
keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay,
|
||||
leaseExpiry,
|
||||
)
|
||||
if err != nil {
|
||||
return emptyRetribution, err
|
||||
}
|
||||
|
||||
// If this is an incoming HTLC, then this means that they were the
|
||||
// sender of the HTLC (relative to us). So we'll re-generate the sender
|
||||
// HTLC script. Otherwise, is this was an outgoing HTLC that we sent,
|
||||
// then from the PoV of the remote commitment state, they're the
|
||||
// receiver of this HTLC.
|
||||
htlcPkScript, htlcWitnessScript, err := genHtlcScript(
|
||||
chanState.ChanType, htlc.Incoming, false,
|
||||
htlc.RefundTimeout, htlc.RHash, keyRing,
|
||||
)
|
||||
if err != nil {
|
||||
return emptyRetribution, err
|
||||
}
|
||||
|
||||
return HtlcRetribution{
|
||||
SignDesc: input.SignDescriptor{
|
||||
KeyDesc: chanState.LocalChanCfg.
|
||||
RevocationBasePoint,
|
||||
DoubleTweak: commitmentSecret,
|
||||
WitnessScript: htlcWitnessScript,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: htlcPkScript,
|
||||
Value: int64(htlc.Amt),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
},
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: commitHash,
|
||||
Index: uint32(htlc.OutputIndex),
|
||||
},
|
||||
SecondLevelWitnessScript: secondLevelScript.WitnessScript,
|
||||
IsIncoming: htlc.Incoming,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// createBreachRetribution creates a partially initiated BreachRetribution
|
||||
// using a RevocationLog. Returns the constructed retribution, our amount,
|
||||
// their amount, and a possible non-nil error.
|
||||
func createBreachRetribution(revokedLog *channeldb.RevocationLog,
|
||||
spendTx *wire.MsgTx, chanState *channeldb.OpenChannel,
|
||||
keyRing *CommitmentKeyRing, commitmentSecret *btcec.PrivateKey,
|
||||
leaseExpiry uint32) (*BreachRetribution, int64, int64, error) {
|
||||
|
||||
commitHash := revokedLog.CommitTxHash
|
||||
|
||||
// Create the htlc retributions.
|
||||
htlcRetributions := make([]HtlcRetribution, len(revokedLog.HTLCEntries))
|
||||
for i, htlc := range revokedLog.HTLCEntries {
|
||||
hr, err := createHtlcRetribution(
|
||||
chanState, keyRing, commitHash,
|
||||
commitmentSecret, leaseExpiry, htlc,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
htlcRetributions[i] = hr
|
||||
}
|
||||
|
||||
var ourAmt, theirAmt int64
|
||||
|
||||
// Construct the our outpoint.
|
||||
ourOutpoint := wire.OutPoint{
|
||||
Hash: commitHash,
|
||||
}
|
||||
if revokedLog.OurOutputIndex != channeldb.OutputIndexEmpty {
|
||||
ourOutpoint.Index = uint32(revokedLog.OurOutputIndex)
|
||||
|
||||
// Sanity check that OurOutputIndex is within range.
|
||||
if int(ourOutpoint.Index) >= len(spendTx.TxOut) {
|
||||
return nil, 0, 0, fmt.Errorf("%w: ours=%v, "+
|
||||
"len(TxOut)=%v", ErrOutputIndexOutOfRange,
|
||||
ourOutpoint.Index, len(spendTx.TxOut),
|
||||
)
|
||||
}
|
||||
// Read the amounts from the breach transaction.
|
||||
//
|
||||
// NOTE: ourAmt here includes commit fee and anchor amount(if
|
||||
// enabled).
|
||||
ourAmt = spendTx.TxOut[ourOutpoint.Index].Value
|
||||
}
|
||||
|
||||
// Construct the their outpoint.
|
||||
theirOutpoint := wire.OutPoint{
|
||||
Hash: commitHash,
|
||||
}
|
||||
if revokedLog.TheirOutputIndex != channeldb.OutputIndexEmpty {
|
||||
theirOutpoint.Index = uint32(revokedLog.TheirOutputIndex)
|
||||
|
||||
// Sanity check that TheirOutputIndex is within range.
|
||||
if int(revokedLog.TheirOutputIndex) >= len(spendTx.TxOut) {
|
||||
return nil, 0, 0, fmt.Errorf("%w: theirs=%v, "+
|
||||
"len(TxOut)=%v", ErrOutputIndexOutOfRange,
|
||||
revokedLog.TheirOutputIndex, len(spendTx.TxOut),
|
||||
)
|
||||
}
|
||||
|
||||
// Read the amounts from the breach transaction.
|
||||
theirAmt = spendTx.TxOut[theirOutpoint.Index].Value
|
||||
}
|
||||
|
||||
return &BreachRetribution{
|
||||
BreachTxHash: commitHash,
|
||||
ChainHash: chanState.ChainHash,
|
||||
LocalOutpoint: ourOutpoint,
|
||||
RemoteOutpoint: theirOutpoint,
|
||||
HtlcRetributions: htlcRetributions,
|
||||
KeyRing: keyRing,
|
||||
}, ourAmt, theirAmt, nil
|
||||
}
|
||||
|
||||
// createBreachRetributionLegacy creates a partially initiated
|
||||
// BreachRetribution using a ChannelCommitment. Returns the constructed
|
||||
// retribution, our amount, their amount, and a possible non-nil error.
|
||||
func createBreachRetributionLegacy(revokedLog *channeldb.ChannelCommitment,
|
||||
chanState *channeldb.OpenChannel, keyRing *CommitmentKeyRing,
|
||||
commitmentSecret *btcec.PrivateKey,
|
||||
ourScript, theirScript *ScriptInfo,
|
||||
leaseExpiry uint32) (*BreachRetribution, int64, int64, error) {
|
||||
|
||||
commitHash := revokedLog.CommitTx.TxHash()
|
||||
ourOutpoint := wire.OutPoint{
|
||||
Hash: commitHash,
|
||||
}
|
||||
theirOutpoint := wire.OutPoint{
|
||||
Hash: commitHash,
|
||||
}
|
||||
for i, txOut := range revokedSnapshot.CommitTx.TxOut {
|
||||
|
||||
// In order to fully populate the breach retribution struct, we'll need
|
||||
// to find the exact index of the commitment outputs.
|
||||
for i, txOut := range revokedLog.CommitTx.TxOut {
|
||||
switch {
|
||||
case bytes.Equal(txOut.PkScript, ourScript.PkScript):
|
||||
ourOutpoint.Index = uint32(i)
|
||||
@@ -2352,127 +2593,52 @@ 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 (
|
||||
ourSignDesc *input.SignDescriptor
|
||||
theirSignDesc *input.SignDescriptor
|
||||
)
|
||||
|
||||
// Compute the balances in satoshis.
|
||||
ourAmt := revokedSnapshot.LocalBalance.ToSatoshis()
|
||||
theirAmt := revokedSnapshot.RemoteBalance.ToSatoshis()
|
||||
|
||||
// If our balance exceeds the remote party's dust limit, instantiate
|
||||
// the sign descriptor for our output.
|
||||
if ourAmt >= chanState.RemoteChanCfg.DustLimit {
|
||||
ourSignDesc = &input.SignDescriptor{
|
||||
SingleTweak: keyRing.LocalCommitKeyTweak,
|
||||
KeyDesc: chanState.LocalChanCfg.PaymentBasePoint,
|
||||
WitnessScript: ourScript.WitnessScript,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: ourScript.PkScript,
|
||||
Value: int64(ourAmt),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
}
|
||||
}
|
||||
|
||||
// Similarly, if their balance exceeds the remote party's dust limit,
|
||||
// assemble the sign descriptor for their output, which we can sweep.
|
||||
if theirAmt >= chanState.RemoteChanCfg.DustLimit {
|
||||
theirSignDesc = &input.SignDescriptor{
|
||||
KeyDesc: chanState.LocalChanCfg.RevocationBasePoint,
|
||||
DoubleTweak: commitmentSecret,
|
||||
WitnessScript: theirScript.WitnessScript,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: theirScript.PkScript,
|
||||
Value: int64(theirAmt),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
}
|
||||
}
|
||||
|
||||
// With the commitment outputs located, we'll now generate all the
|
||||
// retribution structs for each of the HTLC transactions active on the
|
||||
// remote commitment transaction.
|
||||
htlcRetributions := make([]HtlcRetribution, 0, len(revokedSnapshot.Htlcs))
|
||||
for _, htlc := range revokedSnapshot.Htlcs {
|
||||
htlcRetributions := make([]HtlcRetribution, len(revokedLog.Htlcs))
|
||||
for i, htlc := range revokedLog.Htlcs {
|
||||
// If the HTLC is dust, then we'll skip it as it doesn't have
|
||||
// an output on the commitment transaction.
|
||||
if HtlcIsDust(
|
||||
chanState.ChanType, htlc.Incoming, false,
|
||||
chainfee.SatPerKWeight(revokedSnapshot.FeePerKw),
|
||||
htlc.Amt.ToSatoshis(), chanState.RemoteChanCfg.DustLimit,
|
||||
chainfee.SatPerKWeight(revokedLog.FeePerKw),
|
||||
htlc.Amt.ToSatoshis(),
|
||||
chanState.RemoteChanCfg.DustLimit,
|
||||
) {
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// We'll generate the original second level witness script now,
|
||||
// as we'll need it if we're revoking an HTLC output on the
|
||||
// remote commitment transaction, and *they* go to the second
|
||||
// level.
|
||||
secondLevelScript, err := SecondLevelHtlcScript(
|
||||
chanState.ChanType, isRemoteInitiator,
|
||||
keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay,
|
||||
leaseExpiry,
|
||||
entry := &channeldb.HTLCEntry{
|
||||
RHash: htlc.RHash,
|
||||
RefundTimeout: htlc.RefundTimeout,
|
||||
OutputIndex: uint16(htlc.OutputIndex),
|
||||
Incoming: htlc.Incoming,
|
||||
Amt: htlc.Amt.ToSatoshis(),
|
||||
}
|
||||
hr, err := createHtlcRetribution(
|
||||
chanState, keyRing, commitHash,
|
||||
commitmentSecret, leaseExpiry, entry,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
// If this is an incoming HTLC, then this means that they were
|
||||
// the sender of the HTLC (relative to us). So we'll
|
||||
// re-generate the sender HTLC script. Otherwise, is this was
|
||||
// an outgoing HTLC that we sent, then from the PoV of the
|
||||
// remote commitment state, they're the receiver of this HTLC.
|
||||
htlcPkScript, htlcWitnessScript, err := genHtlcScript(
|
||||
chanState.ChanType, htlc.Incoming, false,
|
||||
htlc.RefundTimeout, htlc.RHash, keyRing,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
htlcRetributions = append(htlcRetributions, HtlcRetribution{
|
||||
SignDesc: input.SignDescriptor{
|
||||
KeyDesc: chanState.LocalChanCfg.RevocationBasePoint,
|
||||
DoubleTweak: commitmentSecret,
|
||||
WitnessScript: htlcWitnessScript,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: htlcPkScript,
|
||||
Value: int64(htlc.Amt.ToSatoshis()),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
},
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: commitHash,
|
||||
Index: uint32(htlc.OutputIndex),
|
||||
},
|
||||
SecondLevelWitnessScript: secondLevelScript.WitnessScript,
|
||||
IsIncoming: htlc.Incoming,
|
||||
})
|
||||
htlcRetributions[i] = hr
|
||||
}
|
||||
|
||||
// Finally, with all the necessary data constructed, we can create the
|
||||
// BreachRetribution struct which houses all the data necessary to
|
||||
// swiftly bring justice to the cheating remote party.
|
||||
// Compute the balances in satoshis.
|
||||
ourAmt := int64(revokedLog.LocalBalance.ToSatoshis())
|
||||
theirAmt := int64(revokedLog.RemoteBalance.ToSatoshis())
|
||||
|
||||
return &BreachRetribution{
|
||||
ChainHash: chanState.ChainHash,
|
||||
BreachTransaction: revokedSnapshot.CommitTx,
|
||||
BreachHeight: breachHeight,
|
||||
RevokedStateNum: stateNum,
|
||||
PendingHTLCs: revokedSnapshot.Htlcs,
|
||||
LocalOutpoint: ourOutpoint,
|
||||
LocalOutputSignDesc: ourSignDesc,
|
||||
LocalDelay: ourDelay,
|
||||
RemoteOutpoint: theirOutpoint,
|
||||
RemoteOutputSignDesc: theirSignDesc,
|
||||
RemoteDelay: theirDelay,
|
||||
HtlcRetributions: htlcRetributions,
|
||||
KeyRing: keyRing,
|
||||
}, nil
|
||||
BreachTxHash: commitHash,
|
||||
ChainHash: chanState.ChainHash,
|
||||
LocalOutpoint: ourOutpoint,
|
||||
RemoteOutpoint: theirOutpoint,
|
||||
HtlcRetributions: htlcRetributions,
|
||||
KeyRing: keyRing,
|
||||
}, ourAmt, theirAmt, nil
|
||||
}
|
||||
|
||||
// HtlcIsDust determines if an HTLC output is dust or not depending on two
|
||||
@@ -3822,7 +3988,7 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
|
||||
// but died before the signature was sent. We re-transmit our
|
||||
// revocation, but also initiate a state transition to re-sync
|
||||
// them.
|
||||
if lc.OweCommitment(true) {
|
||||
if lc.OweCommitment() {
|
||||
commitSig, htlcSigs, _, err := lc.SignNextCommitment()
|
||||
switch {
|
||||
|
||||
@@ -4536,11 +4702,11 @@ func (lc *LightningChannel) IsChannelClean() bool {
|
||||
// out a commitment signature because there are outstanding local updates and/or
|
||||
// updates in the local commit tx that aren't reflected in the remote commit tx
|
||||
// yet.
|
||||
func (lc *LightningChannel) OweCommitment(local bool) bool {
|
||||
func (lc *LightningChannel) OweCommitment() bool {
|
||||
lc.RLock()
|
||||
defer lc.RUnlock()
|
||||
|
||||
return lc.oweCommitment(local)
|
||||
return lc.oweCommitment(true)
|
||||
}
|
||||
|
||||
// oweCommitment is the internal version of OweCommitment. This function expects
|
||||
@@ -4874,12 +5040,28 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
|
||||
source, remoteChainTail, addUpdates, settleFailUpdates,
|
||||
)
|
||||
|
||||
// We will soon be saving the current remote commitment to revocation
|
||||
// log bucket, which is `lc.channelState.RemoteCommitment`. After that,
|
||||
// the `RemoteCommitment` will be replaced with a newer version found
|
||||
// in `CommitDiff`. Thus we need to compute the output indexes here
|
||||
// before the change since the indexes are meant for the current,
|
||||
// revoked remote commitment.
|
||||
ourOutputIndex, theirOutputIndex, err := findOutputIndexesFromRemote(
|
||||
revocation, lc.channelState,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
// At this point, the revocation has been accepted, and we've rotated
|
||||
// the current revocation key+hash for the remote party. Therefore we
|
||||
// sync now to ensure the revocation producer state is consistent with
|
||||
// the current commitment height and also to advance the on-disk
|
||||
// commitment chain.
|
||||
err = lc.channelState.AdvanceCommitChainTail(fwdPkg, localPeerUpdates)
|
||||
err = lc.channelState.AdvanceCommitChainTail(
|
||||
fwdPkg, localPeerUpdates,
|
||||
ourOutputIndex, theirOutputIndex,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
+388
-1
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
@@ -7106,8 +7107,9 @@ func TestNewBreachRetributionSkipsDustHtlcs(t *testing.T) {
|
||||
|
||||
// At this point, we'll now simulate a contract breach by Bob using the
|
||||
// NewBreachRetribution method.
|
||||
breachTx := aliceChannel.channelState.RemoteCommitment.CommitTx
|
||||
breachRet, err := NewBreachRetribution(
|
||||
aliceChannel.channelState, revokedStateNum, 100,
|
||||
aliceChannel.channelState, revokedStateNum, 100, breachTx,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create breach retribution: %v", err)
|
||||
@@ -10249,3 +10251,388 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) {
|
||||
checkDust(bobChannel, htlc2Amt+htlc3Amt, htlc2Amt+htlc3Amt)
|
||||
}
|
||||
}
|
||||
|
||||
// deriveDummyRetributionParams is a helper function that derives a list of
|
||||
// dummy params to assist retribution creation related tests.
|
||||
func deriveDummyRetributionParams(chanState *channeldb.OpenChannel) (uint32,
|
||||
*CommitmentKeyRing, chainhash.Hash) {
|
||||
|
||||
config := chanState.RemoteChanCfg
|
||||
commitHash := chanState.RemoteCommitment.CommitTx.TxHash()
|
||||
keyRing := DeriveCommitmentKeys(
|
||||
config.RevocationBasePoint.PubKey, false, chanState.ChanType,
|
||||
&chanState.LocalChanCfg, &chanState.RemoteChanCfg,
|
||||
)
|
||||
leaseExpiry := chanState.ThawHeight
|
||||
return leaseExpiry, keyRing, commitHash
|
||||
}
|
||||
|
||||
// TestCreateHtlcRetribution checks that `createHtlcRetribution` behaves as
|
||||
// epxected.
|
||||
func TestCreateHtlcRetribution(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a dummy private key and an HTLC amount for testing.
|
||||
dummyPrivate, _ := btcec.PrivKeyFromBytes([]byte{1})
|
||||
testAmt := btcutil.Amount(100)
|
||||
|
||||
// Create a test channel.
|
||||
aliceChannel, _, cleanUp, err := CreateTestChannels(
|
||||
channeldb.ZeroHtlcTxFeeBit,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
defer cleanUp()
|
||||
|
||||
// Prepare the params needed to call the function. Note that the values
|
||||
// here are not necessary "cryptography-correct", we just use them to
|
||||
// construct the htlc retribution.
|
||||
leaseExpiry, keyRing, commitHash := deriveDummyRetributionParams(
|
||||
aliceChannel.channelState,
|
||||
)
|
||||
htlc := &channeldb.HTLCEntry{
|
||||
Amt: testAmt,
|
||||
Incoming: true,
|
||||
OutputIndex: 1,
|
||||
}
|
||||
|
||||
// Create the htlc retribution.
|
||||
hr, err := createHtlcRetribution(
|
||||
aliceChannel.channelState, keyRing, commitHash,
|
||||
dummyPrivate, leaseExpiry, htlc,
|
||||
)
|
||||
// Expect no error.
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the fields have expected values.
|
||||
require.EqualValues(t, testAmt, hr.SignDesc.Output.Value)
|
||||
require.Equal(t, commitHash, hr.OutPoint.Hash)
|
||||
require.EqualValues(t, htlc.OutputIndex, hr.OutPoint.Index)
|
||||
require.Equal(t, htlc.Incoming, hr.IsIncoming)
|
||||
}
|
||||
|
||||
// TestCreateBreachRetribution checks that `createBreachRetribution` behaves as
|
||||
// epxected.
|
||||
func TestCreateBreachRetribution(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create dummy values for the test.
|
||||
dummyPrivate, _ := btcec.PrivKeyFromBytes([]byte{1})
|
||||
testAmt := int64(100)
|
||||
ourAmt := int64(1000)
|
||||
theirAmt := int64(2000)
|
||||
localIndex := uint32(0)
|
||||
remoteIndex := uint32(1)
|
||||
htlcIndex := uint32(2)
|
||||
|
||||
// Create a dummy breach tx, which has our output located at index 0
|
||||
// and theirs at 1.
|
||||
spendTx := &wire.MsgTx{
|
||||
TxOut: []*wire.TxOut{
|
||||
{Value: ourAmt},
|
||||
{Value: theirAmt},
|
||||
{Value: testAmt},
|
||||
},
|
||||
}
|
||||
|
||||
// Create a test channel.
|
||||
aliceChannel, _, cleanUp, err := CreateTestChannels(
|
||||
channeldb.ZeroHtlcTxFeeBit,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
defer cleanUp()
|
||||
|
||||
// Prepare the params needed to call the function. Note that the values
|
||||
// here are not necessary "cryptography-correct", we just use them to
|
||||
// construct the retribution.
|
||||
leaseExpiry, keyRing, commitHash := deriveDummyRetributionParams(
|
||||
aliceChannel.channelState,
|
||||
)
|
||||
htlc := &channeldb.HTLCEntry{
|
||||
Amt: btcutil.Amount(testAmt),
|
||||
Incoming: true,
|
||||
OutputIndex: uint16(htlcIndex),
|
||||
}
|
||||
|
||||
// Create a dummy revocation log.
|
||||
revokedLog := channeldb.RevocationLog{
|
||||
CommitTxHash: commitHash,
|
||||
OurOutputIndex: uint16(localIndex),
|
||||
TheirOutputIndex: uint16(remoteIndex),
|
||||
HTLCEntries: []*channeldb.HTLCEntry{htlc},
|
||||
}
|
||||
|
||||
// Create a log with an empty local output index.
|
||||
revokedLogNoLocal := revokedLog
|
||||
revokedLogNoLocal.OurOutputIndex = channeldb.OutputIndexEmpty
|
||||
|
||||
// Create a log with an empty remote output index.
|
||||
revokedLogNoRemote := revokedLog
|
||||
revokedLogNoRemote.TheirOutputIndex = channeldb.OutputIndexEmpty
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
revocationLog *channeldb.RevocationLog
|
||||
expectedErr error
|
||||
expectedOurAmt int64
|
||||
expectedTheirAmt int64
|
||||
}{
|
||||
{
|
||||
name: "create retribution successfully",
|
||||
revocationLog: &revokedLog,
|
||||
expectedErr: nil,
|
||||
expectedOurAmt: ourAmt,
|
||||
expectedTheirAmt: theirAmt,
|
||||
},
|
||||
{
|
||||
name: "fail due to our index too big",
|
||||
revocationLog: &channeldb.RevocationLog{
|
||||
OurOutputIndex: uint16(htlcIndex + 1),
|
||||
},
|
||||
expectedErr: ErrOutputIndexOutOfRange,
|
||||
},
|
||||
{
|
||||
name: "fail due to their index too big",
|
||||
revocationLog: &channeldb.RevocationLog{
|
||||
TheirOutputIndex: uint16(htlcIndex + 1),
|
||||
},
|
||||
expectedErr: ErrOutputIndexOutOfRange,
|
||||
},
|
||||
{
|
||||
name: "empty local output index",
|
||||
revocationLog: &revokedLogNoLocal,
|
||||
expectedErr: nil,
|
||||
expectedOurAmt: 0,
|
||||
expectedTheirAmt: theirAmt,
|
||||
},
|
||||
{
|
||||
name: "empty remote output index",
|
||||
revocationLog: &revokedLogNoRemote,
|
||||
expectedErr: nil,
|
||||
expectedOurAmt: ourAmt,
|
||||
expectedTheirAmt: 0,
|
||||
},
|
||||
}
|
||||
|
||||
// assertRetribution is a helper closure that checks a given breach
|
||||
// retribution has the expected values on certain fields.
|
||||
assertRetribution := func(br *BreachRetribution, our, their int64) {
|
||||
chainHash := aliceChannel.channelState.ChainHash
|
||||
require.Equal(t, commitHash, br.BreachTxHash)
|
||||
require.Equal(t, chainHash, br.ChainHash)
|
||||
|
||||
// Construct local outpoint, we only have the index when the
|
||||
// amount is not zero.
|
||||
local := wire.OutPoint{
|
||||
Hash: commitHash,
|
||||
}
|
||||
if our != 0 {
|
||||
local.Index = localIndex
|
||||
}
|
||||
|
||||
// Construct remote outpoint, we only have the index when the
|
||||
// amount is not zero.
|
||||
remote := wire.OutPoint{
|
||||
Hash: commitHash,
|
||||
}
|
||||
if their != 0 {
|
||||
remote.Index = remoteIndex
|
||||
}
|
||||
|
||||
require.Equal(t, local, br.LocalOutpoint)
|
||||
require.Equal(t, remote, br.RemoteOutpoint)
|
||||
|
||||
for _, hr := range br.HtlcRetributions {
|
||||
require.EqualValues(t, testAmt,
|
||||
hr.SignDesc.Output.Value)
|
||||
require.Equal(t, commitHash, hr.OutPoint.Hash)
|
||||
require.EqualValues(t, htlcIndex, hr.OutPoint.Index)
|
||||
require.Equal(t, htlc.Incoming, hr.IsIncoming)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
br, our, their, err := createBreachRetribution(
|
||||
tc.revocationLog, spendTx,
|
||||
aliceChannel.channelState, keyRing,
|
||||
dummyPrivate, leaseExpiry,
|
||||
)
|
||||
|
||||
// Check the error if expected.
|
||||
if tc.expectedErr != nil {
|
||||
require.ErrorIs(t, err, tc.expectedErr)
|
||||
} else {
|
||||
// Otherwise we expect no error.
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the amounts and the contructed partial
|
||||
// retribution are returned as expected.
|
||||
require.Equal(t, tc.expectedOurAmt, our)
|
||||
require.Equal(t, tc.expectedTheirAmt, their)
|
||||
assertRetribution(br, our, their)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateBreachRetributionLegacy checks that
|
||||
// `createBreachRetributionLegacy` behaves as expected.
|
||||
func TestCreateBreachRetributionLegacy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create dummy values for the test.
|
||||
dummyPrivate, _ := btcec.PrivKeyFromBytes([]byte{1})
|
||||
|
||||
// Create a test channel.
|
||||
aliceChannel, _, cleanUp, err := CreateTestChannels(
|
||||
channeldb.ZeroHtlcTxFeeBit,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
defer cleanUp()
|
||||
|
||||
// Prepare the params needed to call the function. Note that the values
|
||||
// here are not necessary "cryptography-correct", we just use them to
|
||||
// construct the retribution.
|
||||
leaseExpiry, keyRing, _ := deriveDummyRetributionParams(
|
||||
aliceChannel.channelState,
|
||||
)
|
||||
|
||||
// Use the remote commitment as our revocation log.
|
||||
revokedLog := aliceChannel.channelState.RemoteCommitment
|
||||
|
||||
ourOp := revokedLog.CommitTx.TxOut[0]
|
||||
theirOp := revokedLog.CommitTx.TxOut[1]
|
||||
|
||||
// Create the dummy scripts.
|
||||
ourScript := &ScriptInfo{
|
||||
PkScript: ourOp.PkScript,
|
||||
}
|
||||
theirScript := &ScriptInfo{
|
||||
PkScript: theirOp.PkScript,
|
||||
}
|
||||
|
||||
// Create the breach retribution using the legacy format.
|
||||
br, ourAmt, theirAmt, err := createBreachRetributionLegacy(
|
||||
&revokedLog, aliceChannel.channelState, keyRing,
|
||||
dummyPrivate, ourScript, theirScript, leaseExpiry,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the commitHash and chainHash.
|
||||
commitHash := revokedLog.CommitTx.TxHash()
|
||||
chainHash := aliceChannel.channelState.ChainHash
|
||||
require.Equal(t, commitHash, br.BreachTxHash)
|
||||
require.Equal(t, chainHash, br.ChainHash)
|
||||
|
||||
// Check the outpoints.
|
||||
local := wire.OutPoint{
|
||||
Hash: commitHash,
|
||||
Index: 0,
|
||||
}
|
||||
remote := wire.OutPoint{
|
||||
Hash: commitHash,
|
||||
Index: 1,
|
||||
}
|
||||
require.Equal(t, local, br.LocalOutpoint)
|
||||
require.Equal(t, remote, br.RemoteOutpoint)
|
||||
|
||||
// Validate the amounts, note that in the legacy format, our amount is
|
||||
// not directly the amount found in the to local output. Rather, it's
|
||||
// the local output value minus the commit fee and anchor value(if
|
||||
// present).
|
||||
require.EqualValues(t, revokedLog.LocalBalance.ToSatoshis(), ourAmt)
|
||||
require.Equal(t, theirOp.Value, theirAmt)
|
||||
}
|
||||
|
||||
// TestNewBreachRetribution tests that the function `NewBreachRetribution`
|
||||
// behaves as expected.
|
||||
func TestNewBreachRetribution(t *testing.T) {
|
||||
t.Run("non-anchor", func(t *testing.T) {
|
||||
testNewBreachRetribution(t, channeldb.ZeroHtlcTxFeeBit)
|
||||
})
|
||||
t.Run("anchor", func(t *testing.T) {
|
||||
chanType := channeldb.SingleFunderTweaklessBit |
|
||||
channeldb.AnchorOutputsBit
|
||||
testNewBreachRetribution(t, chanType)
|
||||
})
|
||||
}
|
||||
|
||||
// testNewBreachRetribution takes a channel type and tests the function
|
||||
// `NewBreachRetribution`.
|
||||
func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
||||
t.Parallel()
|
||||
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(chanType)
|
||||
require.NoError(t, err)
|
||||
defer cleanUp()
|
||||
|
||||
breachHeight := uint32(101)
|
||||
stateNum := uint64(0)
|
||||
chainHash := aliceChannel.channelState.ChainHash
|
||||
theirDelay := uint32(aliceChannel.channelState.RemoteChanCfg.CsvDelay)
|
||||
breachTx := aliceChannel.channelState.RemoteCommitment.CommitTx
|
||||
|
||||
// Create a breach retribution at height 0, which should give us an
|
||||
// error as there are no past delta state saved as revocation logs yet.
|
||||
_, err = NewBreachRetribution(
|
||||
aliceChannel.channelState, stateNum, breachHeight, breachTx,
|
||||
)
|
||||
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
|
||||
|
||||
// We now force a state transition which will give us a revocation log
|
||||
// at height 0.
|
||||
txid := aliceChannel.channelState.RemoteCommitment.CommitTx.TxHash()
|
||||
err = ForceStateTransition(aliceChannel, bobChannel)
|
||||
require.NoError(t, err)
|
||||
|
||||
// assertRetribution is a helper closure that checks a given breach
|
||||
// retribution has the expected values on certain fields.
|
||||
assertRetribution := func(br *BreachRetribution,
|
||||
localIndex, remoteIndex uint32) {
|
||||
|
||||
require.Equal(t, txid, br.BreachTxHash)
|
||||
require.Equal(t, chainHash, br.ChainHash)
|
||||
require.Equal(t, breachHeight, br.BreachHeight)
|
||||
require.Equal(t, stateNum, br.RevokedStateNum)
|
||||
require.Equal(t, theirDelay, br.RemoteDelay)
|
||||
|
||||
local := wire.OutPoint{
|
||||
Hash: txid,
|
||||
Index: localIndex,
|
||||
}
|
||||
remote := wire.OutPoint{
|
||||
Hash: txid,
|
||||
Index: remoteIndex,
|
||||
}
|
||||
|
||||
if chanType.HasAnchors() {
|
||||
// For anchor channels, we expect the local delay to be
|
||||
// 1 otherwise 0.
|
||||
require.EqualValues(t, 1, br.LocalDelay)
|
||||
} else {
|
||||
require.Zero(t, br.LocalDelay)
|
||||
}
|
||||
|
||||
require.Equal(t, local, br.LocalOutpoint)
|
||||
require.Equal(t, remote, br.RemoteOutpoint)
|
||||
}
|
||||
|
||||
// Create the retribution again and we should expect it to be created
|
||||
// successfully.
|
||||
br, err := NewBreachRetribution(
|
||||
aliceChannel.channelState, stateNum, breachHeight, breachTx,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the retribution is as expected.
|
||||
t.Log(spew.Sdump(breachTx))
|
||||
assertRetribution(br, 1, 0)
|
||||
|
||||
// Create the retribution using a stateNum+1 and we should expect an
|
||||
// error.
|
||||
_, err = NewBreachRetribution(
|
||||
aliceChannel.channelState, stateNum+1, breachHeight, breachTx,
|
||||
)
|
||||
require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound)
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
@@ -960,3 +962,76 @@ func addHTLC(commitTx *wire.MsgTx, ourCommit bool,
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findOutputIndexesFromRemote finds the index of our and their outputs from
|
||||
// the remote commitment transaction. It derives the key ring to compute the
|
||||
// output scripts and compares them against the outputs inside the commitment
|
||||
// to find the match.
|
||||
func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash,
|
||||
chanState *channeldb.OpenChannel) (uint32, uint32, error) {
|
||||
|
||||
// Init the output indexes as empty.
|
||||
ourIndex := uint32(channeldb.OutputIndexEmpty)
|
||||
theirIndex := uint32(channeldb.OutputIndexEmpty)
|
||||
|
||||
chanCommit := chanState.RemoteCommitment
|
||||
_, commitmentPoint := btcec.PrivKeyFromBytes(revocationPreimage[:])
|
||||
|
||||
// With the commitment point generated, we can now derive the king ring
|
||||
// which will be used to generate the output scripts.
|
||||
keyRing := DeriveCommitmentKeys(
|
||||
commitmentPoint, false, chanState.ChanType,
|
||||
&chanState.LocalChanCfg, &chanState.RemoteChanCfg,
|
||||
)
|
||||
|
||||
// Since it's remote commitment chain, we'd used the mirrored values.
|
||||
//
|
||||
// We use the remote's channel config for the csv delay.
|
||||
theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
|
||||
|
||||
// If we are the initiator of this channel, then it's be false from the
|
||||
// remote's PoV.
|
||||
isRemoteInitiator := !chanState.IsInitiator
|
||||
|
||||
var leaseExpiry uint32
|
||||
if chanState.ChanType.HasLeaseExpiration() {
|
||||
leaseExpiry = chanState.ThawHeight
|
||||
}
|
||||
|
||||
// Map the scripts from our PoV. When facing a local commitment, the to
|
||||
// local output belongs to us and the to remote output belongs to them.
|
||||
// When facing a remote commitment, the to local output belongs to them
|
||||
// and the to remote output belongs to us.
|
||||
|
||||
// Compute the to local script. From our PoV, when facing a remote
|
||||
// commitment, the to local output belongs to them.
|
||||
theirScript, err := CommitScriptToSelf(
|
||||
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
|
||||
keyRing.RevocationKey, theirDelay, leaseExpiry,
|
||||
)
|
||||
if err != nil {
|
||||
return ourIndex, theirIndex, err
|
||||
}
|
||||
|
||||
// Compute the to remote script. From our PoV, when facing a remote
|
||||
// commitment, the to remote output belongs to us.
|
||||
ourScript, _, err := CommitScriptToRemote(
|
||||
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
|
||||
leaseExpiry,
|
||||
)
|
||||
if err != nil {
|
||||
return ourIndex, theirIndex, err
|
||||
}
|
||||
|
||||
// Now compare the scripts to find our/their output index.
|
||||
for i, txOut := range chanCommit.CommitTx.TxOut {
|
||||
switch {
|
||||
case bytes.Equal(txOut.PkScript, ourScript.PkScript):
|
||||
ourIndex = uint32(i)
|
||||
case bytes.Equal(txOut.PkScript, theirScript.PkScript):
|
||||
theirIndex = uint32(i)
|
||||
}
|
||||
}
|
||||
|
||||
return ourIndex, theirIndex, nil
|
||||
}
|
||||
@@ -403,8 +403,10 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
FeePerKw: btcutil.Amount(commitFeePerKw),
|
||||
CommitFee: commitFee,
|
||||
},
|
||||
ThawHeight: thawHeight,
|
||||
Db: wallet.Cfg.Database,
|
||||
ThawHeight: thawHeight,
|
||||
Db: wallet.Cfg.Database,
|
||||
InitialLocalBalance: ourBalance,
|
||||
InitialRemoteBalance: theirBalance,
|
||||
},
|
||||
pushMSat: pushMSat,
|
||||
pendingChanID: pendingChanID,
|
||||
|
||||
Reference in new issue
Block a user