So say there is block_a, and block_b, both at height 1677. And a node accepts block_a, but then recieves block_b + block_c (which is mined on top of block_b), how exactly are the state changes made in block_a undone and reverted?
The idea is that you will not revert but build on a previous state instead.
Let's suppose you have the states Sn for block n. Each state only has the changes in that block and a reference to their parent, for example S101 points to S100. The main chain will be S101 -> S100 -> S99 -> ...
To check the balace of an address at block 101: Search address in S101, if the address exists return balance and stop. If doesn't exists search recursively in S100, S99, ...
Now suppose you have block 101' that will replace previous block. It will generate S101' that points to S100. After block 102 the main chain will be S102 -> S101' -> S100.
Orphan states like S101 can be discarded if they are no longer referenced by new blocks.
You already have the answer in your correct usage of the tag
Think about the Ethereum mainnet with thousands of nodes running around the world. A proper re-phrasing of the question would be:
Node_1 mines/ holds the top block as block_a at height 1677. Soon, it learns that there is a node Node_N in the network which has block_b at height 1677 and also a block called block_c at height 1678. Here's a list-diagram:
Node_1: ...block_a (height: 1677)
Node_N: ...block_b (height: 1677). block_c (height: 1678)
How does the Node_1 revert back and put on block_b and block_c?
The answer is simple- the nodes in the network use a simple heuristic that choose the node with the longest chain in the network. So, Node_1 will drop it's block_a i.e. go back to the state that it had at height 1666. Reverting back is just going back to the desired block number state of the Ethereum Virtual Machine. This is quiet easy, as a matter of fact, if you sync your node in
archive mode in Geth, you can debug the blockchain at any block number as if that were the height of the blockchain. So Node_1 will throw away block_a and then it will just catch up with Node_N in our scenario and very easily reflect the state of Node_N by putting on block_b followed by block_c at appropriate heights in the blockchain.
The real question is- because the Ethereum mainnet is trustless, what if someone tries to 'fake mine' and put false blocks, and that is where Proof of Work (PoW) algorithm comes in. Besides, Node_1 will verify the blocks it will put on top, so no one can actually 'fake mine' as 1. It takes a lot of energy to mine a block 2. A fake block is easily checked and will never be accepted by any node in the network. There is another interesting class of attack called 51% attack, which actually limits the possibility of other miners in the network to mine (but never to 'fake mine' or double spend.)
Here's a very good reference for an overview of the consensus mechanism in Ethereum.
If reorganization occurs, the diff of two block are computed. State changes made in block_a are reverted by executing the diff of block_a and block_b:
State(block_a) + [State(block_b) - State(block_a)] = State(block_b)
For more information, please refer to: https://github.com/ethereum/go-ethereum/blob/23524f8/core/blockchain.go#L2249