Closed hstove closed 3 years ago
It might be nice to get some data around how quickly transactions are confirmed in a microblock. One way to investigating this could be to write a script that makes a bunch of transactions with specific delays, while always checking the unanchored chain tip nonce. Since the script would know what the 'correct next nonce' is, we can know what kind of interval would cause this logic to fail.
Unfortunately, this information is effectively unknowable. First, each miner is free to choose how quickly or slowly it produces microblocks and who it decides to broadcast them to, and it doesn't need to be consistent. In fact, a malicious miner can make these decisions adaptively in a bid to break whatever heuristic you come up with. Second, apart from the protocol-enforced format, the only constraint on the miner's microblock-producing behaviors that other nodes can enforce is to prevent the miner from exceeding its block budget during its tenure (note this is the combined budget -- the budget consumed by the on-chain Stacks block plus all of its trailing microblocks). A malicious or buggy miner can produce microblocks at arbitrary rates, withhold microblocks from certain peers, or even produce microblock stream forks.
While it may not be economically advantageous for a miner to do any of these things, they can nevertheless happen. It may not even be the miner's fault -- for example, the ISP itself may be malicious, and choose to delay or prevent microblock propagation. Robust client software must accommodate this behavior.
A different approach, which might be more durable, would be for the API to have a special route to return a nonce based on known mempool transactions. Since mempool transactions get emitted to the API quite quickly, this could work well. It also has different failure scenarios.
Same problem with transactions, I'm afraid. There's no guarantee that the mempool of the node you talk to accurately reflects a given account's latest nonce. Nodes can't be compelled to relay transactions -- they can keep them to themselves, they can choose to only replicate transactions as part of a block, they can mine blocks out of hidden transactions and then withhold the blocks for a time (e.g. as part of a selfish mining attack), they can choose which of a set of conflicting transactions gets relayed and/or mined, and so on.
In general, state replication is only going to be best-effort in open-membership distributed systems.
This works quite well from my experience, but it is not foolproof. Additionally, it requires every client (wallets, exchanges, etc) to figure out this persistence layer and logic for themselves. There might also be edge cases where chaining nonces might not work - like if one of the earlier transactions gets lost or something.
Not trying to be a Debbie Downer here, but this is just something we're going to have to live with. It's not solvable in general -- the transaction you send to the peer network has no TTL, meaning it can be mined at any point arbitrarily far into the future as long as it remains valid. Moreover, if there are conflicting transactions, they can both be mined in different Stacks forks. There will always be edge-cases where a transaction appears "stuck."
Maybe we need to have a conversation on whether or not Stacks transactions should have TTLs (this would make for an interesting SIP). Having a TTL would at least let us bound the number of Stacks tenures for which a given transaction could be mined, so if a transaction doesn't get mined within its TTL, the wallet can correctly infer which nonce to use when it re-sends the transaction.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
This issue has been automatically closed. Please reopen if needed.
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
We often run into issues when two transactions end up in the mempool with the same nonce. This is the default behavior if two transactions are created before the first tx is confirmed. Our tools, aka
@stacks/transactions
has a straightforward method for fetching the nonce on a transaction if a nonce is not explicitly provided to the library's transaction builder methods. It works like this:nonce
is provided to tx builder/v2/accounts/SPXXX
, and get thenonce
There are multiple ways to handle this client-side. In the Web Wallet, I have a persistence layer that essentially keeps track of the last transaction nonce that was successfully broadcasted to the mempool, and the confirmed block that the nonce was fetched from. Then I have some logic that can get the "correct next nonce" based on the latest confirmed block, and the nonce that is returned from
/v2/accounts/SPXX
. This works quite well from my experience, but it is not foolproof. Additionally, it requires every client (wallets, exchanges, etc) to figure out this persistence layer and logic for themselves. There might also be edge cases where chaining nonces might not work - like if one of the earlier transactions gets lost or something.One potential improvement we can add to our tooling is to fetch the nonce from the
unanchored_chain_tip
. The logic in@stacks/transactions
could go like this:/v2/info
unanchored_chain_tip
/v2/accounts/SPXX?tip=${unanchored_tip}
If transactions are confirmed in a microblock quickly enough, then this might work quite well by default. However, this is also not foolproof. If you send two transactions before the first is in a microblock, you'll still have an overlapping chain tip. This seems pretty plausible for an exchange that is processing a lot of withdraws quickly.
It might be nice to get some data around how quickly transactions are confirmed in a microblock. One way to investigating this could be to write a script that makes a bunch of transactions with specific delays, while always checking the unanchored chain tip nonce. Since the script would know what the 'correct next nonce' is, we can know what kind of interval would cause this logic to fail.
A different approach, which might be more durable, would be for the API to have a special route to return a nonce based on known mempool transactions. Since mempool transactions get emitted to the API quite quickly, this could work well. It also has different failure scenarios.