Recently, a well-known domestic technology association invited me to share my humble opinion on blockchain system with the technology circle. I found an interesting thing. Even students with computer background and programming skills do not know much about blockchain. Today, I intend to communicate with you in computer language, so that at least students with computer background can thoroughly understand what blockchain is and how it works.

However, before starting, one thing to be clear is that, unlike previous computer technologies, the core of blockchain technology is about the automatic supervision and governance of a computing system, not to make computing happen more efficiently or on a larger scale. We need to clarify this expectation so that we can understand why the blockchain is designed and works like this.

Pseudo code is a mixture of C + + and JavaScript syntax. It’s a little messy. Welcome to improve it

=================Warning split line==============

All right, here we go. High energy ahead.

We will take the most simplified encrypted digital currency as an example to introduce the precise working principle of blockchain. In order to facilitate understanding, we will omit fees, most optimization, interoperability and other aspects. Here, strongly typed pseudo code will be used to accurately define its data structure and execution logic. Here, we will implement a blockchain system similar to Ethereum digital currency from scratch. For ease of understanding, we will use the account state model adopted by Ethereum to represent the account book, rather than the utxo of bitcoin.

Let’s start with the definition of a series of basic entities and primitives:

Basic data type

class String; // Basic string data structure

class Blob; // Basic binary data is used to represent linear binary data after object serialization

class CriticalSecTIon; // Critical area, multithreaded mutually exclusive object

class BigInt; // Values in many parts of the blockchain are represented by large integers, such as balance, mining difficulty, etc.

//For example, use a 32 byte unsigned large integer to represent an integer from 0 to 2 ^ 256-1.

Digital signature primitive

In the standard asymmetric encryption system, public-private key pairs can be generated arbitrarily without networking, and are unique in the world. Typically 32 to 64 bytes of unstructured binary data. The public key will be disclosed and used in the blockchain system to indicate a specific identity for others to verify their control over a specific account. The private key is used to prove its control over the account through digital signature. The verifysignature primitive is used to verify whether the given data and signature are signed by the corresponding signer.

typedef BYTE PublicKey[32]; // Public key data

typedef BYTE PrivateKey[64];// Private key data

typedef BYTE Signature[64]; // Digital signature data

void Sign(Blob data, PrivateKey sk, Signature sigdata); // digital signature

bool VerifySignature(Blob data, PublicKey pk, Signature sigdata); // Check that the digital signature is correct

Account address

In our example here, all hash functions use sha256, which will produce a 32 byte hash value. The address is the identifier of the account. It is a 32 byte unstructured binary data, which is obtained from the hash value sha256 (publickey) of the public key. In other words, each public key corresponds to a unique address and a unique account.

What is the minimized blockchain system

typedef BYTE HashValue[32]; // Hash value of sha256

typedef HashValue Address; // Account address

HashValue SHA256(Blob data); // Sha256 hash function

Smart contract

This is a bit like a C + + class, which defines some states and functions to modify these states. In a blockchain system, multiple smart contracts can exist at the same time, but there will only be one instance of each. Here we give an example of an extremely simplified smart contract for digital currency:

class MyCoin

{

// internal state

hash_ map《Address, BigInt》 _ Ledger;

// internal funcTIon

BigInt _ GetBalance(Address addr)

{

if(_Ledger.has(addr))return _ Ledger[addr];

else return 0;

}

//Transfer function

void Transfer(Address signer, Address from, Address to, BigInt amount)

{

if(signer != from)return;

if(amount 》 0 && _GetBalance(from) 》= amount)

{

_ Ledger[from] -= amount;

amount += _ GetBalance(to);

_ Ledger[to] = amount;

}

}

//Mining reward function

void CoinBase(int height, Address miner)

{

BigInt reward = 5000000000; // Here it is simplified to 50 coins each time

if(reward 》 0)

{

reward += _ GetBalance(miner);

_ Ledger[miner] = reward;

}

}

};

Transaction

A transaction represents a status modification request for a specific related account. The transaction does not carry any logic code, but only specifies which public function and its call parameters in the smart contract will be called by the transaction. Of course, in our extremely simplified system, there is only one transaction, that is, the previous transfer. The initiator of the transaction must be the deduction Party (from), and the whole transaction carries the digital signature of the transaction content to make sure that the transaction is initiated by the deduction party. Based on our example here, a transaction contains at least the following structure:

struct TransacTIon

{

String InvokeFunctionName; // It is always “transfer” here

Blob InvokeArguments; // Call parameters after serialization

PublicKey Signer; // The initiator’s public key. Note that this is not an address

Signature SignData; // The signature of a transaction by the initiator’s private key

};

Block

A block represents a step in the execution of block link force, which mainly includes a batch of transactions confirmed in this step, as well as the verification data of consensus mechanism and block header metadata. The simplest definition can be as follows:

struct Block

