Bitcoin doesn't track a balance on an address. You can get a balance but that is for humans and isn't how the protocol really works. What bitcoin nodes do track is a list of unspent transaction outputs.
Here is your scenario reworded a bit.
BLOCK 108
address ddd111 received 2 BTC by mining block 108 in transaction A:0
(By this I mean transaction A has one output (index 0) that is 2 BTC to address ddd111)
Transaction A:
Inputs: coinbase
Outputs: A:0 - ddd111 2.0 BTC
BLOCK 109
address abc123 received 2 BTC by mining block 109 in transaction B
Transaction B
Inputs: coinbase
Outputs: B:0 - abc123 2.0 BTC
BLOCK 110
The network knows nothing about address with no funds, this doesn't happen
address xyz999 created with 0 BTC
address fff555 created with 0 BTC
address abc123 spends B:0, sending 1 BTC to xyz999 (C:1) and remainder to self (1 BTC) (C:0) transaction C
Transaction C
Inputs: B:0 (signed by abc123)
Outputs: C:0 - abc123 1.0 BTC
C:1 - xyz999 1.0 BTC
address abc123 spends C:0, sending 1 BTC to fff555 transaction D
Transaction D
Inputs: C:0 (signed by abc123)
Outputs: D:0 - fff555 1.0 BTC
BLOCK 111
address ddd111 spends transaction A sending xyz999 1 BTC and 1 BTC back to self - transaction E
Transaction E
Inputs: A:0 (signed by ddd111)
Outputs: E:0 - xyz999 1.0 BTC
E:1 - ddd111 1.0 BTC
BLOCK 112
address xyz999 spends transaction E:0 and C:1 to send bbb888 2 BTC - transaction F
Transaction F
Inputs: E:0 (signed by xyz999)
C:1 (signed by xyz999)
Outputs:
F:0 - bbb888 2.0 BTC
(xyz999 couldn't form a valid transaction sending 5 BTC because there aren't 5 BTC of unspend outputs for it to use)
Note: A transaction can have multiple outputs which can be spent independantly.
Now we have this list after block 112:
Unspent after block 112
D:0
F:0
E:1
Each node's job is to import the block by marking the spent outputs as spent and tracking the new unspent outputs. This is part of why it takes time to sync the blockchain, not only do the blocks need to be downloaded but they need to be validated and the local database needs to be updated.
Note: if you run a node that prunes, it will just keep the unspent outputs. If you don't prune, it will keep all blocks and the unspent outputs.