nochowderforyou / clams

Clam Project
MIT License
62 stars 58 forks source link

client cannot switch to longest chain when one output stakes twice on fork #273

Open dooglus opened 8 years ago

dooglus commented 8 years ago

I was playing about with CLAM's testnet. It had been idle for a few weeks, so difficulty was very low and wallets were staking every 16 seconds as difficulty adjusted.

I had 2 wallets staking independently. Both were staking every 16 seconds, building 2 separate forks in parallel. I figured that once difficulty had adjusted high enough that one of them failed to stake just once in a 16 second time period the other would "win", and the loser would do a massive reorg to switch to the winning chain. That's how it's meant to work.

What actually happened was the losing wallet kept on working on its shorter chain. Both wallets continued working on different chains, even though they were different lengths.

I tried making a bootstrap.dat on the 'winning' client (which still had the longest chain) and importing it on the 'losing' client. But that didn't help either. The losing client refused to switch to the longer chain even when presented with the full longer chain in order.

It turned out the reason is that the 'winning' fork contained two blocks staked by the same output (N -> N+1 in block x, and N+1 -> N+2 in block y). Blocks x and y aren't in the main chain on the losing client. The losing client accepts block x and a block but doesn't put it on the main chain yet (because it isn't the longest chain yet), but when it sees block y, CheckProofOfStake() in kernel.cpp rejects it because the output being staked isn't in the "main chain":

    // First try finding the previous transaction in database
    CTxDB txdb("r");
    CTransaction txPrev;
    CTxIndex txindex;
    if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
        return tx.DoS(1, error("CheckProofOfStake() : INFO: read txPrev failed"));  // previous transaction not in main chain, may occur during initial download

ie. we can't find the staking transaction in the txdb because it isn't on the main chain yet.

I wasn't able to get the 'losing' client to sync without deleting the blockchain and reimporting the whole thing from a bootstrap.dat file. (Letting it sync from scratch over the p2p network would have presumably worked too).