{

int Timestamp; // Block out time

HashValue PrevBlock; // Hash value of the previous block

Address Miner; // Miner’s address

int TxnCount; // Number of transactions contained in this block

Transaction Txns[TxnCount]; // Complete transaction list

BigInt PowTarget; // Objective of workload demonstration (consensus verification data)

int PowNonce; // Nonce value of workload proof (consensus verification data)

};

Here we give the most simplified proof of work verification data structure. If other consensus algorithms are adopted, this part will change. It can be seen from this structure that the reason why a blockchain is called a chain is that the blockstructure contains a “pointer” to the previous block, prevblock. Any confirmed block also means to recognize all its precursor blocks and all transactions carried by these blocks. There are three conditions for a block to be confirmed:

1. The consensus verification of this block shall meet the requirements of its specific consensus algorithm. In the workload proof algorithm, powtarget must be less than the requirements of the current mining difficulty, and ((bigint &) sha256 (block)) block:: powtarget.

2. The transactions contained in this block must not be included in the previous block, and each transaction must be able to ensure that its digital signature can be correctly verified by its signer’s public key. It doesn’t matter whether the logic executed by the exchange is correct or wrong.

3. Among all bifurcated blocks, that is, blocks with the same prevblock, only the priority block will be confirmed. Different consensus algorithms have different situations.

P2P communication primitive

The network layer of blockchain only uses the simple part of P2P network technology, and uses the gossip protocol based on TCP long connection to realize the whole network broadcasting of a data block. Here we abstract it from the following communication primitives:

interface BroadcastNetwork

{

template《typename T》

void Broadcast(const T& object); // Serialize the object and broadcast it

function OnRecvBlock; // A block callback function was received

function OnRecvTransaction; // Callback function received for a transaction

};

Memory pool primitive

The memory pool is used in the blockchain system to record transactions that have not been confirmed, which is easy to implement, such as hash table.

interface Mempool

{

bool Has(Transaction txn);

void Insert(Transaction new_txn);

void Remove(Transaction txns[count]);

int Collect(Transaction txns[max_count]);

};

The collect primitive is used to synthesize new blocks during mining, select a series of transactions from MemPool to fill the txns array, select txnmaxcount at most, and return the actual filled number.

Block archive database primitive

After the blocks and transactions in the blockchain system are confirmed, they will be removed from memory and written into the archive database. This part is easy to implement with a key value storage system. Of course, it is also possible to use SQL data, but it is less efficient.

interface ArchiveDatabase

{

void Archive(Transactiontxns[count]);

void Archive(Block blk);

void Has(Transaction txn);

void Has(Block blk);

}

With these definitions, we can give a pseudo code of the simplest blockchain system based on workload proof without considering bifurcation:

static const int TARGET_ ADJUST_ INTERVAL = 256; // Adjust the calculation difficulty every 256 blocks

static const int BLOCK_ CREATION_ INTERVAL = 600*1000; // One block every ten minutes

static const int TRANSCATION_ PERBLOCK_ MAX = 1024; // Each block contains up to 1024 transactions

BroadcastNetwork* g_ pNet = BroadcastNetwork::Create(。。.);

Mempool* g_ pMempool = Mempool::Create(。。.);

ArchiveDatabase* g_ pArchiDB = ArchiveDatabase::Create(。。.);

MyCoin g_ MyLedger; // Account books

//Current blockchain header

Block g_ BlockHead = Block::GenesisBlock(6); // Initialize to start block

HashValue g_ BlockHeadHash = SHA256(g_BlockHead);

int g_ BlockNextHeight = 1;

CriticalSection g_ BlockHeadCS;

//Consensus related information of the next block (proof of workload)

PowTarget g_ NextPowTarget = Block::InitialPowTarget(); // Initial mining difficulty

int g_ LastTargetAdjustedTime;

//Transaction received from webcast

