Merge pull request #9929 from yyforyongyu/fix-decalylog
htlcswitch: remove the bucket `batchReplayBucket`
This commit is contained in:
5 files changed
+72
-82
No files matched your search
@@ -51,6 +51,11 @@
|
|||||||
|
|
||||||
## Performance Improvements
|
## Performance Improvements
|
||||||
|
|
||||||
|
- The replay protection is
|
||||||
|
[optimized](https://github.com/lightningnetwork/lnd/pull/9929) to use less disk
|
||||||
|
space such that the `sphinxreplay.db` or the `decayedlogdb_kv` table will grow
|
||||||
|
much more slowly.
|
||||||
|
|
||||||
## Deprecations
|
## Deprecations
|
||||||
|
|
||||||
# Technical and Architectural Updates
|
# Technical and Architectural Updates
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package htlcswitch
|
package htlcswitch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -24,11 +23,6 @@ var (
|
|||||||
// bytes of a received HTLC's hashed shared secret as the key and the HTLC's
|
// bytes of a received HTLC's hashed shared secret as the key and the HTLC's
|
||||||
// CLTV expiry as the value.
|
// CLTV expiry as the value.
|
||||||
sharedHashBucket = []byte("shared-hash")
|
sharedHashBucket = []byte("shared-hash")
|
||||||
|
|
||||||
// batchReplayBucket is a bucket that maps batch identifiers to
|
|
||||||
// serialized ReplaySets. This is used to give idempotency in the event
|
|
||||||
// that a batch is processed more than once.
|
|
||||||
batchReplayBucket = []byte("batch-replay")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -138,11 +132,6 @@ func (d *DecayedLog) initBuckets() error {
|
|||||||
return ErrDecayedLogInit
|
return ErrDecayedLogInit
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.CreateTopLevelBucket(batchReplayBucket)
|
|
||||||
if err != nil {
|
|
||||||
return ErrDecayedLogInit
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}, func() {})
|
}, func() {})
|
||||||
}
|
}
|
||||||
@@ -329,11 +318,8 @@ func (d *DecayedLog) Put(hash *sphinx.HashPrefix, cltv uint32) error {
|
|||||||
// PutBatch accepts a pending batch of hashed secret entries to write to disk.
|
// PutBatch accepts a pending batch of hashed secret entries to write to disk.
|
||||||
// Each hashed secret is inserted with a corresponding time value, dictating
|
// Each hashed secret is inserted with a corresponding time value, dictating
|
||||||
// when the entry will be evicted from the log.
|
// when the entry will be evicted from the log.
|
||||||
// NOTE: This method enforces idempotency by writing the replay set obtained
|
//
|
||||||
// from the first attempt for a particular batch ID, and decoding the return
|
// TODO(yy): remove this method and use `Put` instead.
|
||||||
// value to subsequent calls. For the indices of the replay set to be aligned
|
|
||||||
// properly, the batch MUST be constructed identically to the first attempt,
|
|
||||||
// pruning will cause the indices to become invalid.
|
|
||||||
func (d *DecayedLog) PutBatch(b *sphinx.Batch) (*sphinx.ReplaySet, error) {
|
func (d *DecayedLog) PutBatch(b *sphinx.Batch) (*sphinx.ReplaySet, error) {
|
||||||
// Since batched boltdb txns may be executed multiple times before
|
// Since batched boltdb txns may be executed multiple times before
|
||||||
// succeeding, we will create a new replay set for each invocation to
|
// succeeding, we will create a new replay set for each invocation to
|
||||||
@@ -348,25 +334,6 @@ func (d *DecayedLog) PutBatch(b *sphinx.Batch) (*sphinx.ReplaySet, error) {
|
|||||||
return ErrDecayedLogCorrupted
|
return ErrDecayedLogCorrupted
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the batch replay bucket, which will be used to either
|
|
||||||
// retrieve the result of previously processing this batch, or
|
|
||||||
// to write the result of this operation.
|
|
||||||
batchReplayBkt := tx.ReadWriteBucket(batchReplayBucket)
|
|
||||||
if batchReplayBkt == nil {
|
|
||||||
return ErrDecayedLogCorrupted
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for the existence of this batch's id in the replay
|
|
||||||
// bucket. If a non-nil value is found, this indicates that we
|
|
||||||
// have already processed this batch before. We deserialize the
|
|
||||||
// resulting and return it to ensure calls to put batch are
|
|
||||||
// idempotent.
|
|
||||||
replayBytes := batchReplayBkt.Get(b.ID)
|
|
||||||
if replayBytes != nil {
|
|
||||||
replays = sphinx.NewReplaySet()
|
|
||||||
return replays.Decode(bytes.NewReader(replayBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// The CLTV will be stored into scratch and then stored into the
|
// The CLTV will be stored into scratch and then stored into the
|
||||||
// sharedHashBucket.
|
// sharedHashBucket.
|
||||||
var scratch [4]byte
|
var scratch [4]byte
|
||||||
@@ -394,17 +361,7 @@ func (d *DecayedLog) PutBatch(b *sphinx.Batch) (*sphinx.ReplaySet, error) {
|
|||||||
// batch's construction.
|
// batch's construction.
|
||||||
replays.Merge(b.ReplaySet)
|
replays.Merge(b.ReplaySet)
|
||||||
|
|
||||||
// Write the replay set under the batch identifier to the batch
|
return nil
|
||||||
// replays bucket. This can be used during recovery to test (1)
|
|
||||||
// that a particular batch was successfully processed and (2)
|
|
||||||
// recover the indexes of the adds that were rejected as
|
|
||||||
// replays.
|
|
||||||
var replayBuf bytes.Buffer
|
|
||||||
if err := replays.Encode(&replayBuf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return batchReplayBkt.Put(b.ID, replayBuf.Bytes())
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -745,7 +745,8 @@ func (r *DecodeHopIteratorResponse) Result() (Iterator, lnwire.FailCode) {
|
|||||||
// the presented readers and rhashes *NEVER* deviate across invocations for the
|
// the presented readers and rhashes *NEVER* deviate across invocations for the
|
||||||
// same id.
|
// same id.
|
||||||
func (p *OnionProcessor) DecodeHopIterators(id []byte,
|
func (p *OnionProcessor) DecodeHopIterators(id []byte,
|
||||||
reqs []DecodeHopIteratorRequest) ([]DecodeHopIteratorResponse, error) {
|
reqs []DecodeHopIteratorRequest,
|
||||||
|
reforward bool) ([]DecodeHopIteratorResponse, error) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
batchSize = len(reqs)
|
batchSize = len(reqs)
|
||||||
@@ -783,6 +784,8 @@ func (p *OnionProcessor) DecodeHopIterators(id []byte,
|
|||||||
b.Val,
|
b.Val,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO(yy): use `p.router.ProcessOnionPacket` instead.
|
||||||
err = tx.ProcessOnionPacket(
|
err = tx.ProcessOnionPacket(
|
||||||
seqNum, onionPkt, req.RHash, req.IncomingCltv, opts...,
|
seqNum, onionPkt, req.RHash, req.IncomingCltv, opts...,
|
||||||
)
|
)
|
||||||
@@ -864,11 +867,12 @@ func (p *OnionProcessor) DecodeHopIterators(id []byte,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this index is contained in the replay set, mark it with a
|
// If this index is contained in the replay set, and it is not a
|
||||||
// temporary channel failure error code. We infer that the
|
// reforwarding on startup, mark it with a permanent channel
|
||||||
// offending error was due to a replayed packet because this
|
// failure error code. We infer that the offending error was due
|
||||||
// index was found in the replay set.
|
// to a replayed packet because this index was found in the
|
||||||
if replays.Contains(uint16(i)) {
|
// replay set.
|
||||||
|
if !reforward && replays.Contains(uint16(i)) {
|
||||||
log.Errorf("unable to process onion packet: %v",
|
log.Errorf("unable to process onion packet: %v",
|
||||||
sphinx.ErrReplayedPacket)
|
sphinx.ErrReplayedPacket)
|
||||||
|
|
||||||
|
|||||||
+53
-29
@@ -108,8 +108,10 @@ type ChannelLinkConfig struct {
|
|||||||
// blobs, which are then used to inform how to forward an HTLC.
|
// blobs, which are then used to inform how to forward an HTLC.
|
||||||
//
|
//
|
||||||
// NOTE: This function assumes the same set of readers and preimages
|
// NOTE: This function assumes the same set of readers and preimages
|
||||||
// are always presented for the same identifier.
|
// are always presented for the same identifier. The last boolean is
|
||||||
DecodeHopIterators func([]byte, []hop.DecodeHopIteratorRequest) (
|
// used to decide whether this is a reforwarding or not - when it's
|
||||||
|
// reforwarding, we skip the replay check enforced in our decay log.
|
||||||
|
DecodeHopIterators func([]byte, []hop.DecodeHopIteratorRequest, bool) (
|
||||||
[]hop.DecodeHopIteratorResponse, error)
|
[]hop.DecodeHopIteratorResponse, error)
|
||||||
|
|
||||||
// ExtractErrorEncrypter function is responsible for decoding HTLC
|
// ExtractErrorEncrypter function is responsible for decoding HTLC
|
||||||
@@ -3630,6 +3632,13 @@ func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exit early if the fwdPkg is already processed.
|
||||||
|
if fwdPkg.State == channeldb.FwdStateCompleted {
|
||||||
|
l.log.Debugf("skipped processing completed fwdPkg %v", fwdPkg)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
l.log.Debugf("settle-fail-filter: %v", fwdPkg.SettleFailFilter)
|
l.log.Debugf("settle-fail-filter: %v", fwdPkg.SettleFailFilter)
|
||||||
|
|
||||||
var switchPackets []*htlcPacket
|
var switchPackets []*htlcPacket
|
||||||
@@ -3738,13 +3747,36 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exit early if the fwdPkg is already processed.
|
||||||
|
if fwdPkg.State == channeldb.FwdStateCompleted {
|
||||||
|
l.log.Debugf("skipped processing completed fwdPkg %v", fwdPkg)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
l.log.Tracef("processing %d remote adds for height %d",
|
l.log.Tracef("processing %d remote adds for height %d",
|
||||||
len(fwdPkg.Adds), fwdPkg.Height)
|
len(fwdPkg.Adds), fwdPkg.Height)
|
||||||
|
|
||||||
decodeReqs := make(
|
// decodeReqs is a list of requests sent to the onion decoder. We expect
|
||||||
[]hop.DecodeHopIteratorRequest, 0, len(fwdPkg.Adds),
|
// the same length of responses to be returned.
|
||||||
)
|
decodeReqs := make([]hop.DecodeHopIteratorRequest, 0, len(fwdPkg.Adds))
|
||||||
for _, update := range fwdPkg.Adds {
|
|
||||||
|
// unackedAdds is a list of ADDs that's waiting for the remote's
|
||||||
|
// settle/fail update.
|
||||||
|
unackedAdds := make([]*lnwire.UpdateAddHTLC, 0, len(fwdPkg.Adds))
|
||||||
|
|
||||||
|
for i, update := range fwdPkg.Adds {
|
||||||
|
// If this index is already found in the ack filter, the
|
||||||
|
// response to this forwarding decision has already been
|
||||||
|
// committed by one of our commitment txns. ADDs in this state
|
||||||
|
// are waiting for the rest of the fwding package to get acked
|
||||||
|
// before being garbage collected.
|
||||||
|
if fwdPkg.State == channeldb.FwdStateProcessed &&
|
||||||
|
fwdPkg.AckFilter.Contains(uint16(i)) {
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if msg, ok := update.UpdateMsg.(*lnwire.UpdateAddHTLC); ok {
|
if msg, ok := update.UpdateMsg.(*lnwire.UpdateAddHTLC); ok {
|
||||||
// Before adding the new htlc to the state machine,
|
// Before adding the new htlc to the state machine,
|
||||||
// parse the onion object in order to obtain the
|
// parse the onion object in order to obtain the
|
||||||
@@ -3761,15 +3793,20 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
decodeReqs = append(decodeReqs, req)
|
decodeReqs = append(decodeReqs, req)
|
||||||
|
unackedAdds = append(unackedAdds, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the fwdPkg has already been processed, it means we are
|
||||||
|
// reforwarding the packets again, which happens only on a restart.
|
||||||
|
reforward := fwdPkg.State == channeldb.FwdStateProcessed
|
||||||
|
|
||||||
// Atomically decode the incoming htlcs, simultaneously checking for
|
// Atomically decode the incoming htlcs, simultaneously checking for
|
||||||
// replay attempts. A particular index in the returned, spare list of
|
// replay attempts. A particular index in the returned, spare list of
|
||||||
// channel iterators should only be used if the failure code at the
|
// channel iterators should only be used if the failure code at the
|
||||||
// same index is lnwire.FailCodeNone.
|
// same index is lnwire.FailCodeNone.
|
||||||
decodeResps, sphinxErr := l.cfg.DecodeHopIterators(
|
decodeResps, sphinxErr := l.cfg.DecodeHopIterators(
|
||||||
fwdPkg.ID(), decodeReqs,
|
fwdPkg.ID(), decodeReqs, reforward,
|
||||||
)
|
)
|
||||||
if sphinxErr != nil {
|
if sphinxErr != nil {
|
||||||
l.failf(LinkFailureError{code: ErrInternalError},
|
l.failf(LinkFailureError{code: ErrInternalError},
|
||||||
@@ -3779,23 +3816,10 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||||||
|
|
||||||
var switchPackets []*htlcPacket
|
var switchPackets []*htlcPacket
|
||||||
|
|
||||||
for i, update := range fwdPkg.Adds {
|
for i, update := range unackedAdds {
|
||||||
idx := uint16(i)
|
idx := uint16(i)
|
||||||
|
|
||||||
//nolint:forcetypeassert
|
|
||||||
add := *update.UpdateMsg.(*lnwire.UpdateAddHTLC)
|
|
||||||
sourceRef := fwdPkg.SourceRef(idx)
|
sourceRef := fwdPkg.SourceRef(idx)
|
||||||
|
add := *update
|
||||||
if fwdPkg.State == channeldb.FwdStateProcessed &&
|
|
||||||
fwdPkg.AckFilter.Contains(idx) {
|
|
||||||
|
|
||||||
// If this index is already found in the ack filter,
|
|
||||||
// the response to this forwarding decision has already
|
|
||||||
// been committed by one of our commitment txns. ADDs
|
|
||||||
// in this state are waiting for the rest of the fwding
|
|
||||||
// package to get acked before being garbage collected.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// An incoming HTLC add has been full-locked in. As a result we
|
// An incoming HTLC add has been full-locked in. As a result we
|
||||||
// can now examine the forwarding details of the HTLC, and the
|
// can now examine the forwarding details of the HTLC, and the
|
||||||
@@ -3815,8 +3839,10 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||||||
add.ID, failureCode, add.OnionBlob, &sourceRef,
|
add.ID, failureCode, add.OnionBlob, &sourceRef,
|
||||||
)
|
)
|
||||||
|
|
||||||
l.log.Errorf("unable to decode onion hop "+
|
l.log.Errorf("unable to decode onion hop iterator "+
|
||||||
"iterator: %v", failureCode)
|
"for htlc(id=%v, hash=%x): %v", add.ID,
|
||||||
|
add.PaymentHash, failureCode)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4120,17 +4146,15 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
replay := fwdPkg.State != channeldb.FwdStateLockedIn
|
l.log.Debugf("forwarding %d packets to switch: reforward=%v",
|
||||||
|
len(switchPackets), reforward)
|
||||||
l.log.Debugf("forwarding %d packets to switch: replay=%v",
|
|
||||||
len(switchPackets), replay)
|
|
||||||
|
|
||||||
// NOTE: This call is made synchronous so that we ensure all circuits
|
// NOTE: This call is made synchronous so that we ensure all circuits
|
||||||
// are committed in the exact order that they are processed in the link.
|
// are committed in the exact order that they are processed in the link.
|
||||||
// Failing to do this could cause reorderings/gaps in the range of
|
// Failing to do this could cause reorderings/gaps in the range of
|
||||||
// opened circuits, which violates assumptions made by the circuit
|
// opened circuits, which violates assumptions made by the circuit
|
||||||
// trimming.
|
// trimming.
|
||||||
l.forwardBatch(replay, switchPackets...)
|
l.forwardBatch(reforward, switchPackets...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// experimentalEndorsement returns the value to set for our outgoing
|
// experimentalEndorsement returns the value to set for our outgoing
|
||||||
|
|||||||
+1
-1
@@ -522,7 +522,7 @@ func (p *mockIteratorDecoder) DecodeHopIterator(r io.Reader, rHash []byte,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *mockIteratorDecoder) DecodeHopIterators(id []byte,
|
func (p *mockIteratorDecoder) DecodeHopIterators(id []byte,
|
||||||
reqs []hop.DecodeHopIteratorRequest) (
|
reqs []hop.DecodeHopIteratorRequest, _ bool) (
|
||||||
[]hop.DecodeHopIteratorResponse, error) {
|
[]hop.DecodeHopIteratorResponse, error) {
|
||||||
|
|
||||||
idHash := sha256.Sum256(id)
|
idHash := sha256.Sum256(id)
|
||||||
|
|||||||
Reference in new issue
Block a user