The design of sequence numbers is subtle (read: confusing), but the code is correct. I wish Satoshi had tried to document this stuff better. I got the impression that he didn't intend to leave the project when he did, but decided he was getting too much attention for his liking.
Let me try and explain why this is as designed.
A contract may contain contributions by multiple parties. The definition of IsNewerThan() says, in English,
- If the transaction has the same number of inputs which are connected to the same outputs (ie, only the script+seqno are different), and ...
- at least one of the new inputs sequence numbers is higher than all of the other sequence numbers
- ... then allow tx replacement
Here is a table showing iterations of the core loop of IsNewerThan():
bool fNewer = false;
unsigned int nLowest = std::numeric_limits::max();
for (int i = 0; i < vin.size(); i++)
{
if (vin[i].nSequence != old.vin[i].nSequence)
{
if (vin[i].nSequence <= nLowest)
{
fNewer = false;
nLowest = vin[i].nSequence;
}
if (old.vin[i].nSequence < nLowest)
{
fNewer = true;
nLowest = old.vin[i].nSequence;
}
}
}
OLD NEW nLowest fNewer
MAX f
input 1 1 1 MAX f
input 2 1 2 1 t
OLD NEW nLowest fNewer
MAX f
input 1 2 1 1 f
input 2 1 1 1 f
OLD NEW nLowest fNewer
MAX f
input 1 1 2 1 t
input 2 1 2 1 t
OLD NEW nLowest fNewer
MAX f
input 1 3 2 2 f
input 2 2 5 2 t
Because the sequence numbers are always zeroed out in SignatureHash(), this means that
any partner in the contract can issue a new version without breaking the signatures on the other inputs.
If the sequence number was a signed property of the transaction, changing it would invalidate all other signatures. If it was an unsigned property of the transaction, anyone could issue new versions of the whole transaction. By making it a signed property of the input, only participants in the contract can issue new versions, and they can do so without requiring the involvement of the other parties.
I haven't found any code that prevents transactions from using the outputs of non-final transactions in CTransaction::AcceptToMemoryPool. In the event of transaction replacement, such code will be necessary in case outputs are changed about.
The code appears to work, actually, though I've never tested it. It does however have a DoS attack (as is typical of Bitcoin).
You're allowed to depend on non-final transactions. If the tx is replaced, the dependees of that tx become orphans. They will stay in the memory pool forever because nothing deletes them. This is a general attack: you're allowed to fill up the memory pool with orphan transactions, it is independent of replacement, however replacing a TX on which other transactions depend can cause this too.
Other than wasting memory the code will not do anything incorrect. In any case, there is little legitimate reason to depend on a non final transaction because the moment it's replaced your tx becomes invalid. To reliably spend it you'd have to wait for the tx to finalize. There may be a use case for it I haven't thought of.
At the very least, we'll need to add output to RPC and possibly to the UI to show whether unconfirmed transactions are final or not.
Yes. It may be safest to add a new parameter, defaulted to false, that indicates whether to show non-final transactions.
Some form of discouragement should be undertaken to prevent miners from including versions of transactions that aren't the latest version.
Why would a miner want to do that? Unless the fees on the newer tx are lower, there's no reason not to just follow the standard rules here.
There are no incentives to forward multiple versions of a transaction; in fact, there is a disincentive: each time a new version is received, it must be validated before being accepted to the memory pool and forwarded.
You could just have a form of exponential backoff for updating transactions. If you try and replace a transaction way too frequently replacements would be dropped until it "cools down".