-
Notifications
You must be signed in to change notification settings - Fork 440
Make wallet rebroadcast transactions until they are confirmed #2611
base: master
Are you sure you want to change the base?
Conversation
modules/wallet/consts.go
Outdated
|
||
// rebroadcastMaxTries is the maximum number of times a transaction set | ||
// will be rebroadcasted before the wallet stops tracking it | ||
rebroadcastMaxTries = 10 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a const called RespendTimeout
in wallet.go
(that should be moved to consts.go
) which says the wallet will try to spend an output again after 40 blocks of it not being spent. Instead of having rebroadcastMaxTries
, you should just stop trying once the respend timeout has expired.
Now that we are trying to rebroadcast transactions, we can probably jump the RespendTimeout
to 72 blocks, which is 12 hours.
Part of the purpose of this PR is to make sure transactions get re-broadcast. Right now, I'm pretty sure that if you call So we should modify the I guess it's a little complicated though, because the wallet is going to start respending the transactions at some point, and we need to make sure that by that point the previous transaction in the transaction pool has been properly booted. I guess we'll need to leverage some consts in the modules package. |
for _, sco := range txn.SiacoinOutputs { | ||
relevant = relevant || w.isWalletAddress(sco.UnlockHash) | ||
} | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just throwing it out there to put it on the radar, it's not important for this PR, but the wallet is going to need to start recognizing file contracts as well as siacoin + siafund transactions.
modules/wallet/wallet.go
Outdated
tries int | ||
confirmedTxn map[types.TransactionID]bool | ||
transactions []types.Transaction | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should probably move this code into broadcast.go
or something. I think there's enough logic here that it deserves its own file.
I think generally speaking this code is on the right track. The one thing that seems to be missing is persistence - if you create a transaction, you should persist it in the database, so that if you start the wallet again later, it can be re-broadcast again. Have to be careful about that though, because after |
87422ae
to
29c5100
Compare
Great issue! Just throwing it out there, wr could use a traditional 'progressive' retry? Begin to retry in a short block height and get slower. Not sure is a very short retry would induce problems or cause a feedback overload to the blockchain/network itself? So pick a reasonably fast yet safe start duration. Say 5, then 10, 20, 40, 80, 160, untill acceptable max like 1 month? Week? |
modules/wallet/consts.go
Outdated
// will boot transactions after MaxTxnAge. We need to make sure that we | ||
// leave at least MaxTxnAge blocks after the last broadcast to allow for | ||
// the transasction to be pruned before the wallet tries to respend it. | ||
rebroadcastTimeout = types.BlockHeight(respendTimeout - modules.MaxTxnAge) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
excellent comments
Well, it's bad for the network to broadcast frequently. And, if you haven't broadcasted in 24 blocks, your transactions will have been dropped from any nodes running standard code, and will be unlikely to reach miners. So at the very least, you'd want to try once every 24 blocks I'd think. But, once every 6 blocks seems pretty sound / okay as well, at least for now. If transaction pools are full or the fees are too high, it'll likely change after 6 blocks. |
767ff66
to
17b5d9e
Compare
Tranascations not found here I see it every time so can't send coins at all, I reinstalled all from scratch and recovered the wallet, but still the same. Can you reproduce the issue? |
Are you running a host? Did you just make that transaction? How long did you wait for the coins to send? |
const ( | ||
// maxTxnAge determines the maximum age of a transaction (in block height) | ||
// allowed before the transaction is pruned from the transaction pool. | ||
MaxTxnAge = types.BlockHeight(24) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that there are other consts throughout the modules package, we may want to collect them all here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should that be done in the same PR?
modules/transactionpool/accept.go
Outdated
tp.transactionHeights[txn.ID()] = tp.blockHeight | ||
} | ||
} | ||
go tp.gateway.Broadcast("RelayTransactionSet", ts, tp.gateway.Peers()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
calling tp.gateway.Peers()
while the tpool is under lock is a deadlock risk, need to restructure to avoid this (looks like the existing code had this deadlock risk in it as well)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The general convention is that a module can't call exported methods of other modules while under lock
modules/wallet/broadcast.go
Outdated
|
||
// confirmed is a helper function that sets a certain transactions to confirmed | ||
// or unconfirmed. It also updates the state on disk. | ||
func (bts *broadcastedTSet) confirmed(txid types.TransactionID, confirmed bool) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function name suggests a query, as though you are asking for the confirmed status of a transaction. Should change the name to 'markConfirmation' or something similar.
modules/wallet/update.go
Outdated
|
||
// rebroadcastOldTransaction rebroadcasts transactions that haven't been | ||
// confirmed within rebroadcastInterval blocks | ||
func (w *Wallet) rebroadcastOldTransactions(tx *bolt.Tx, cc modules.ConsensusChange) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should probably be moved to broadcast.go
modules/wallet/update.go
Outdated
|
||
// Mark reverted transactions as not confirmed | ||
for _, block := range cc.RevertedBlocks { | ||
for _, bts := range w.broadcastedTSets { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an n^2 algorithm. The thing that worries me is that for each broadcastedTSet, you are iterating through every transaction of every block. If a wallet has worked up something like 100 broadcastedTSets over the past few blocks (not unreasonable for an exchange with lots of inbound attention), and there are 2000 transactions per block, we could be seeing 200,000 operations happening to get through each block. Combine that with a large (say 5 blocks) reorg and you could be seeing 4 reversions + 5 additions = 1.8 million total operations.
Need to find a faster way to do this.
modules/wallet/update.go
Outdated
if _, exists := bts.confirmedTxn[txn.ID()]; exists { | ||
err = bts.confirmed(txn.ID(), false) | ||
} | ||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
error handling needs to happen in the same scope as error creation
@@ -331,18 +331,33 @@ func (tp *TransactionPool) AcceptTransactionSet(ts []types.Transaction) error { | |||
return errors.New("consensus set does not support LockedTryTransactionSet method") | |||
} | |||
|
|||
return cs.LockedTryTransactionSet(func(txnFn func(txns []types.Transaction) (modules.ConsensusChange, error)) error { | |||
err := cs.LockedTryTransactionSet(func(txnFn func(txns []types.Transaction) (modules.ConsensusChange, error)) error { | |||
tp.mu.Lock() | |||
defer tp.mu.Unlock() | |||
err := tp.acceptTransactionSet(ts, txnFn) | |||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if err == nil
, right now you are only updating subscribers if there is an error :P
for _, txn := range ts { | ||
tp.transactionHeights[txn.ID()] = tp.blockHeight | ||
} | ||
tp.mu.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah I missed this. This should happen under the same lock that you update the subscribers with.
f26934a
to
0d3edb8
Compare
modules/wallet/update.go
Outdated
// blocks | ||
if consensusHeight >= bts.firstTry+RebroadcastTimeout { | ||
if err := w.deleteBroadcastedTSet(tSetID); err != nil { | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably better to log the error than to return in this case.
5260b38
to
00e9604
Compare
This is a work in progress but I would appreciate some code review before I continue this.