Skip to content
This repository was archived by the owner on Nov 2, 2018. It is now read-only.

Make wallet rebroadcast transactions until they are confirmed#2611

Open
ChrisSchinnerl wants to merge 12 commits into
masterfrom
wallet-rebroadcast
Open

Make wallet rebroadcast transactions until they are confirmed#2611
ChrisSchinnerl wants to merge 12 commits into
masterfrom
wallet-rebroadcast

Conversation

@ChrisSchinnerl

Copy link
Copy Markdown
Contributor

This is a work in progress but I would appreciate some code review before I continue this.

Comment thread 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

Copy link
Copy Markdown
Member

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.

@DavidVorick

Copy link
Copy Markdown
Member

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 tpool.AcceptTransactionSet with a transaction set that you've already added to the tpool, you will get an error, and it won't broadcast.

So we should modify the AcceptTransactionSet to go ahead and broadcast the transaction and reset the timer on the transactions (they get booted after 12 blocks) if the broadcast function is called again.

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.

Comment thread modules/wallet/update.go
for _, sco := range txn.SiacoinOutputs {
relevant = relevant || w.isWalletAddress(sco.UnlockHash)
}
return

Copy link
Copy Markdown
Member

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.

Comment thread modules/wallet/wallet.go Outdated
tries int
confirmedTxn map[types.TransactionID]bool
transactions []types.Transaction
}

Copy link
Copy Markdown
Member

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.

@DavidVorick

Copy link
Copy Markdown
Member

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 RespendTimeout the wallet will start using those outputs to make other transactions again, and we want to make sure we aren't broadcasting transactions which the wallet will try to spend again.

@ChrisSchinnerl ChrisSchinnerl force-pushed the wallet-rebroadcast branch 4 times, most recently from 87422ae to 29c5100 Compare January 5, 2018 14:42
@4Dolio

4Dolio commented Jan 5, 2018

Copy link
Copy Markdown

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?

Comment thread 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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

excellent comments

@DavidVorick

Copy link
Copy Markdown
Member

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.

@jarcko

jarcko commented Jan 11, 2018

Copy link
Copy Markdown

Tranascations not found here
https://explore.sia.tech/hashes/50a860e7e4ce6976710d1d52b580bb83ca5e125dbbfd5e34722811a365676a05

screen shot 2018-01-11 at 7 54 55 pm

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?

@DavidVorick

Copy link
Copy Markdown
Member

Are you running a host? Did you just make that transaction? How long did you wait for the coins to send?

Comment thread modules/consts.go
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)

Copy link
Copy Markdown
Member

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

Copy link
Copy Markdown
Contributor Author

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?

Comment thread modules/transactionpool/accept.go Outdated
tp.transactionHeights[txn.ID()] = tp.blockHeight
}
}
go tp.gateway.Broadcast("RelayTransactionSet", ts, tp.gateway.Peers())

Copy link
Copy Markdown
Member

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)

Copy link
Copy Markdown
Member

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

Comment thread 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 {

Copy link
Copy Markdown
Member

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.

Comment thread 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 {

Copy link
Copy Markdown
Member

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

Comment thread modules/wallet/update.go Outdated

// Mark reverted transactions as not confirmed
for _, block := range cc.RevertedBlocks {
for _, bts := range w.broadcastedTSets {

Copy link
Copy Markdown
Member

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.

Comment thread modules/wallet/update.go Outdated
if _, exists := bts.confirmedTxn[txn.ID()]; exists {
err = bts.confirmed(txn.ID(), false)
}
if err != nil {

Copy link
Copy Markdown
Member

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

tp.mu.Lock()
defer tp.mu.Unlock()
err := tp.acceptTransactionSet(ts, txnFn)
if err != nil {

Copy link
Copy Markdown
Member

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()

Copy link
Copy Markdown
Member

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.

Comment thread modules/wallet/update.go Outdated
// blocks
if consensusHeight >= bts.firstTry+RebroadcastTimeout {
if err := w.deleteBroadcastedTSet(tSetID); err != nil {
return err

Copy link
Copy Markdown
Member

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants