In my opinion, it will make more sense to think of the blockchain as a well-ordered sequence of inputs with some latency.
Say I have smart contract C with a uint variable called C
Okay
contract Concurrency {
uint public c;
}
So far, so good.
Player A accesses this smart contract, reads its value as "4" from the current block, and increments C by 1. They then do a lot of interaction with the smart contract based upon the value of C now being "5" and broadcast this to the network.
This is conceptually misleading. c
is still 4. Let's imagine a function that does that. It will have to accept an input (1
) and do something with it.
function addUp(uint x) public {
c = c + x;
}
Great. So player A sent Concurrent.addUp(1)
. What's c
? It's still 4 and player 2 should know that. Why? Because the transaction hasn't been mined. It's "submitted", not "executed".
Player B accesses this same smart contract, also reads the value as "4" from the current block but increments C by 5 instead of 1. They also do extensive interaction with the smart contract assuming the value of C to be 9 after its increment, and also broadcast this to the network.
Okay, so Player B sends Concurrent.addUp(5)
. Again, this is only "submitted", not executed. Everyone agrees that the last known c
is 4
. These two players know their own transaction is in the queue and they also know there may be transactions from other players also in flight.
A block arrives.
A bunch of unrelated stuff, then from player B, +5 and then from player A, +1. I deviated from the expected order to illustrate that neither player controls that and there is no such assurance. So, c
goes:
4 + 5 = 9;
9 + 1 = 10; // this executes in the context of the previous transaction
When this block is processed, then everyone will agree that c
is 10
.
To make this more illustrative, let's say the game is like 21, only in this game players go bust above 9 and 9 is a perfect play.
function addUp(uint x) public {
c = c + x;
if(c > 9) playerIsBust();
if(c == 9) playerWins();
}
Now the order the network accepts the transactions is super-critical. Whichever player's move is processed first will win the game.
It's not a matter of a miner or a node computing c
for everyone else's benefit. Everyone will figure that out for themselves when they see the inputs in a block. The miners disambiguate the order of the transactions. Miners even have latitude about which transactions to include and in what order.
The simple example creates a race condition, which means player A and player B might be in a bidding war to entice miners to include their transactions first. This is the role of the gasPrice. Players determine the priority they're willing to pay for.
The players can conduct themselves as if they know what c
is, but it's probably an error. It's important not to proceed on faulty assumptions. They know that c
is probably 4
and they have submitted something that's awaiting inclusion in a block. They know other players may have already done things they don't know about. If the contact is well-crafted, it won't allow any game story to unfold if it's against the rules of the game.
Another way to summarize would be to say that transactions execute in the context of all the transactions that came before. Blocks are just batches of transactions that arrive more or less together. The first one runs completely. The second runs in the state left by the first. And so on.
There's a similar question over here. Same idea, different example: Ethereum Smart Contract execution principle
Hope it helps.