Corner case: ineffective self-spawn tx

As @dmitry pointed out in this thread, there’s a possible corner case in the tx design where a self-spawn tx that’s included in a block and thus “consumed”, but later made ineffective (e.g., due to a reorg that causes the account not to have enough funds to cover the spawn), can never be self-spawned. This is due to the fact that the nonce (self-spawn always uses a zero nonce) and txid have been consumed.

Here are some possible responses/workarounds:

  1. Ignore this, it should be very rare; and in any case an ordinary (i.e., non-self) spawn could still be used to spawn the account in question (once support is added for this)
  2. Allow a self-spawn tx to use a nonce > 0
  3. Make an exception such that a self-spawn tx that’s invalid is in fact totally ignored by the protocol (and, thus, the same tx could be broadcast again later)
  4. Remove the nonce from a self-spawn tx entirely, and add a salt or similar so that the txid can be unique

I like option (4).

But do we actually need a salt? Self-spawn transactions can only be executed for accounts in a “stubbed” state (there’s a balance, but the account hasn’t been spawned yet). You can never execute a successful self-spawn twice for the same account, since the system verifies that the corresponding account is stubbed.

I don’t think the issue is executing a successful self-spawn tx. On the contrary, the issue is when a self-spawn tx fails, but consumes the zero nonce.

Thinking about this further, I don’t think option (4) works - it might allow for a unique txid but doesn’t solve the problem of the nonce being consumed. I think @noam is planning to add a nonce to the self-spawn tx to address this.

This raises a related question though: do we allow a failed self-spawn tx to “un-stub” the account? (Incrementing the nonce would require this.)

I don’t see why option (4) wouldn’t work. There’s no nonce, so there’s nothing to consume. The idea is that the nonce is only initialized to 0 after spawn succeeds. Replay protection for self-spawn doesn’t require a nonce — it doesn’t even require storing the txid, since nodes have to store the accountid and state anyway.

We need only consider the case where a tx can cover its intrinsic gas cost, but cannot cover max-gas: this is the case discussed in this issue and cases 4.2.3 and 7.2.3 in the tx status lifecycle. This is truly a corner case, since if the account in question has zero balance then the tx would be discarded and this would not happen.

In these cases, the nonce (implicitly or explicitly) is consumed and the tx cannot be replayed later.

We could probably come up with some clever, harebrained scheme (pun intended) that would not require a nonce, but I think the simplest thing to do here is just to add an explicit nonce to the self-spawn tx as @noam proposed (and assume that it’ll be zero 99.99% of the time). This solves our problem, makes the self-spawn tx look more like a regular tx, and has the added benefit that the txid will not reappear.

I still don’t understand why we need a nonce for self-spawn. Here’s the extremely simple, tortoisebrained scheme for dealing with a self-spawn tx for account id:

if (id does not exist or id.balance < intrinsic self-spawn balance):
    drop tx
else if (id has already been spawned):
    drop tx
else: // id is stubbed
   attempt to run id.spawn
   // if spawn was unsuccessful do nothing.

In almost all cases, if we reach “attempt to run spawn” it will be successful. In fact, it can only happen due to user error — there’s no way to extract funds from a stubbed account until it’s spawned, so there are only two possibilities: either it did have enough to cover a successful spawn (in which case we’re not in the corner) or it didn’t, in which case the user shouldn’t try to submit a self-spawn transaction.

I agree that there’s a corner case when the account balance is sufficient for intrinsic gas but not to run the computation for spawn itself (this gets us to the last commented line). In this case, since we don’t have a nonce, the exact same transaction can be repeated.

However, the very nature of the problem upper bounds the damage that can be done: at worst, a malicious actor can empty the account by repeating the user’s bad spawn transaction — but since there’s very little balance in the account, it doesn’t cost the user much.
This is an extremely unlikely and low-value attack, so I’m not worried.

Just to make sure I understand your (@noam’s?) proposal, let me try to rephrase, and correct me if I’m mistaken:

  • You propose that every stubbed account, in addition to a balance, also stores a nonce.
  • The self-spawn transactions would have a nonce as well
  • we use the counter-with gaps mechanism to prevent repeats.
  • If a spawn succeeds, the newly-spawned account would keep the nonce from the stubbed account.

I can live with this solution as well.

Sounds accurate to me. In addition to preventing the “low-value” replay attack you describe above, I’d very much like to maintain the invariant that a given txid is only ever effective once (by “effective” I mean “spends gas”). Without this, an identical tx (same txid) can spend gas multiple times.

The corner case under discussion can be actually turned into a regular attack that would work this way:

  • a malicious user M (think “a bot”) is constantly observing the blockchain
  • every time M notices a self-spawn transaction T that was able to cover intrinsic gas but was ineffective (this will be publicly visible), M starts a repeat attack and continues as al long as the balance of T.principal is drained

The attacked will not gain any profit by doing so. Also, every individual user under attack will not regard this as a big damage. But is would be so annoying to see this attack happening for decades and be unable to stop it.

  • You propose that every stubbed account, in addition to a balance, also stores a nonce.
  • The self-spawn transactions would have a nonce as well
  • we use the counter-with gaps mechanism to prevent repeats.
  • If a spawn succeeds, the newly-spawned account would keep the nonce from the stubbed account.

I agree this is a good solution.
It also works OK after migrating to new nonce semantics (whatever it will be) - as long as the stub is able to store whatever is the tx nonce.

BTW, as we are talking about stubs, let me point your attention that there is non-trivial interference between stubs model and Muggle/Elf/Wizard model: because a stub has only one balance, how does this fits into the split balance model of accounts, as we plan it ?

A short analysis of this:

  • self-spawn cannot be supported for Elf account by the very definition of Elf (cannot pay gas)
  • self-spawn works “naturally” for Muggle (because there is no split balance there)
  • self-spawn is not compliant with Wizard account semantics; we have 3 options:
    • disallow self-spawn of Wizard accounts
    • modify the model of stubs so to include split balance at the stub level
    • have special variant of self-spawn for Wizard accounts - the one which combines charging GasPurse and account creation as one atomic operation

Overall I think that disallowing self-spawn for Wizard accounts is probably best option. Self-spawn is mostly a convenience path of account creation for “small” users. If someone wants the power of WizardAccount, creating one via a normal spawn should not be a big deal.