Proof of Work (Explained)
So you’ve read the theory, but want to understand Bitcoin programmatically. In this post we will analyse the
core codebase written in C++ to understand how one can generate and include a block in the chain. Starting with
the atomic unit of construction in this context, a block is a grouping of transactions that alter the state of
the ledger. For example, a coinbase
transaction may be included by a miner to collect a block reward. But in
order for these to be processed by the network, a miner must provide a proof of work for the serialization of
the following fields:
nVersion
hashPrevBlock
hashMerkleRoot
nTime
nBits
nNonce
The class definition of a block header is shown in the following snippet, of particular interest is SerializationOp
which tells the compiler how an instance of this class should be serialized and GetHash
which returns a 256-bit
unsigned integer.
The implementation of GetHash
calls another function SerializeHash
passing a dereferenced pointer to its context.
From here, CHashWriter
is established which computes the digest via the CHash256
class.
We can see that Finalize
writes and flushes the hash to the input buffer. It is important to note that
this actually computes SHA-256d
(double) in an effort to prevent “length-extension” attacks.
Assuming this process has given us a valid digest of our input, how can we convince other participants to
append our block to their view of the chain? This is where the consensus parameter bnTarget
comes in.
For brevity, we will ignore how this is chosen but I will note that this is dynamic - the network adapts it
based on mining speeds. Honest nodes will thus accept a gossiped block under the condition that the arithmetical
representation of its hash is less that that of the current target.
To recompute a new hash, we need some variability in the form of nNonce
. As there is no way to predict what input will lead to
a desired hash, all we can do is increment it. There’s a useful example of this in the following test helper.
Providing the final hash meets the criteria and enough nodes know about it, subsequent miners will include it as
hashPrevBlock
in their constructions.