Merge pull request #9446 from yyforyongyu/yy-prepare-fee-replace
sweeper: rename `Failed` to `Fatal` and minor refactor
This commit is contained in:
5 files changed
+266
-158
No files matched your search
@@ -87,6 +87,7 @@
|
|||||||
## Functional Enhancements
|
## Functional Enhancements
|
||||||
* [Add ability](https://github.com/lightningnetwork/lnd/pull/8998) to paginate
|
* [Add ability](https://github.com/lightningnetwork/lnd/pull/8998) to paginate
|
||||||
wallet transactions.
|
wallet transactions.
|
||||||
|
|
||||||
## RPC Additions
|
## RPC Additions
|
||||||
|
|
||||||
* [Add a new rpc endpoint](https://github.com/lightningnetwork/lnd/pull/8843)
|
* [Add a new rpc endpoint](https://github.com/lightningnetwork/lnd/pull/8843)
|
||||||
@@ -317,6 +318,10 @@ The underlying functionality between those two options remain the same.
|
|||||||
StateMachine](https://github.com/lightningnetwork/lnd/pull/9342) to use the
|
StateMachine](https://github.com/lightningnetwork/lnd/pull/9342) to use the
|
||||||
new GoroutineManager API along with structured logging.
|
new GoroutineManager API along with structured logging.
|
||||||
|
|
||||||
|
* A minor [refactor](https://github.com/lightningnetwork/lnd/pull/9446) is done
|
||||||
|
to the sweeper to improve code quality, with a renaming of the internal state
|
||||||
|
(`Failed` -> `Fatal`) used by the inputs tracked in the sweeper.
|
||||||
|
|
||||||
## Tooling and Documentation
|
## Tooling and Documentation
|
||||||
|
|
||||||
* [Improved `lncli create` command help text](https://github.com/lightningnetwork/lnd/pull/9077)
|
* [Improved `lncli create` command help text](https://github.com/lightningnetwork/lnd/pull/9077)
|
||||||
|
|||||||
+79
-85
@@ -410,34 +410,50 @@ func (t *TxPublisher) Broadcast(req *BumpRequest) <-chan *BumpResult {
|
|||||||
lnutils.SpewLogClosure(req))
|
lnutils.SpewLogClosure(req))
|
||||||
|
|
||||||
// Store the request.
|
// Store the request.
|
||||||
requestID, record := t.storeInitialRecord(req)
|
record := t.storeInitialRecord(req)
|
||||||
|
|
||||||
// Create a chan to send the result to the caller.
|
// Create a chan to send the result to the caller.
|
||||||
subscriber := make(chan *BumpResult, 1)
|
subscriber := make(chan *BumpResult, 1)
|
||||||
t.subscriberChans.Store(requestID, subscriber)
|
t.subscriberChans.Store(record.requestID, subscriber)
|
||||||
|
|
||||||
// Publish the tx immediately if specified.
|
// Publish the tx immediately if specified.
|
||||||
if req.Immediate {
|
if req.Immediate {
|
||||||
t.handleInitialBroadcast(record, requestID)
|
t.handleInitialBroadcast(record)
|
||||||
}
|
}
|
||||||
|
|
||||||
return subscriber
|
return subscriber
|
||||||
}
|
}
|
||||||
|
|
||||||
// storeInitialRecord initializes a monitor record and saves it in the map.
|
// storeInitialRecord initializes a monitor record and saves it in the map.
|
||||||
func (t *TxPublisher) storeInitialRecord(req *BumpRequest) (
|
func (t *TxPublisher) storeInitialRecord(req *BumpRequest) *monitorRecord {
|
||||||
uint64, *monitorRecord) {
|
|
||||||
|
|
||||||
// Increase the request counter.
|
// Increase the request counter.
|
||||||
//
|
//
|
||||||
// NOTE: this is the only place where we increase the counter.
|
// NOTE: this is the only place where we increase the counter.
|
||||||
requestID := t.requestCounter.Add(1)
|
requestID := t.requestCounter.Add(1)
|
||||||
|
|
||||||
// Register the record.
|
// Register the record.
|
||||||
record := &monitorRecord{req: req}
|
record := &monitorRecord{
|
||||||
|
requestID: requestID,
|
||||||
|
req: req,
|
||||||
|
}
|
||||||
t.records.Store(requestID, record)
|
t.records.Store(requestID, record)
|
||||||
|
|
||||||
return requestID, record
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateRecord updates the given record's tx and fee, and saves it in the
|
||||||
|
// records map.
|
||||||
|
func (t *TxPublisher) updateRecord(r *monitorRecord,
|
||||||
|
sweepCtx *sweepTxCtx) *monitorRecord {
|
||||||
|
|
||||||
|
r.tx = sweepCtx.tx
|
||||||
|
r.fee = sweepCtx.fee
|
||||||
|
r.outpointToTxIndex = sweepCtx.outpointToTxIndex
|
||||||
|
|
||||||
|
// Register the record.
|
||||||
|
t.records.Store(r.requestID, r)
|
||||||
|
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: part of the `chainio.Consumer` interface.
|
// NOTE: part of the `chainio.Consumer` interface.
|
||||||
@@ -447,21 +463,30 @@ func (t *TxPublisher) Name() string {
|
|||||||
|
|
||||||
// initializeTx initializes a fee function and creates an RBF-compliant tx. If
|
// initializeTx initializes a fee function and creates an RBF-compliant tx. If
|
||||||
// succeeded, the initial tx is stored in the records map.
|
// succeeded, the initial tx is stored in the records map.
|
||||||
func (t *TxPublisher) initializeTx(requestID uint64, req *BumpRequest) error {
|
func (t *TxPublisher) initializeTx(r *monitorRecord) (*monitorRecord, error) {
|
||||||
// Create a fee bumping algorithm to be used for future RBF.
|
// Create a fee bumping algorithm to be used for future RBF.
|
||||||
feeAlgo, err := t.initializeFeeFunction(req)
|
feeAlgo, err := t.initializeFeeFunction(r.req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("init fee function: %w", err)
|
return nil, fmt.Errorf("init fee function: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attach the newly created fee function.
|
||||||
|
//
|
||||||
|
// TODO(yy): current we'd initialize a monitorRecord before creating the
|
||||||
|
// fee function, while we could instead create the fee function first
|
||||||
|
// then save it to the record. To make this happen we need to change the
|
||||||
|
// conf target calculation below since we would be initializing the fee
|
||||||
|
// function one block before.
|
||||||
|
r.feeFunction = feeAlgo
|
||||||
|
|
||||||
// Create the initial tx to be broadcasted. This tx is guaranteed to
|
// Create the initial tx to be broadcasted. This tx is guaranteed to
|
||||||
// comply with the RBF restrictions.
|
// comply with the RBF restrictions.
|
||||||
err = t.createRBFCompliantTx(requestID, req, feeAlgo)
|
record, err := t.createRBFCompliantTx(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create RBF-compliant tx: %w", err)
|
return nil, fmt.Errorf("create RBF-compliant tx: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// initializeFeeFunction initializes a fee function to be used for this request
|
// initializeFeeFunction initializes a fee function to be used for this request
|
||||||
@@ -497,29 +522,28 @@ func (t *TxPublisher) initializeFeeFunction(
|
|||||||
// so by creating a tx, validate it using `TestMempoolAccept`, and bump its fee
|
// so by creating a tx, validate it using `TestMempoolAccept`, and bump its fee
|
||||||
// and redo the process until the tx is valid, or return an error when non-RBF
|
// and redo the process until the tx is valid, or return an error when non-RBF
|
||||||
// related errors occur or the budget has been used up.
|
// related errors occur or the budget has been used up.
|
||||||
func (t *TxPublisher) createRBFCompliantTx(requestID uint64, req *BumpRequest,
|
func (t *TxPublisher) createRBFCompliantTx(
|
||||||
f FeeFunction) error {
|
r *monitorRecord) (*monitorRecord, error) {
|
||||||
|
|
||||||
|
f := r.feeFunction
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Create a new tx with the given fee rate and check its
|
// Create a new tx with the given fee rate and check its
|
||||||
// mempool acceptance.
|
// mempool acceptance.
|
||||||
sweepCtx, err := t.createAndCheckTx(req, f)
|
sweepCtx, err := t.createAndCheckTx(r.req, f)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
// The tx is valid, store it.
|
// The tx is valid, store it.
|
||||||
t.storeRecord(
|
record := t.updateRecord(r, sweepCtx)
|
||||||
requestID, sweepCtx.tx, req, f, sweepCtx.fee,
|
|
||||||
sweepCtx.outpointToTxIndex,
|
|
||||||
)
|
|
||||||
|
|
||||||
log.Infof("Created initial sweep tx=%v for %v inputs: "+
|
log.Infof("Created initial sweep tx=%v for %v inputs: "+
|
||||||
"feerate=%v, fee=%v, inputs:\n%v",
|
"feerate=%v, fee=%v, inputs:\n%v",
|
||||||
sweepCtx.tx.TxHash(), len(req.Inputs),
|
sweepCtx.tx.TxHash(), len(r.req.Inputs),
|
||||||
f.FeeRate(), sweepCtx.fee,
|
f.FeeRate(), sweepCtx.fee,
|
||||||
inputTypeSummary(req.Inputs))
|
inputTypeSummary(r.req.Inputs))
|
||||||
|
|
||||||
return nil
|
return record, nil
|
||||||
|
|
||||||
// If the error indicates the fees paid is not enough, we will
|
// If the error indicates the fees paid is not enough, we will
|
||||||
// ask the fee function to increase the fee rate and retry.
|
// ask the fee function to increase the fee rate and retry.
|
||||||
@@ -550,7 +574,7 @@ func (t *TxPublisher) createRBFCompliantTx(requestID uint64, req *BumpRequest,
|
|||||||
// cluster these inputs differetly.
|
// cluster these inputs differetly.
|
||||||
increased, err = f.Increment()
|
increased, err = f.Increment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,26 +584,11 @@ func (t *TxPublisher) createRBFCompliantTx(requestID uint64, req *BumpRequest,
|
|||||||
// mempool acceptance.
|
// mempool acceptance.
|
||||||
default:
|
default:
|
||||||
log.Debugf("Failed to create RBF-compliant tx: %v", err)
|
log.Debugf("Failed to create RBF-compliant tx: %v", err)
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// storeRecord stores the given record in the records map.
|
|
||||||
func (t *TxPublisher) storeRecord(requestID uint64, tx *wire.MsgTx,
|
|
||||||
req *BumpRequest, f FeeFunction, fee btcutil.Amount,
|
|
||||||
outpointToTxIndex map[wire.OutPoint]int) {
|
|
||||||
|
|
||||||
// Register the record.
|
|
||||||
t.records.Store(requestID, &monitorRecord{
|
|
||||||
tx: tx,
|
|
||||||
req: req,
|
|
||||||
feeFunction: f,
|
|
||||||
fee: fee,
|
|
||||||
outpointToTxIndex: outpointToTxIndex,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// createAndCheckTx creates a tx based on the given inputs, change output
|
// createAndCheckTx creates a tx based on the given inputs, change output
|
||||||
// script, and the fee rate. In addition, it validates the tx's mempool
|
// script, and the fee rate. In addition, it validates the tx's mempool
|
||||||
// acceptance before returning a tx that can be published directly, along with
|
// acceptance before returning a tx that can be published directly, along with
|
||||||
@@ -638,13 +647,7 @@ func (t *TxPublisher) createAndCheckTx(req *BumpRequest,
|
|||||||
// the event channel to the record. Any broadcast-related errors will not be
|
// the event channel to the record. Any broadcast-related errors will not be
|
||||||
// returned here, instead, they will be put inside the `BumpResult` and
|
// returned here, instead, they will be put inside the `BumpResult` and
|
||||||
// returned to the caller.
|
// returned to the caller.
|
||||||
func (t *TxPublisher) broadcast(requestID uint64) (*BumpResult, error) {
|
func (t *TxPublisher) broadcast(record *monitorRecord) (*BumpResult, error) {
|
||||||
// Get the record being monitored.
|
|
||||||
record, ok := t.records.Load(requestID)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("tx record %v not found", requestID)
|
|
||||||
}
|
|
||||||
|
|
||||||
txid := record.tx.TxHash()
|
txid := record.tx.TxHash()
|
||||||
|
|
||||||
tx := record.tx
|
tx := record.tx
|
||||||
@@ -691,7 +694,7 @@ func (t *TxPublisher) broadcast(requestID uint64) (*BumpResult, error) {
|
|||||||
Fee: record.fee,
|
Fee: record.fee,
|
||||||
FeeRate: record.feeFunction.FeeRate(),
|
FeeRate: record.feeFunction.FeeRate(),
|
||||||
Err: err,
|
Err: err,
|
||||||
requestID: requestID,
|
requestID: record.requestID,
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -777,6 +780,9 @@ func (t *TxPublisher) handleResult(result *BumpResult) {
|
|||||||
// monitorRecord is used to keep track of the tx being monitored by the
|
// monitorRecord is used to keep track of the tx being monitored by the
|
||||||
// publisher internally.
|
// publisher internally.
|
||||||
type monitorRecord struct {
|
type monitorRecord struct {
|
||||||
|
// requestID is the ID of the request that created this record.
|
||||||
|
requestID uint64
|
||||||
|
|
||||||
// tx is the tx being monitored.
|
// tx is the tx being monitored.
|
||||||
tx *wire.MsgTx
|
tx *wire.MsgTx
|
||||||
|
|
||||||
@@ -919,35 +925,35 @@ func (t *TxPublisher) processRecords() {
|
|||||||
t.records.ForEach(visitor)
|
t.records.ForEach(visitor)
|
||||||
|
|
||||||
// Handle the initial broadcast.
|
// Handle the initial broadcast.
|
||||||
for requestID, r := range initialRecords {
|
for _, r := range initialRecords {
|
||||||
t.handleInitialBroadcast(r, requestID)
|
t.handleInitialBroadcast(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For records that are confirmed, we'll notify the caller about this
|
// For records that are confirmed, we'll notify the caller about this
|
||||||
// result.
|
// result.
|
||||||
for requestID, r := range confirmedRecords {
|
for _, r := range confirmedRecords {
|
||||||
log.Debugf("Tx=%v is confirmed", r.tx.TxHash())
|
log.Debugf("Tx=%v is confirmed", r.tx.TxHash())
|
||||||
t.wg.Add(1)
|
t.wg.Add(1)
|
||||||
go t.handleTxConfirmed(r, requestID)
|
go t.handleTxConfirmed(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current height to be used in the following goroutines.
|
// Get the current height to be used in the following goroutines.
|
||||||
currentHeight := t.currentHeight.Load()
|
currentHeight := t.currentHeight.Load()
|
||||||
|
|
||||||
// For records that are not confirmed, we perform a fee bump if needed.
|
// For records that are not confirmed, we perform a fee bump if needed.
|
||||||
for requestID, r := range feeBumpRecords {
|
for _, r := range feeBumpRecords {
|
||||||
log.Debugf("Attempting to fee bump Tx=%v", r.tx.TxHash())
|
log.Debugf("Attempting to fee bump Tx=%v", r.tx.TxHash())
|
||||||
t.wg.Add(1)
|
t.wg.Add(1)
|
||||||
go t.handleFeeBumpTx(requestID, r, currentHeight)
|
go t.handleFeeBumpTx(r, currentHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For records that are failed, we'll notify the caller about this
|
// For records that are failed, we'll notify the caller about this
|
||||||
// result.
|
// result.
|
||||||
for requestID, r := range failedRecords {
|
for _, r := range failedRecords {
|
||||||
log.Debugf("Tx=%v has inputs been spent by a third party, "+
|
log.Debugf("Tx=%v has inputs been spent by a third party, "+
|
||||||
"failing it now", r.tx.TxHash())
|
"failing it now", r.tx.TxHash())
|
||||||
t.wg.Add(1)
|
t.wg.Add(1)
|
||||||
go t.handleThirdPartySpent(r, requestID)
|
go t.handleThirdPartySpent(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,7 +961,7 @@ func (t *TxPublisher) processRecords() {
|
|||||||
// notify the subscriber then remove the record from the maps .
|
// notify the subscriber then remove the record from the maps .
|
||||||
//
|
//
|
||||||
// NOTE: Must be run as a goroutine to avoid blocking on sending the result.
|
// NOTE: Must be run as a goroutine to avoid blocking on sending the result.
|
||||||
func (t *TxPublisher) handleTxConfirmed(r *monitorRecord, requestID uint64) {
|
func (t *TxPublisher) handleTxConfirmed(r *monitorRecord) {
|
||||||
defer t.wg.Done()
|
defer t.wg.Done()
|
||||||
|
|
||||||
// Create a result that will be sent to the resultChan which is
|
// Create a result that will be sent to the resultChan which is
|
||||||
@@ -963,7 +969,7 @@ func (t *TxPublisher) handleTxConfirmed(r *monitorRecord, requestID uint64) {
|
|||||||
result := &BumpResult{
|
result := &BumpResult{
|
||||||
Event: TxConfirmed,
|
Event: TxConfirmed,
|
||||||
Tx: r.tx,
|
Tx: r.tx,
|
||||||
requestID: requestID,
|
requestID: r.requestID,
|
||||||
Fee: r.fee,
|
Fee: r.fee,
|
||||||
FeeRate: r.feeFunction.FeeRate(),
|
FeeRate: r.feeFunction.FeeRate(),
|
||||||
}
|
}
|
||||||
@@ -1021,10 +1027,8 @@ func (t *TxPublisher) handleInitialTxError(requestID uint64, err error) {
|
|||||||
// 1. init a fee function based on the given strategy.
|
// 1. init a fee function based on the given strategy.
|
||||||
// 2. create an RBF-compliant tx and monitor it for confirmation.
|
// 2. create an RBF-compliant tx and monitor it for confirmation.
|
||||||
// 3. notify the initial broadcast result back to the caller.
|
// 3. notify the initial broadcast result back to the caller.
|
||||||
func (t *TxPublisher) handleInitialBroadcast(r *monitorRecord,
|
func (t *TxPublisher) handleInitialBroadcast(r *monitorRecord) {
|
||||||
requestID uint64) {
|
log.Debugf("Initial broadcast for requestID=%v", r.requestID)
|
||||||
|
|
||||||
log.Debugf("Initial broadcast for requestID=%v", requestID)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
result *BumpResult
|
result *BumpResult
|
||||||
@@ -1035,18 +1039,18 @@ func (t *TxPublisher) handleInitialBroadcast(r *monitorRecord,
|
|||||||
// RBF rules.
|
// RBF rules.
|
||||||
//
|
//
|
||||||
// Create the initial tx to be broadcasted.
|
// Create the initial tx to be broadcasted.
|
||||||
err = t.initializeTx(requestID, r.req)
|
record, err := t.initializeTx(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Initial broadcast failed: %v", err)
|
log.Errorf("Initial broadcast failed: %v", err)
|
||||||
|
|
||||||
// We now handle the initialization error and exit.
|
// We now handle the initialization error and exit.
|
||||||
t.handleInitialTxError(requestID, err)
|
t.handleInitialTxError(r.requestID, err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Successfully created the first tx, now broadcast it.
|
// Successfully created the first tx, now broadcast it.
|
||||||
result, err = t.broadcast(requestID)
|
result, err = t.broadcast(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The broadcast failed, which can only happen if the tx record
|
// The broadcast failed, which can only happen if the tx record
|
||||||
// cannot be found or the aux sweeper returns an error. In
|
// cannot be found or the aux sweeper returns an error. In
|
||||||
@@ -1055,7 +1059,7 @@ func (t *TxPublisher) handleInitialBroadcast(r *monitorRecord,
|
|||||||
result = &BumpResult{
|
result = &BumpResult{
|
||||||
Event: TxFailed,
|
Event: TxFailed,
|
||||||
Err: err,
|
Err: err,
|
||||||
requestID: requestID,
|
requestID: r.requestID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1066,9 +1070,7 @@ func (t *TxPublisher) handleInitialBroadcast(r *monitorRecord,
|
|||||||
// attempt to bump the fee of the tx.
|
// attempt to bump the fee of the tx.
|
||||||
//
|
//
|
||||||
// NOTE: Must be run as a goroutine to avoid blocking on sending the result.
|
// NOTE: Must be run as a goroutine to avoid blocking on sending the result.
|
||||||
func (t *TxPublisher) handleFeeBumpTx(requestID uint64, r *monitorRecord,
|
func (t *TxPublisher) handleFeeBumpTx(r *monitorRecord, currentHeight int32) {
|
||||||
currentHeight int32) {
|
|
||||||
|
|
||||||
defer t.wg.Done()
|
defer t.wg.Done()
|
||||||
|
|
||||||
oldTxid := r.tx.TxHash()
|
oldTxid := r.tx.TxHash()
|
||||||
@@ -1099,7 +1101,7 @@ func (t *TxPublisher) handleFeeBumpTx(requestID uint64, r *monitorRecord,
|
|||||||
|
|
||||||
// The fee function now has a new fee rate, we will use it to bump the
|
// The fee function now has a new fee rate, we will use it to bump the
|
||||||
// fee of the tx.
|
// fee of the tx.
|
||||||
resultOpt := t.createAndPublishTx(requestID, r)
|
resultOpt := t.createAndPublishTx(r)
|
||||||
|
|
||||||
// If there's a result, we will notify the caller about the result.
|
// If there's a result, we will notify the caller about the result.
|
||||||
resultOpt.WhenSome(func(result BumpResult) {
|
resultOpt.WhenSome(func(result BumpResult) {
|
||||||
@@ -1113,9 +1115,7 @@ func (t *TxPublisher) handleFeeBumpTx(requestID uint64, r *monitorRecord,
|
|||||||
// and send a TxFailed event to the subscriber.
|
// and send a TxFailed event to the subscriber.
|
||||||
//
|
//
|
||||||
// NOTE: Must be run as a goroutine to avoid blocking on sending the result.
|
// NOTE: Must be run as a goroutine to avoid blocking on sending the result.
|
||||||
func (t *TxPublisher) handleThirdPartySpent(r *monitorRecord,
|
func (t *TxPublisher) handleThirdPartySpent(r *monitorRecord) {
|
||||||
requestID uint64) {
|
|
||||||
|
|
||||||
defer t.wg.Done()
|
defer t.wg.Done()
|
||||||
|
|
||||||
// Create a result that will be sent to the resultChan which is
|
// Create a result that will be sent to the resultChan which is
|
||||||
@@ -1127,7 +1127,7 @@ func (t *TxPublisher) handleThirdPartySpent(r *monitorRecord,
|
|||||||
result := &BumpResult{
|
result := &BumpResult{
|
||||||
Event: TxFailed,
|
Event: TxFailed,
|
||||||
Tx: r.tx,
|
Tx: r.tx,
|
||||||
requestID: requestID,
|
requestID: r.requestID,
|
||||||
Err: ErrThirdPartySpent,
|
Err: ErrThirdPartySpent,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1138,7 +1138,7 @@ func (t *TxPublisher) handleThirdPartySpent(r *monitorRecord,
|
|||||||
// createAndPublishTx creates a new tx with a higher fee rate and publishes it
|
// createAndPublishTx creates a new tx with a higher fee rate and publishes it
|
||||||
// to the network. It will update the record with the new tx and fee rate if
|
// to the network. It will update the record with the new tx and fee rate if
|
||||||
// successfully created, and return the result when published successfully.
|
// successfully created, and return the result when published successfully.
|
||||||
func (t *TxPublisher) createAndPublishTx(requestID uint64,
|
func (t *TxPublisher) createAndPublishTx(
|
||||||
r *monitorRecord) fn.Option[BumpResult] {
|
r *monitorRecord) fn.Option[BumpResult] {
|
||||||
|
|
||||||
// Fetch the old tx.
|
// Fetch the old tx.
|
||||||
@@ -1189,22 +1189,16 @@ func (t *TxPublisher) createAndPublishTx(requestID uint64,
|
|||||||
Event: TxFailed,
|
Event: TxFailed,
|
||||||
Tx: oldTx,
|
Tx: oldTx,
|
||||||
Err: err,
|
Err: err,
|
||||||
requestID: requestID,
|
requestID: r.requestID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// The tx has been created without any errors, we now register a new
|
// The tx has been created without any errors, we now register a new
|
||||||
// record by overwriting the same requestID.
|
// record by overwriting the same requestID.
|
||||||
t.records.Store(requestID, &monitorRecord{
|
record := t.updateRecord(r, sweepCtx)
|
||||||
tx: sweepCtx.tx,
|
|
||||||
req: r.req,
|
|
||||||
feeFunction: r.feeFunction,
|
|
||||||
fee: sweepCtx.fee,
|
|
||||||
outpointToTxIndex: sweepCtx.outpointToTxIndex,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Attempt to broadcast this new tx.
|
// Attempt to broadcast this new tx.
|
||||||
result, err := t.broadcast(requestID)
|
result, err := t.broadcast(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Infof("Failed to broadcast replacement tx %v: %v",
|
log.Infof("Failed to broadcast replacement tx %v: %v",
|
||||||
sweepCtx.tx.TxHash(), err)
|
sweepCtx.tx.TxHash(), err)
|
||||||
|
|||||||
+144
-38
@@ -313,9 +313,9 @@ func TestInitializeFeeFunction(t *testing.T) {
|
|||||||
require.Equal(t, feerate, f.FeeRate())
|
require.Equal(t, feerate, f.FeeRate())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestStoreRecord correctly increases the request counter and saves the
|
// TestUpdateRecord correctly updates the fields fee and tx, and saves the
|
||||||
// record.
|
// record.
|
||||||
func TestStoreRecord(t *testing.T) {
|
func TestUpdateRecord(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Create a test input.
|
// Create a test input.
|
||||||
@@ -351,8 +351,22 @@ func TestStoreRecord(t *testing.T) {
|
|||||||
op: 0,
|
op: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a sweepTxCtx.
|
||||||
|
sweepCtx := &sweepTxCtx{
|
||||||
|
tx: tx,
|
||||||
|
fee: fee,
|
||||||
|
outpointToTxIndex: utxoIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a test record.
|
||||||
|
record := &monitorRecord{
|
||||||
|
requestID: initialCounter,
|
||||||
|
req: req,
|
||||||
|
feeFunction: feeFunc,
|
||||||
|
}
|
||||||
|
|
||||||
// Call the method under test.
|
// Call the method under test.
|
||||||
tp.storeRecord(initialCounter, tx, req, feeFunc, fee, utxoIndex)
|
tp.updateRecord(record, sweepCtx)
|
||||||
|
|
||||||
// Read the saved record and compare.
|
// Read the saved record and compare.
|
||||||
record, ok := tp.records.Load(initialCounter)
|
record, ok := tp.records.Load(initialCounter)
|
||||||
@@ -657,14 +671,31 @@ func TestCreateRBFCompliantTx(t *testing.T) {
|
|||||||
tc := tc
|
tc := tc
|
||||||
|
|
||||||
rid := requestCounter.Add(1)
|
rid := requestCounter.Add(1)
|
||||||
|
|
||||||
|
// Create a test record.
|
||||||
|
record := &monitorRecord{
|
||||||
|
requestID: rid,
|
||||||
|
req: req,
|
||||||
|
feeFunction: m.feeFunc,
|
||||||
|
}
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
tc.setupMock()
|
tc.setupMock()
|
||||||
|
|
||||||
// Call the method under test.
|
// Call the method under test.
|
||||||
err := tp.createRBFCompliantTx(rid, req, m.feeFunc)
|
rec, err := tp.createRBFCompliantTx(record)
|
||||||
|
|
||||||
// Check the result is as expected.
|
// Check the result is as expected.
|
||||||
require.ErrorIs(t, err, tc.expectedErr)
|
require.ErrorIs(t, err, tc.expectedErr)
|
||||||
|
|
||||||
|
if tc.expectedErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert the returned record has the following fields
|
||||||
|
// populated.
|
||||||
|
require.NotEmpty(t, rec.tx)
|
||||||
|
require.NotEmpty(t, rec.fee)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -698,13 +729,21 @@ func TestTxPublisherBroadcast(t *testing.T) {
|
|||||||
// Create a testing record and put it in the map.
|
// Create a testing record and put it in the map.
|
||||||
fee := btcutil.Amount(1000)
|
fee := btcutil.Amount(1000)
|
||||||
requestID := uint64(1)
|
requestID := uint64(1)
|
||||||
tp.storeRecord(requestID, tx, req, m.feeFunc, fee, utxoIndex)
|
|
||||||
|
|
||||||
// Quickly check when the requestID cannot be found, an error is
|
// Create a sweepTxCtx.
|
||||||
// returned.
|
sweepCtx := &sweepTxCtx{
|
||||||
result, err := tp.broadcast(uint64(1000))
|
tx: tx,
|
||||||
require.Error(t, err)
|
fee: fee,
|
||||||
require.Nil(t, result)
|
outpointToTxIndex: utxoIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a test record.
|
||||||
|
record := &monitorRecord{
|
||||||
|
requestID: requestID,
|
||||||
|
req: req,
|
||||||
|
feeFunction: m.feeFunc,
|
||||||
|
}
|
||||||
|
rec := tp.updateRecord(record, sweepCtx)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -759,7 +798,7 @@ func TestTxPublisherBroadcast(t *testing.T) {
|
|||||||
tc.setupMock()
|
tc.setupMock()
|
||||||
|
|
||||||
// Call the method under test.
|
// Call the method under test.
|
||||||
result, err := tp.broadcast(requestID)
|
result, err := tp.broadcast(rec)
|
||||||
|
|
||||||
// Check the result is as expected.
|
// Check the result is as expected.
|
||||||
require.ErrorIs(t, err, tc.expectedErr)
|
require.ErrorIs(t, err, tc.expectedErr)
|
||||||
@@ -796,6 +835,13 @@ func TestRemoveResult(t *testing.T) {
|
|||||||
// Create a test request ID counter.
|
// Create a test request ID counter.
|
||||||
requestCounter := atomic.Uint64{}
|
requestCounter := atomic.Uint64{}
|
||||||
|
|
||||||
|
// Create a sweepTxCtx.
|
||||||
|
sweepCtx := &sweepTxCtx{
|
||||||
|
tx: tx,
|
||||||
|
fee: fee,
|
||||||
|
outpointToTxIndex: utxoIndex,
|
||||||
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
setupRecord func() uint64
|
setupRecord func() uint64
|
||||||
@@ -808,9 +854,15 @@ func TestRemoveResult(t *testing.T) {
|
|||||||
name: "remove on TxConfirmed",
|
name: "remove on TxConfirmed",
|
||||||
setupRecord: func() uint64 {
|
setupRecord: func() uint64 {
|
||||||
rid := requestCounter.Add(1)
|
rid := requestCounter.Add(1)
|
||||||
tp.storeRecord(
|
|
||||||
rid, tx, req, m.feeFunc, fee, utxoIndex,
|
// Create a test record.
|
||||||
)
|
record := &monitorRecord{
|
||||||
|
requestID: rid,
|
||||||
|
req: req,
|
||||||
|
feeFunction: m.feeFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
tp.updateRecord(record, sweepCtx)
|
||||||
tp.subscriberChans.Store(rid, nil)
|
tp.subscriberChans.Store(rid, nil)
|
||||||
|
|
||||||
return rid
|
return rid
|
||||||
@@ -826,9 +878,15 @@ func TestRemoveResult(t *testing.T) {
|
|||||||
name: "remove on TxFailed",
|
name: "remove on TxFailed",
|
||||||
setupRecord: func() uint64 {
|
setupRecord: func() uint64 {
|
||||||
rid := requestCounter.Add(1)
|
rid := requestCounter.Add(1)
|
||||||
tp.storeRecord(
|
|
||||||
rid, tx, req, m.feeFunc, fee, utxoIndex,
|
// Create a test record.
|
||||||
)
|
record := &monitorRecord{
|
||||||
|
requestID: rid,
|
||||||
|
req: req,
|
||||||
|
feeFunction: m.feeFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
tp.updateRecord(record, sweepCtx)
|
||||||
tp.subscriberChans.Store(rid, nil)
|
tp.subscriberChans.Store(rid, nil)
|
||||||
|
|
||||||
return rid
|
return rid
|
||||||
@@ -845,9 +903,15 @@ func TestRemoveResult(t *testing.T) {
|
|||||||
name: "noop when tx is not confirmed or failed",
|
name: "noop when tx is not confirmed or failed",
|
||||||
setupRecord: func() uint64 {
|
setupRecord: func() uint64 {
|
||||||
rid := requestCounter.Add(1)
|
rid := requestCounter.Add(1)
|
||||||
tp.storeRecord(
|
|
||||||
rid, tx, req, m.feeFunc, fee, utxoIndex,
|
// Create a test record.
|
||||||
)
|
record := &monitorRecord{
|
||||||
|
requestID: rid,
|
||||||
|
req: req,
|
||||||
|
feeFunction: m.feeFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
tp.updateRecord(record, sweepCtx)
|
||||||
tp.subscriberChans.Store(rid, nil)
|
tp.subscriberChans.Store(rid, nil)
|
||||||
|
|
||||||
return rid
|
return rid
|
||||||
@@ -906,7 +970,21 @@ func TestNotifyResult(t *testing.T) {
|
|||||||
// Create a testing record and put it in the map.
|
// Create a testing record and put it in the map.
|
||||||
fee := btcutil.Amount(1000)
|
fee := btcutil.Amount(1000)
|
||||||
requestID := uint64(1)
|
requestID := uint64(1)
|
||||||
tp.storeRecord(requestID, tx, req, m.feeFunc, fee, utxoIndex)
|
|
||||||
|
// Create a sweepTxCtx.
|
||||||
|
sweepCtx := &sweepTxCtx{
|
||||||
|
tx: tx,
|
||||||
|
fee: fee,
|
||||||
|
outpointToTxIndex: utxoIndex,
|
||||||
|
}
|
||||||
|
// Create a test record.
|
||||||
|
record := &monitorRecord{
|
||||||
|
requestID: requestID,
|
||||||
|
req: req,
|
||||||
|
feeFunction: m.feeFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
tp.updateRecord(record, sweepCtx)
|
||||||
|
|
||||||
// Create a subscription to the event.
|
// Create a subscription to the event.
|
||||||
subscriber := make(chan *BumpResult, 1)
|
subscriber := make(chan *BumpResult, 1)
|
||||||
@@ -1058,6 +1136,7 @@ func TestCreateAnPublishFail(t *testing.T) {
|
|||||||
// Overwrite the budget to make it smaller than the fee.
|
// Overwrite the budget to make it smaller than the fee.
|
||||||
req.Budget = 100
|
req.Budget = 100
|
||||||
record := &monitorRecord{
|
record := &monitorRecord{
|
||||||
|
requestID: requestID,
|
||||||
req: req,
|
req: req,
|
||||||
feeFunction: m.feeFunc,
|
feeFunction: m.feeFunc,
|
||||||
tx: &wire.MsgTx{},
|
tx: &wire.MsgTx{},
|
||||||
@@ -1073,7 +1152,7 @@ func TestCreateAnPublishFail(t *testing.T) {
|
|||||||
mock.Anything).Return(script, nil)
|
mock.Anything).Return(script, nil)
|
||||||
|
|
||||||
// Call the createAndPublish method.
|
// Call the createAndPublish method.
|
||||||
resultOpt := tp.createAndPublishTx(requestID, record)
|
resultOpt := tp.createAndPublishTx(record)
|
||||||
result := resultOpt.UnwrapOrFail(t)
|
result := resultOpt.UnwrapOrFail(t)
|
||||||
|
|
||||||
// We expect the result to be TxFailed and the error is set in the
|
// We expect the result to be TxFailed and the error is set in the
|
||||||
@@ -1092,7 +1171,7 @@ func TestCreateAnPublishFail(t *testing.T) {
|
|||||||
mock.Anything).Return(lnwallet.ErrMempoolFee).Once()
|
mock.Anything).Return(lnwallet.ErrMempoolFee).Once()
|
||||||
|
|
||||||
// Call the createAndPublish method and expect a none option.
|
// Call the createAndPublish method and expect a none option.
|
||||||
resultOpt = tp.createAndPublishTx(requestID, record)
|
resultOpt = tp.createAndPublishTx(record)
|
||||||
require.True(t, resultOpt.IsNone())
|
require.True(t, resultOpt.IsNone())
|
||||||
|
|
||||||
// Mock the testmempoolaccept to return a fee related error that should
|
// Mock the testmempoolaccept to return a fee related error that should
|
||||||
@@ -1101,7 +1180,7 @@ func TestCreateAnPublishFail(t *testing.T) {
|
|||||||
mock.Anything).Return(chain.ErrInsufficientFee).Once()
|
mock.Anything).Return(chain.ErrInsufficientFee).Once()
|
||||||
|
|
||||||
// Call the createAndPublish method and expect a none option.
|
// Call the createAndPublish method and expect a none option.
|
||||||
resultOpt = tp.createAndPublishTx(requestID, record)
|
resultOpt = tp.createAndPublishTx(record)
|
||||||
require.True(t, resultOpt.IsNone())
|
require.True(t, resultOpt.IsNone())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1123,6 +1202,7 @@ func TestCreateAnPublishSuccess(t *testing.T) {
|
|||||||
// Create a testing monitor record.
|
// Create a testing monitor record.
|
||||||
req := createTestBumpRequest()
|
req := createTestBumpRequest()
|
||||||
record := &monitorRecord{
|
record := &monitorRecord{
|
||||||
|
requestID: requestID,
|
||||||
req: req,
|
req: req,
|
||||||
feeFunction: m.feeFunc,
|
feeFunction: m.feeFunc,
|
||||||
tx: &wire.MsgTx{},
|
tx: &wire.MsgTx{},
|
||||||
@@ -1145,7 +1225,7 @@ func TestCreateAnPublishSuccess(t *testing.T) {
|
|||||||
mock.Anything, mock.Anything).Return(errDummy).Once()
|
mock.Anything, mock.Anything).Return(errDummy).Once()
|
||||||
|
|
||||||
// Call the createAndPublish method and expect a failure result.
|
// Call the createAndPublish method and expect a failure result.
|
||||||
resultOpt := tp.createAndPublishTx(requestID, record)
|
resultOpt := tp.createAndPublishTx(record)
|
||||||
result := resultOpt.UnwrapOrFail(t)
|
result := resultOpt.UnwrapOrFail(t)
|
||||||
|
|
||||||
// We expect the result to be TxFailed and the error is set.
|
// We expect the result to be TxFailed and the error is set.
|
||||||
@@ -1166,7 +1246,7 @@ func TestCreateAnPublishSuccess(t *testing.T) {
|
|||||||
mock.Anything, mock.Anything).Return(nil).Once()
|
mock.Anything, mock.Anything).Return(nil).Once()
|
||||||
|
|
||||||
// Call the createAndPublish method and expect a success result.
|
// Call the createAndPublish method and expect a success result.
|
||||||
resultOpt = tp.createAndPublishTx(requestID, record)
|
resultOpt = tp.createAndPublishTx(record)
|
||||||
result = resultOpt.UnwrapOrFail(t)
|
result = resultOpt.UnwrapOrFail(t)
|
||||||
require.True(t, resultOpt.IsSome())
|
require.True(t, resultOpt.IsSome())
|
||||||
|
|
||||||
@@ -1208,7 +1288,22 @@ func TestHandleTxConfirmed(t *testing.T) {
|
|||||||
// Create a testing record and put it in the map.
|
// Create a testing record and put it in the map.
|
||||||
fee := btcutil.Amount(1000)
|
fee := btcutil.Amount(1000)
|
||||||
requestID := uint64(1)
|
requestID := uint64(1)
|
||||||
tp.storeRecord(requestID, tx, req, m.feeFunc, fee, utxoIndex)
|
|
||||||
|
// Create a sweepTxCtx.
|
||||||
|
sweepCtx := &sweepTxCtx{
|
||||||
|
tx: tx,
|
||||||
|
fee: fee,
|
||||||
|
outpointToTxIndex: utxoIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a test record.
|
||||||
|
record := &monitorRecord{
|
||||||
|
requestID: requestID,
|
||||||
|
req: req,
|
||||||
|
feeFunction: m.feeFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
tp.updateRecord(record, sweepCtx)
|
||||||
record, ok := tp.records.Load(requestID)
|
record, ok := tp.records.Load(requestID)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
@@ -1226,7 +1321,7 @@ func TestHandleTxConfirmed(t *testing.T) {
|
|||||||
tp.wg.Add(1)
|
tp.wg.Add(1)
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
tp.handleTxConfirmed(record, requestID)
|
tp.handleTxConfirmed(record)
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -1272,7 +1367,11 @@ func TestHandleFeeBumpTx(t *testing.T) {
|
|||||||
|
|
||||||
// Create a testing monitor record.
|
// Create a testing monitor record.
|
||||||
req := createTestBumpRequest()
|
req := createTestBumpRequest()
|
||||||
|
|
||||||
|
// Create a testing record and put it in the map.
|
||||||
|
requestID := uint64(1)
|
||||||
record := &monitorRecord{
|
record := &monitorRecord{
|
||||||
|
requestID: requestID,
|
||||||
req: req,
|
req: req,
|
||||||
feeFunction: m.feeFunc,
|
feeFunction: m.feeFunc,
|
||||||
tx: tx,
|
tx: tx,
|
||||||
@@ -1285,11 +1384,16 @@ func TestHandleFeeBumpTx(t *testing.T) {
|
|||||||
utxoIndex := map[wire.OutPoint]int{
|
utxoIndex := map[wire.OutPoint]int{
|
||||||
op: 0,
|
op: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a testing record and put it in the map.
|
|
||||||
fee := btcutil.Amount(1000)
|
fee := btcutil.Amount(1000)
|
||||||
requestID := uint64(1)
|
|
||||||
tp.storeRecord(requestID, tx, req, m.feeFunc, fee, utxoIndex)
|
// Create a sweepTxCtx.
|
||||||
|
sweepCtx := &sweepTxCtx{
|
||||||
|
tx: tx,
|
||||||
|
fee: fee,
|
||||||
|
outpointToTxIndex: utxoIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
tp.updateRecord(record, sweepCtx)
|
||||||
|
|
||||||
// Create a subscription to the event.
|
// Create a subscription to the event.
|
||||||
subscriber := make(chan *BumpResult, 1)
|
subscriber := make(chan *BumpResult, 1)
|
||||||
@@ -1305,7 +1409,7 @@ func TestHandleFeeBumpTx(t *testing.T) {
|
|||||||
|
|
||||||
// Call the method and expect no result received.
|
// Call the method and expect no result received.
|
||||||
tp.wg.Add(1)
|
tp.wg.Add(1)
|
||||||
go tp.handleFeeBumpTx(requestID, record, testHeight)
|
go tp.handleFeeBumpTx(record, testHeight)
|
||||||
|
|
||||||
// Check there's no result sent back.
|
// Check there's no result sent back.
|
||||||
select {
|
select {
|
||||||
@@ -1319,7 +1423,7 @@ func TestHandleFeeBumpTx(t *testing.T) {
|
|||||||
|
|
||||||
// Call the method and expect no result received.
|
// Call the method and expect no result received.
|
||||||
tp.wg.Add(1)
|
tp.wg.Add(1)
|
||||||
go tp.handleFeeBumpTx(requestID, record, testHeight)
|
go tp.handleFeeBumpTx(record, testHeight)
|
||||||
|
|
||||||
// Check there's no result sent back.
|
// Check there's no result sent back.
|
||||||
select {
|
select {
|
||||||
@@ -1351,7 +1455,7 @@ func TestHandleFeeBumpTx(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// NOTE: must be called in a goroutine in case it blocks.
|
// NOTE: must be called in a goroutine in case it blocks.
|
||||||
tp.wg.Add(1)
|
tp.wg.Add(1)
|
||||||
go tp.handleFeeBumpTx(requestID, record, testHeight)
|
go tp.handleFeeBumpTx(record, testHeight)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
@@ -1397,6 +1501,7 @@ func TestProcessRecords(t *testing.T) {
|
|||||||
|
|
||||||
// Create a monitor record that's confirmed.
|
// Create a monitor record that's confirmed.
|
||||||
recordConfirmed := &monitorRecord{
|
recordConfirmed := &monitorRecord{
|
||||||
|
requestID: requestID1,
|
||||||
req: req1,
|
req: req1,
|
||||||
feeFunction: m.feeFunc,
|
feeFunction: m.feeFunc,
|
||||||
tx: tx1,
|
tx: tx1,
|
||||||
@@ -1410,6 +1515,7 @@ func TestProcessRecords(t *testing.T) {
|
|||||||
// Create a monitor record that's not confirmed. We know it's not
|
// Create a monitor record that's not confirmed. We know it's not
|
||||||
// confirmed because the num of confirms is zero.
|
// confirmed because the num of confirms is zero.
|
||||||
recordFeeBump := &monitorRecord{
|
recordFeeBump := &monitorRecord{
|
||||||
|
requestID: requestID2,
|
||||||
req: req2,
|
req: req2,
|
||||||
feeFunction: m.feeFunc,
|
feeFunction: m.feeFunc,
|
||||||
tx: tx2,
|
tx: tx2,
|
||||||
@@ -1548,7 +1654,7 @@ func TestHandleInitialBroadcastSuccess(t *testing.T) {
|
|||||||
|
|
||||||
// Call the method under test.
|
// Call the method under test.
|
||||||
tp.wg.Add(1)
|
tp.wg.Add(1)
|
||||||
tp.handleInitialBroadcast(rec, rid)
|
tp.handleInitialBroadcast(rec)
|
||||||
|
|
||||||
// Check the result is sent back.
|
// Check the result is sent back.
|
||||||
select {
|
select {
|
||||||
@@ -1619,7 +1725,7 @@ func TestHandleInitialBroadcastFail(t *testing.T) {
|
|||||||
|
|
||||||
// Call the method under test and expect an error returned.
|
// Call the method under test and expect an error returned.
|
||||||
tp.wg.Add(1)
|
tp.wg.Add(1)
|
||||||
tp.handleInitialBroadcast(rec, rid)
|
tp.handleInitialBroadcast(rec)
|
||||||
|
|
||||||
// Check the result is sent back.
|
// Check the result is sent back.
|
||||||
select {
|
select {
|
||||||
@@ -1652,7 +1758,7 @@ func TestHandleInitialBroadcastFail(t *testing.T) {
|
|||||||
|
|
||||||
// Call the method under test.
|
// Call the method under test.
|
||||||
tp.wg.Add(1)
|
tp.wg.Add(1)
|
||||||
tp.handleInitialBroadcast(rec, rid)
|
tp.handleInitialBroadcast(rec)
|
||||||
|
|
||||||
// Check the result is sent back.
|
// Check the result is sent back.
|
||||||
select {
|
select {
|
||||||
|
|||||||
+18
-15
@@ -119,9 +119,12 @@ const (
|
|||||||
// sweeping transactions confirmed, the remaining two will be excluded.
|
// sweeping transactions confirmed, the remaining two will be excluded.
|
||||||
Excluded
|
Excluded
|
||||||
|
|
||||||
// Failed is the state when a pending input has too many failed publish
|
// Fatal is the final state of a pending input. Inputs ending in this
|
||||||
// atttempts or unknown broadcast error is returned.
|
// state won't be retried. This could happen,
|
||||||
Failed
|
// - when a pending input has too many failed publish attempts;
|
||||||
|
// - the input has been spent by another party;
|
||||||
|
// - unknown broadcast error is returned.
|
||||||
|
Fatal
|
||||||
)
|
)
|
||||||
|
|
||||||
// String gives a human readable text for the sweep states.
|
// String gives a human readable text for the sweep states.
|
||||||
@@ -145,8 +148,8 @@ func (s SweepState) String() string {
|
|||||||
case Excluded:
|
case Excluded:
|
||||||
return "Excluded"
|
return "Excluded"
|
||||||
|
|
||||||
case Failed:
|
case Fatal:
|
||||||
return "Failed"
|
return "Fatal"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
@@ -215,7 +218,7 @@ func (p *SweeperInput) terminated() bool {
|
|||||||
// If the input has reached a final state, that it's either
|
// If the input has reached a final state, that it's either
|
||||||
// been swept, or failed, or excluded, we will remove it from
|
// been swept, or failed, or excluded, we will remove it from
|
||||||
// our sweeper.
|
// our sweeper.
|
||||||
case Failed, Swept, Excluded:
|
case Fatal, Swept, Excluded:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -1264,7 +1267,7 @@ func (s *UtxoSweeper) handleNewInput(input *sweepInputMessage) error {
|
|||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("wait for spend: %w", err)
|
err := fmt.Errorf("wait for spend: %w", err)
|
||||||
s.markInputFailed(pi, err)
|
s.markInputFatal(pi, err)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1477,12 +1480,12 @@ func (s *UtxoSweeper) markInputsSwept(tx *wire.MsgTx, isOurTx bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// markInputFailed marks the given input as failed and won't be retried. It
|
// markInputFatal marks the given input as fatal and won't be retried. It
|
||||||
// will also notify all the subscribers of this input.
|
// will also notify all the subscribers of this input.
|
||||||
func (s *UtxoSweeper) markInputFailed(pi *SweeperInput, err error) {
|
func (s *UtxoSweeper) markInputFatal(pi *SweeperInput, err error) {
|
||||||
log.Errorf("Failed to sweep input: %v, error: %v", pi, err)
|
log.Errorf("Failed to sweep input: %v, error: %v", pi, err)
|
||||||
|
|
||||||
pi.state = Failed
|
pi.state = Fatal
|
||||||
|
|
||||||
s.signalResult(pi, Result{Err: err})
|
s.signalResult(pi, Result{Err: err})
|
||||||
}
|
}
|
||||||
@@ -1784,15 +1787,15 @@ func (s *UtxoSweeper) handleBumpEventTxFatal(resp *bumpResp) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the inputs as failed.
|
// Mark the inputs as fatal.
|
||||||
s.markInputsFailed(resp.set, r.Err)
|
s.markInputsFatal(resp.set, r.Err)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// markInputsFailed marks all inputs found in the tx as failed. It will also
|
// markInputsFatal marks all inputs in the input set as failed. It will also
|
||||||
// notify all the subscribers of these inputs.
|
// notify all the subscribers of these inputs.
|
||||||
func (s *UtxoSweeper) markInputsFailed(set InputSet, err error) {
|
func (s *UtxoSweeper) markInputsFatal(set InputSet, err error) {
|
||||||
for _, inp := range set.Inputs() {
|
for _, inp := range set.Inputs() {
|
||||||
outpoint := inp.OutPoint()
|
outpoint := inp.OutPoint()
|
||||||
|
|
||||||
@@ -1816,7 +1819,7 @@ func (s *UtxoSweeper) markInputsFailed(set InputSet, err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
s.markInputFailed(input, err)
|
s.markInputFatal(input, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-20
@@ -215,7 +215,7 @@ func TestMarkInputsPublishFailed(t *testing.T) {
|
|||||||
// published.
|
// published.
|
||||||
// - inputSwept specifies an input that's swept.
|
// - inputSwept specifies an input that's swept.
|
||||||
// - inputExcluded specifies an input that's excluded.
|
// - inputExcluded specifies an input that's excluded.
|
||||||
// - inputFailed specifies an input that's failed.
|
// - inputFatal specifies an input that's fatal.
|
||||||
var (
|
var (
|
||||||
inputInit = createMockInput(t, s, Init)
|
inputInit = createMockInput(t, s, Init)
|
||||||
inputPendingPublish = createMockInput(t, s, PendingPublish)
|
inputPendingPublish = createMockInput(t, s, PendingPublish)
|
||||||
@@ -223,13 +223,13 @@ func TestMarkInputsPublishFailed(t *testing.T) {
|
|||||||
inputPublishFailed = createMockInput(t, s, PublishFailed)
|
inputPublishFailed = createMockInput(t, s, PublishFailed)
|
||||||
inputSwept = createMockInput(t, s, Swept)
|
inputSwept = createMockInput(t, s, Swept)
|
||||||
inputExcluded = createMockInput(t, s, Excluded)
|
inputExcluded = createMockInput(t, s, Excluded)
|
||||||
inputFailed = createMockInput(t, s, Failed)
|
inputFatal = createMockInput(t, s, Fatal)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Gather all inputs.
|
// Gather all inputs.
|
||||||
set.On("Inputs").Return([]input.Input{
|
set.On("Inputs").Return([]input.Input{
|
||||||
inputInit, inputPendingPublish, inputPublished,
|
inputInit, inputPendingPublish, inputPublished,
|
||||||
inputPublishFailed, inputSwept, inputExcluded, inputFailed,
|
inputPublishFailed, inputSwept, inputExcluded, inputFatal,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mark the test inputs. We expect the non-exist input and the
|
// Mark the test inputs. We expect the non-exist input and the
|
||||||
@@ -264,7 +264,7 @@ func TestMarkInputsPublishFailed(t *testing.T) {
|
|||||||
require.Equal(Excluded, s.inputs[inputExcluded.OutPoint()].state)
|
require.Equal(Excluded, s.inputs[inputExcluded.OutPoint()].state)
|
||||||
|
|
||||||
// We expect the failed input to stay unchanged.
|
// We expect the failed input to stay unchanged.
|
||||||
require.Equal(Failed, s.inputs[inputFailed.OutPoint()].state)
|
require.Equal(Fatal, s.inputs[inputFatal.OutPoint()].state)
|
||||||
|
|
||||||
// Assert mocked statements are executed as expected.
|
// Assert mocked statements are executed as expected.
|
||||||
mockStore.AssertExpectations(t)
|
mockStore.AssertExpectations(t)
|
||||||
@@ -437,7 +437,7 @@ func TestUpdateSweeperInputs(t *testing.T) {
|
|||||||
// These inputs won't hit RequiredLockTime so we won't mock.
|
// These inputs won't hit RequiredLockTime so we won't mock.
|
||||||
input4 := &SweeperInput{state: Swept, Input: inp1}
|
input4 := &SweeperInput{state: Swept, Input: inp1}
|
||||||
input5 := &SweeperInput{state: Excluded, Input: inp1}
|
input5 := &SweeperInput{state: Excluded, Input: inp1}
|
||||||
input6 := &SweeperInput{state: Failed, Input: inp1}
|
input6 := &SweeperInput{state: Fatal, Input: inp1}
|
||||||
|
|
||||||
// Mock the input to have a locktime in the future so it will NOT be
|
// Mock the input to have a locktime in the future so it will NOT be
|
||||||
// returned.
|
// returned.
|
||||||
@@ -575,7 +575,7 @@ func TestDecideStateAndRBFInfo(t *testing.T) {
|
|||||||
require.Equal(Published, state)
|
require.Equal(Published, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMarkInputFailed checks that the input is marked as failed as expected.
|
// TestMarkInputFatal checks that the input is marked as expected.
|
||||||
func TestMarkInputFailed(t *testing.T) {
|
func TestMarkInputFailed(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -596,10 +596,10 @@ func TestMarkInputFailed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call the method under test.
|
// Call the method under test.
|
||||||
s.markInputFailed(pi, errors.New("dummy error"))
|
s.markInputFatal(pi, errors.New("dummy error"))
|
||||||
|
|
||||||
// Assert the state is updated.
|
// Assert the state is updated.
|
||||||
require.Equal(t, Failed, pi.state)
|
require.Equal(t, Fatal, pi.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSweepPendingInputs checks that `sweepPendingInputs` correctly executes
|
// TestSweepPendingInputs checks that `sweepPendingInputs` correctly executes
|
||||||
@@ -1102,7 +1102,7 @@ func TestMarkInputsFailed(t *testing.T) {
|
|||||||
// published.
|
// published.
|
||||||
// - inputSwept specifies an input that's swept.
|
// - inputSwept specifies an input that's swept.
|
||||||
// - inputExcluded specifies an input that's excluded.
|
// - inputExcluded specifies an input that's excluded.
|
||||||
// - inputFailed specifies an input that's failed.
|
// - inputFatal specifies an input that's fatal.
|
||||||
var (
|
var (
|
||||||
inputInit = createMockInput(t, s, Init)
|
inputInit = createMockInput(t, s, Init)
|
||||||
inputPendingPublish = createMockInput(t, s, PendingPublish)
|
inputPendingPublish = createMockInput(t, s, PendingPublish)
|
||||||
@@ -1110,33 +1110,33 @@ func TestMarkInputsFailed(t *testing.T) {
|
|||||||
inputPublishFailed = createMockInput(t, s, PublishFailed)
|
inputPublishFailed = createMockInput(t, s, PublishFailed)
|
||||||
inputSwept = createMockInput(t, s, Swept)
|
inputSwept = createMockInput(t, s, Swept)
|
||||||
inputExcluded = createMockInput(t, s, Excluded)
|
inputExcluded = createMockInput(t, s, Excluded)
|
||||||
inputFailed = createMockInput(t, s, Failed)
|
inputFatal = createMockInput(t, s, Fatal)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Gather all inputs.
|
// Gather all inputs.
|
||||||
set.On("Inputs").Return([]input.Input{
|
set.On("Inputs").Return([]input.Input{
|
||||||
inputInit, inputPendingPublish, inputPublished,
|
inputInit, inputPendingPublish, inputPublished,
|
||||||
inputPublishFailed, inputSwept, inputExcluded, inputFailed,
|
inputPublishFailed, inputSwept, inputExcluded, inputFatal,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mark the test inputs. We expect the non-exist input and
|
// Mark the test inputs. We expect the non-exist input and
|
||||||
// inputSwept/inputExcluded/inputFailed to be skipped.
|
// inputSwept/inputExcluded/inputFatal to be skipped.
|
||||||
s.markInputsFailed(set, errDummy)
|
s.markInputsFatal(set, errDummy)
|
||||||
|
|
||||||
// We expect unchanged number of pending inputs.
|
// We expect unchanged number of pending inputs.
|
||||||
require.Len(s.inputs, 7)
|
require.Len(s.inputs, 7)
|
||||||
|
|
||||||
// We expect the init input's to be marked as failed.
|
// We expect the init input's to be marked as fatal.
|
||||||
require.Equal(Failed, s.inputs[inputInit.OutPoint()].state)
|
require.Equal(Fatal, s.inputs[inputInit.OutPoint()].state)
|
||||||
|
|
||||||
// We expect the pending-publish input to be marked as failed.
|
// We expect the pending-publish input to be marked as failed.
|
||||||
require.Equal(Failed, s.inputs[inputPendingPublish.OutPoint()].state)
|
require.Equal(Fatal, s.inputs[inputPendingPublish.OutPoint()].state)
|
||||||
|
|
||||||
// We expect the published input to be marked as failed.
|
// We expect the published input to be marked as fatal.
|
||||||
require.Equal(Failed, s.inputs[inputPublished.OutPoint()].state)
|
require.Equal(Fatal, s.inputs[inputPublished.OutPoint()].state)
|
||||||
|
|
||||||
// We expect the publish failed input to be markd as failed.
|
// We expect the publish failed input to be markd as failed.
|
||||||
require.Equal(Failed, s.inputs[inputPublishFailed.OutPoint()].state)
|
require.Equal(Fatal, s.inputs[inputPublishFailed.OutPoint()].state)
|
||||||
|
|
||||||
// We expect the swept input to stay unchanged.
|
// We expect the swept input to stay unchanged.
|
||||||
require.Equal(Swept, s.inputs[inputSwept.OutPoint()].state)
|
require.Equal(Swept, s.inputs[inputSwept.OutPoint()].state)
|
||||||
@@ -1145,7 +1145,7 @@ func TestMarkInputsFailed(t *testing.T) {
|
|||||||
require.Equal(Excluded, s.inputs[inputExcluded.OutPoint()].state)
|
require.Equal(Excluded, s.inputs[inputExcluded.OutPoint()].state)
|
||||||
|
|
||||||
// We expect the failed input to stay unchanged.
|
// We expect the failed input to stay unchanged.
|
||||||
require.Equal(Failed, s.inputs[inputFailed.OutPoint()].state)
|
require.Equal(Fatal, s.inputs[inputFatal.OutPoint()].state)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestHandleBumpEventTxFatal checks that `handleBumpEventTxFatal` correctly
|
// TestHandleBumpEventTxFatal checks that `handleBumpEventTxFatal` correctly
|
||||||
|
|||||||
Reference in new issue
Block a user