Skip to content

Commit 2f1205a

Browse files
committed
sweep: start tracking inputs spent by unknown tx
This commit adds a new field `InputsSpent` to the `BumpResult` so they can be used to track inputs spent by txns not recoginized by the fee bumper.
1 parent 388183e commit 2f1205a

File tree

2 files changed

+83
-7
lines changed

2 files changed

+83
-7
lines changed

sweep/fee_bumper.go

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@ type BumpResult struct {
273273
// Err is the error that occurred during the broadcast.
274274
Err error
275275

276+
// SpentInputs are the inputs spent by another tx which caused the
277+
// current tx to be failed.
278+
SpentInputs map[wire.OutPoint]*wire.MsgTx
279+
276280
// requestID is the ID of the request that created this record.
277281
requestID uint64
278282
}
@@ -812,6 +816,10 @@ type monitorRecord struct {
812816

813817
// outpointToTxIndex is a map of outpoint to tx index.
814818
outpointToTxIndex map[wire.OutPoint]int
819+
820+
// spentInputs are the inputs spent by another tx which caused the
821+
// current tx failed.
822+
spentInputs map[wire.OutPoint]*wire.MsgTx
815823
}
816824

817825
// Start starts the publisher by subscribing to block epoch updates and kicking
@@ -910,6 +918,9 @@ func (t *TxPublisher) processRecords() {
910918
// If the any of the inputs has been spent, the record will be
911919
// marked as failed or confirmed.
912920
if len(spends) != 0 {
921+
// Attach the spending txns.
922+
r.spentInputs = spends
923+
913924
// When tx is nil, it means we haven't tried the initial
914925
// broadcast yet the input is already spent. This could
915926
// happen when the node shuts down, a previous sweeping
@@ -1159,16 +1170,71 @@ func (t *TxPublisher) handleUnknownSpent(r *monitorRecord) {
11591170
"bumper, failing it now:\n%v", r.requestID,
11601171
inputTypeSummary(r.req.Inputs))
11611172

1162-
// Create a result that will be sent to the resultChan which is
1163-
// listened by the caller.
1173+
// Create a result that will be sent to the resultChan which is listened
1174+
// by the caller.
11641175
result := &BumpResult{
1165-
Event: TxUnknownSpend,
1166-
Tx: r.tx,
1167-
requestID: r.requestID,
1168-
Err: ErrUnknownSpent,
1176+
Event: TxUnknownSpend,
1177+
Tx: r.tx,
1178+
requestID: r.requestID,
1179+
Err: ErrUnknownSpent,
1180+
SpentInputs: r.spentInputs,
11691181
}
11701182

1171-
// Notify that this tx is confirmed and remove the record from the map.
1183+
// Get the fee function, which will be used to decided the next fee rate
1184+
// to use if the sweeper decides to retry sweeping this input.
1185+
feeFunc := r.feeFunction
1186+
1187+
// When the record is failed before the initial broadcast is attempted,
1188+
// it will have a nil fee func. In this case, we'll create the fee func
1189+
// here.
1190+
//
1191+
// NOTE: Since the current record is failed and will be deleted, we
1192+
// don't need to update the record on this fee function. We only need
1193+
// the fee rate data so the sweeper can pick up where we left off.
1194+
if feeFunc == nil {
1195+
f, err := t.initializeFeeFunction(r.req)
1196+
// TODO(yy): The only error we would receive here is when the
1197+
// pkScript is not recognized by the weightEstimator. What we
1198+
// should do instead is to check the pkScript immediately after
1199+
// receiving a sweep request so we don't need to check it again,
1200+
// which will also save us from error checking from several
1201+
// callsites.
1202+
if err != nil {
1203+
log.Errorf("Failed to create fee func for record %v: "+
1204+
"%v", r.requestID, err)
1205+
1206+
// Overwrite the event and error so the sweeper will
1207+
// remove this input.
1208+
result.Event = TxFatal
1209+
result.Err = err
1210+
1211+
// Notify the sweeper about this result in the end.
1212+
t.handleResult(result)
1213+
1214+
return
1215+
}
1216+
1217+
feeFunc = f
1218+
}
1219+
1220+
// Since the sweeping tx has been replaced by another party's tx, we
1221+
// missed this block window to increase its fee rate. To make sure the
1222+
// fee rate stays in the initial line, we now ask the fee function to
1223+
// give us the next fee rate as if the sweeping tx were RBFed. This new
1224+
// fee rate will be used as the starting fee rate if the upper system
1225+
// decides to continue sweeping the rest of the inputs.
1226+
_, err := feeFunc.Increment()
1227+
if err != nil {
1228+
// The fee function has reached its max position - nothing we
1229+
// can do here other than letting the user increase the budget.
1230+
log.Errorf("Failed to calculate the next fee rate for "+
1231+
"Record(%v): %v", r.requestID, err)
1232+
}
1233+
1234+
// Attach the new fee rate to be used for the next sweeping attempt.
1235+
result.FeeRate = feeFunc.FeeRate()
1236+
1237+
// Notify the sweeper about this result in the end.
11721238
t.handleResult(result)
11731239
}
11741240

sweep/fee_bumper_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,6 +1772,13 @@ func TestProcessRecordsSpent(t *testing.T) {
17721772
tp.subscriberChans.Store(requestID, subscriber)
17731773
tp.records.Store(requestID, recordConfirmed)
17741774

1775+
// Mock the fee function to increase feerate.
1776+
m.feeFunc.On("Increment").Return(true, nil).Once()
1777+
1778+
// Create a test feerate and return it from the mock fee function.
1779+
feerate := chainfee.SatPerKWeight(1000)
1780+
m.feeFunc.On("FeeRate").Return(feerate)
1781+
17751782
// Call processRecords and expect the results are notified back.
17761783
tp.processRecords()
17771784

@@ -1785,6 +1792,9 @@ func TestProcessRecordsSpent(t *testing.T) {
17851792
require.Equal(t, TxUnknownSpend, result.Event)
17861793
require.Equal(t, tx, result.Tx)
17871794

1795+
// We expect the fee rate to be updated.
1796+
require.Equal(t, feerate, result.FeeRate)
1797+
17881798
// No error should be set.
17891799
require.ErrorIs(t, result.Err, ErrUnknownSpent)
17901800
require.Equal(t, requestID, result.requestID)

0 commit comments

Comments
 (0)