Make wallet rebroadcast transactions until they are confirmed#2611
Make wallet rebroadcast transactions until they are confirmed#2611ChrisSchinnerl wants to merge 12 commits into
Conversation
|
|
||
| // 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.
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.
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.
| tries int | ||
| confirmedTxn map[types.TransactionID]bool | ||
| transactions []types.Transaction | ||
| } |
There was a problem hiding this comment.
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? |
| // 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) |
|
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.
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.
Should that be done in the same PR?
| tp.transactionHeights[txn.ID()] = tp.blockHeight | ||
| } | ||
| } | ||
| go tp.gateway.Broadcast("RelayTransactionSet", ts, tp.gateway.Peers()) |
There was a problem hiding this comment.
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.
The general convention is that a module can't call exported methods of other modules while under lock
|
|
||
| // 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.
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.
|
|
||
| // 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.
This function should probably be moved to broadcast.go
|
|
||
| // Mark reverted transactions as not confirmed | ||
| for _, block := range cc.RevertedBlocks { | ||
| for _, bts := range w.broadcastedTSets { |
There was a problem hiding this comment.
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.
| if _, exists := bts.confirmedTxn[txn.ID()]; exists { | ||
| err = bts.confirmed(txn.ID(), false) | ||
| } | ||
| if err != nil { |
There was a problem hiding this comment.
error handling needs to happen in the same scope as error creation
| tp.mu.Lock() | ||
| defer tp.mu.Unlock() | ||
| err := tp.acceptTransactionSet(ts, txnFn) | ||
| if err != nil { |
There was a problem hiding this comment.
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.
ah I missed this. This should happen under the same lock that you update the subscribers with.
f26934a to
0d3edb8
Compare
| // blocks | ||
| if consensusHeight >= bts.firstTry+RebroadcastTimeout { | ||
| if err := w.deleteBroadcastedTSet(tSetID); err != nil { | ||
| return err |
There was a problem hiding this comment.
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.