g_ pNet-》 OnRecvTransaction = [](Transaction txn) {

if(g_pMempool-》Has(txn) || g_pArchiDB-》Has(txn))

return; // Ignore existing transactions

if(!VerifySignature(

txn.InvokeFunctionName + txn.InvokeArguments +txn.Signer,

txn.Signer,

txn.Signature

)return;// Verify that the signature is correct

g_ pNet-》Broadcast(txn); // After basically verifying the legitimacy, relay the broadcast of this transaction

g_ pMempool-》Insert(txn);

};

//Block received from webcast

g_ pNet-》 OnRecvBlock = [](Block blk) {

if(blk.PrevBlock != g_BlockHeadHash)

return; // Ignore out of order blocks, ignore forked blocks

if(blk.PowTarget 》 g_NextPowTarget)

return; // Ignore blocks that do not meet the current force requirements

if(blk.TxnCount 》 TRANSCATION_PERBLOCK_MAX)

return; // Ignore too large blocks

HashValue h = SHA256(blk);

if( ((BigInt&)h) 》= blk.PowTarget )

return; // Blocks that do not meet the current nominal force requirements are ignored

//Verify transactions in all blocks

for(Int32 i=0; i《blk.TxnsCount; i++)

{

auto& txn = blk.Txns[i];

If (g_parchidb – “has (TxN) | / / contains transactions that have been confirmed before

! VerifySignature(

txn.InvokeFunctionName + txn.InvokeArguments +txn.Signer,

txn.Signer,

txn.Signature

)//Including transactions with failed signature verification

)return;

}

//So far, this block has been confirmed

g_ pNet-》Broadcast(txn); // After confirmation, relay the broadcast of this block as soon as possible

g_ MyLedger.CoinBase(g_BlockNextHeight, Miner); // Execute block out reward

For (Auto & TxN: BLK. Txns) / / execute each transaction and archive it

{

//Call the function specified in the transaction

g_ MyLedger[txn.InvokeFunctionName](txn.Signer, txn.InvokeArguments…);

g_ pArchiDB-》Archive(txn);

g_ pMempool-》Remove(txn); // Delete from the memory pool, if present

}

g_ pArchiDB-》Archive(g_BlockHead); // Archive previous block

//Update the blockchain header. This part of the code needs to be mutually exclusive with the steps of constructing new blocks in the mining process

g_ BlockHeadCS.Lock();

{

if(g_BlockNextHeight%TARGET_ADJUST_INTERVAL == 1)

{/ / adjust the calculation force, and the cycle is target_adjust_interval blocks

if(g_BlockNextHeight 》 1)

{ g_NextPowTarget = PowTargetAdjustment(

g_ NextPowTarget,

blk.Timestamp – g_ LastTargetAdjustedTime

);

}

g_ LastTargetAdjustedTime = blk.Timestamp;

}

//Update the blockchain header in the latest block

g_ BlockHeadHash = h;

g_ BlockHead = blk;

g_ BlockNextHeight++;

}

g_ BlockHeadCS.Unlock();

};

This involves an algorithm not defined above. Powtargetadjustment is used to adjust the calculation force difficulty requirements of blocks according to the recent block output speed, so that the expectation of obtaining the average interval of blocks can be generally stable at a preset value (block_creation_interval). This is an algorithm related to workload proof consensus algorithm, which is not available in all blockchain systems. A simplified definition of this algorithm is as follows:

Calculation difficulty adjustment

BigInt PowTargetAdjustment(BigInt cur_target, int nth_block_interval)

{

return cur_ target*nth_ block_ interval/(BLOCK_CREATION_INTERVAL*TARGET_ADJUST_INTERVAL);

}

Here, a blockchain node without block, that is, all nodes, can work. All nodes are most nodes in the blockchain network, which ensures the stable and robust operation of the underlying P2P network of the blockchain. At the same time, it also realizes the highly redundant whole network storage of block data and transaction data. Although there is no block, the whole node is different from the client of the Internet architecture. A whole node does not need to trust other nodes, and there is no server. The whole node can independently verify the complete historical evolution process of the blockchain, and then reconstruct the status on it (such as the balance of an account), rather than querying a server that needs to be trusted.

Of course, the computing relay process of blockchain network is completed by the out of block node, that is, the so-called miner node. These few nodes are mixed with a large number of all nodes. Most nodes receive the latest blocks from relay broadcasts from other all nodes, rather than directly from an outgoing node. Of course, as the receiver, it is impossible to judge whether the sender is the relay all node or the miner node just out of the block. This also effectively protects the security of the real outbound node and avoids exposing the physical IP address of the miner node.

An outbound node, first of all, is a full node. In addition to these behaviors defined above, an additional process is required to run on one or more threads. We define the most simplified deblocking process as follows:

void Mining()

{

while(g_KeepMining)

{

//To construct a new block, this part needs to be mutually exclusive with the blockchain header update code

g_ BlockHeadCS.Lock();

{

int next_ height = g_ BlockNextHeight;

Block new_ block;

new_ block.Timestamp = os::GetCurrentTime();

new_ block.PrevBlock = g_ BlockHeadHash; // Point to the latest block

new_ block.Miner = g_ MyAddress;

new_ block.TxnCount = g_ pMempool-》Collect(new_block.Txns[TRANSCATION_PERBLOCK_MAX]);

new_ block.PowTarget = g_ NextPowTarget;

new_ block.PowNonce = os::Random《Int64》(); // Random initial value

}

g_ BlockHeadCS.Unlock();

//Start mining

while(next_height == g_BlockNextHeight)

{

if( ((BigInt64&)SHA256(new_block)) 《 new_block.PowTarget )

{

//Mining success

g_ pNet-》Broadcast(new_block); // Broadcast it immediately

g_ pNet-》OnRecvBlock(new_block); // Update the blockchain header of this node

break; // Start digging the next block

}

new_ block.PowNonce++; // Try the next nonce

}

//In most cases, other nodes create new blocks and update the block height

//The mining is interrupted to dig the next block after the update

}

}

Responsible editor: CT

Leave a Reply

Your email address will not be published. Required fields are marked *