Relay blocks and update active chain

14 minute read
Subscribe to the mailing list!

Project: Let's build Bitcoin

In the previous blog post, we’ve explained how mining process uses the “get block template” protocol. When miner finds a block it submits it to the node it’s connected to.

However, no other nodes know about the mined block. In this post, we will extend the protocol to allow nodes to relay mined blocks to its peers. We will also implement the consensus algorithm to ensure all nodes agree on the active chain eventually.

Relay block

As explained in the previous blog post, a miner submits new blocks to the node via the JSON-RPC protocol. The node stores the block in the block index and the storage. Additionally, the node should notify all of its peers about it.

Extend the protocol

The protocol already supports a message to send a full block to a peer, i.e. Block message. However, the existing message is sent in response to the GetBlock message. Therefore, we will introduce a new message called RelayBlock to clearly indicate that it’s sent as a result of a miner finding a new block.

// learncoin/src/peer_message.rs

//...

pub enum PeerMessagePayload {
    // ...
    BlockRelay(Block),
}

Relay the mined block

A node relays the block to its peers as soon as it’s mined, assuming it’s valid. The node trusts the miner in our setup (for now), so we will skip the validation and send the new block immediately. With that said, we will implement the validation step for mined blocks in future blog posts.

// learncoin/src/learncoin_node.rs

// ...

impl LearnCoinNode {
    // ...

    fn on_submit_block(&mut self, peer_address: &str, id: u64, block: Block) {
        // ...
        } else { // 
            // ... block added to the index and the storage.
            self.network
                .send_to_all(&PeerMessagePayload::BlockRelay(block));
        }
    }
}

Handle relayed blocks

Let’s now implement the handler for relayed blocks.

There are three possible states the the node needs to handle upon receiving the relayed block.

The relayed block doesn’t connect

Firstly, the relayed block doesn’t connect with the node’s blockchain, i.e. the node doesn’t know about the block’s parent. In this case, the node ignores the relayed block and sends the GetHeaders message to the peer. The message starts the initial block download process with that peer, downloading the missing block headers. Upon receiving the missing block headers, the node sends GetBlockData message to get the full data, same as the IBD process.

// learncoin/src/learncoin_node.rs

// ...

impl LearnCoinNode {
    // ...

    fn on_relay_block(&mut self, peer_address: &str, block: Block) {
        let mut peer_state = self.peer_states.get_mut(peer_address).unwrap();
        if !self
            .block_index
            .exists(&block.header().previous_block_hash())
        {
            // If the block doesn't connect, request headers and let the IBD process sync the
            // block.
            let block_locator_object = self.block_index.locator(&peer_state.last_common_block);
            self.network.send(
                peer_address,
                &PeerMessagePayload::GetHeaders(block_locator_object),
            );
            return;
        }
    }
}

The block and its parent already exist

Secondly, the block and its parent already exist in the node’s blockchain. For example, multiple peers relay the same block or the peer echos back the relayed block. In this case, the node ignores the relayed block.

fn on_relay_block(&mut self, peer_address: &str, block: Block) {
    // ...

    if self.block_index.exists(block.id()) {
        // If the block already exists, ignore it.
        return;
    }
}

The block doesn’t exist

Thirdly, the block doesn’t exist in the node’s blockchain. The node adds the block ti the index and the storage, then validates it. If the block is valid, the node relays it to its peers. The node is allowed to relay it back to the sender, who will ignore it because it knows about it.

fn on_relay_block(&mut self, peer_address: &str, block: Block) {
    // ...

    // The block connects.
    self.block_index.insert(block.header().clone());
    self.block_storage.insert(block.clone());
    // TODO: Validation
    self.network
        .send_to_all(&PeerMessagePayload::BlockRelay(block));
}

Run the blockchain

That’s it! Nodes relay the mined blocks now, so let’s see it in action. Let’s start two nodes with one miner connected to the first one. The miner will find a block eventually, submit it to its server, and the server relays it to the network. On the other side, we expect the second node to receive the mined block and relay it.

Let’s start the first node and the miner.

./learncoin server --address 127.0.0.1:8333
New peers connected: [
    "127.0.0.1:57609",
]
[127.0.0.1:57609] Recv: Version(
    VersionMessage {
        version: 1,
    },
)
[127.0.0.1:57609] Send: Verack
[127.0.0.1:57609] Recv: JsonRpcRequest(
    JsonRpcRequest {
        id: 0,
        method: GetBlockTemplate,
    },
)
[127.0.0.1:57609] Send: JsonRpcResponse(
    JsonRpcResponse {
        id: 0,
        result: Ok(
            BlockTemplate(
                BlockTemplate {
                    previous_block_hash: BlockHash(
                        00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
                    ),
                    transactions: [],
                    difficulty_target: 28,
                    height: 0,
                    public_key_address: PublicKeyAddress,
                    current_time: 1638118158,
                },
            ),
        ),
    },
)
[127.0.0.1:57609] Recv: JsonRpcRequest(
    JsonRpcRequest {
        id: 1,
        method: GetBlockTemplate,
    },
)
[127.0.0.1:57609] Send: JsonRpcResponse(
    JsonRpcResponse {
        id: 1,
        result: Ok(
            BlockTemplate(
                BlockTemplate {
                    previous_block_hash: BlockHash(
                        00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
                    ),
                    transactions: [],
                    difficulty_target: 28,
                    height: 0,
                    public_key_address: PublicKeyAddress,
                    current_time: 1638118159,
                },
            ),
        ),
    },
)

Here’s the output of the miner running in parallel.

./learncoin miner --server 127.0.0.1:8333

[127.0.0.1:8333] Send: Version(
    VersionMessage {
        version: 1,
    },
)
[127.0.0.1:8333] Recv: Verack
[127.0.0.1:8333] Send: JsonRpcRequest(
    JsonRpcRequest {
        id: 0,
        method: GetBlockTemplate,
    },
)
[127.0.0.1:8333] Recv: JsonRpcResponse(
    JsonRpcResponse {
        id: 0,
        result: Ok(
            BlockTemplate(
                BlockTemplate {
                    previous_block_hash: BlockHash(
                        00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
                    ),
                    transactions: [],
                    difficulty_target: 28,
                    height: 0,
                    public_key_address: PublicKeyAddress,
                    current_time: 1638118158,
                },
            ),
        ),
    },
)
[127.0.0.1:8333] Send: JsonRpcRequest(
    JsonRpcRequest {
        id: 1,
        method: GetBlockTemplate,
    },
)
Active template, mining. Start: 0. Stop: 100000000
[127.0.0.1:8333] Recv: JsonRpcResponse(
    JsonRpcResponse {
        id: 1,
        result: Ok(
            BlockTemplate(
                BlockTemplate {
                    previous_block_hash: BlockHash(
                        00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
                    ),
                    transactions: [],
                    difficulty_target: 28,
                    height: 0,
                    public_key_address: PublicKeyAddress,
                    current_time: 1638118159,
                },
            ),
        ),
    },
)
[127.0.0.1:8333] Send: JsonRpcRequest(
    JsonRpcRequest {
        id: 2,
        method: GetBlockTemplate,
    },
)

Let’s connect the second node and wait until the miner submits a new block.

./learncoin server --address 127.0.0.1:8334 --peers "127.0.0.1:8333"

[127.0.0.1:8333] Send: Version(
    VersionMessage {
        version: 1,
    },
)
[127.0.0.1:8333] Recv: Verack
[127.0.0.1:8333] Send: GetHeaders(
    BlockLocatorObject {
        hashes: [
            BlockHash(
                00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
            ),
        ],
    },
)
[127.0.0.1:8333] Recv: Headers(
    [],
)
Initial headers sync complete.
[127.0.0.1:8333] Send: GetHeaders(
    BlockLocatorObject {
        hashes: [
            BlockHash(
                00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
            ),
        ],
    },
)
[127.0.0.1:8333] Recv: Headers(
    [],
)

The difficulty is not high, so the miner should find one quickly. Eventually, the miner finds a block and submits it to the server.

# ... miner output continuation
[127.0.0.1:8333] Send: JsonRpcRequest(
    JsonRpcRequest {
        id: 3,
        method: SubmitBlock(
            Block {
                id: BlockHash(
                    00000007f8528228ba31c716139b697836a249c21ad2f8b07b4e8a60ef49f0c7,
                ),
                header: BlockHeader {
                    previous_block_hash: BlockHash(
                        00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
                    ),
                    merkle_root: MerkleHash(
                        c6d96f868d2c9068c0865d38d14873a0b62a3d03af1add517af1b494a527baf8,
                    ),
                    timestamp: 1638118159,
                    difficulty_target: 28,
                    nonce: 21073642,
                },
                transactions: [
                    Transaction {
                        id: TransactionId(
                            69bce1ce26f67aa8244741000b3ae00dfacb1f20169bb0f3b677ddf29a1da0f8,
                        ),
                        inputs: [
                            TransactionInput {
                                utxo_id: TransactionId(
                                    0000000000000000000000000000000000000000000000000000000000000000,
                                ),
                                output_index: OutputIndex(
                                    -1,
                                ),
                                unlocking_script: UnlockingScript,
                            },
                        ],
                        outputs: [
                            TransactionOutput {
                                locking_script: LockingScript,
                                amount: 50,
                            },
                        ],
                    },
                ],
            },
        ),
    },
)
[127.0.0.1:8333] Recv: JsonRpcResponse(
    JsonRpcResponse {
        id: 2,
        result: Ok(
            BlockTemplate(
                BlockTemplate {
                    previous_block_hash: BlockHash(
                        00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
                    ),
                    transactions: [],
                    difficulty_target: 28,
                    height: 0,
                    public_key_address: PublicKeyAddress,
                    current_time: 1638118231,
                },
            ),
        ),
    },
)
[127.0.0.1:8333] Send: JsonRpcRequest(
    JsonRpcRequest {
        id: 4,
        method: GetBlockTemplate,
    },
)

The miner successfully finds the next block 00000007f8528228ba31c716139b697836a249c21ad2f8b07b4e8a60ef49f0c7, which corresponds to the given diffiiculty target of 28 (7 leading zeroes). The previous block hash is 00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1, which is the genesis block. The mined block has only the coinbase transaction that produces 50 coins.

The miner immediately requests a new block template, so it can start mining the next block.

In the meantime, let’s have a look at what’s the first node doing.

# ... first node output continuation

[127.0.0.1:57609] Recv: JsonRpcRequest(
    JsonRpcRequest {
        id: 3,
        method: SubmitBlock(
            Block {
                id: BlockHash(
                    00000007f8528228ba31c716139b697836a249c21ad2f8b07b4e8a60ef49f0c7,
                ),
                header: BlockHeader {
                    previous_block_hash: BlockHash(
                        00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
                    ),
                    merkle_root: MerkleHash(
                        c6d96f868d2c9068c0865d38d14873a0b62a3d03af1add517af1b494a527baf8,
                    ),
                    timestamp: 1638118159,
                    difficulty_target: 28,
                    nonce: 21073642,
                },
                transactions: [
                    Transaction {
                        id: TransactionId(
                            69bce1ce26f67aa8244741000b3ae00dfacb1f20169bb0f3b677ddf29a1da0f8,
                        ),
                        inputs: [
                            TransactionInput {
                                utxo_id: TransactionId(
                                    0000000000000000000000000000000000000000000000000000000000000000,
                                ),
                                output_index: OutputIndex(
                                    -1,
                                ),
                                unlocking_script: UnlockingScript,
                            },
                        ],
                        outputs: [
                            TransactionOutput {
                                locking_script: LockingScript,
                                amount: 50,
                            },
                        ],
                    },
                ],
            },
        ),
    },
)
[127.0.0.1:57609] Recv: JsonRpcRequest(
    JsonRpcRequest {
        id: 4,
        method: GetBlockTemplate,
    },
)
[127.0.0.1:57609] Send: JsonRpcResponse(
    JsonRpcResponse {
        id: 3,
        result: Ok(
            Notification,
        ),
    },
)
[127.0.0.1:57609] Send: BlockRelay(
    Block {
        id: BlockHash(
            00000007f8528228ba31c716139b697836a249c21ad2f8b07b4e8a60ef49f0c7,
        ),
        header: BlockHeader {
            previous_block_hash: BlockHash(
                00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
            ),
            merkle_root: MerkleHash(
                c6d96f868d2c9068c0865d38d14873a0b62a3d03af1add517af1b494a527baf8,
            ),
            timestamp: 1638118159,
            difficulty_target: 28,
            nonce: 21073642,
        },
        transactions: [
            Transaction {
                id: TransactionId(
                    69bce1ce26f67aa8244741000b3ae00dfacb1f20169bb0f3b677ddf29a1da0f8,
                ),
                inputs: [
                    TransactionInput {
                        utxo_id: TransactionId(
                            0000000000000000000000000000000000000000000000000000000000000000,
                        ),
                        output_index: OutputIndex(
                            -1,
                        ),
                        unlocking_script: UnlockingScript,
                    },
                ],
                outputs: [
                    TransactionOutput {
                        locking_script: LockingScript,
                        amount: 50,
                    },
                ],
            },
        ],
    },
)
[127.0.0.1:57609] Send: JsonRpcResponse(
    JsonRpcResponse {
        id: 4,
        result: Ok(
            BlockTemplate(
                BlockTemplate {
                    previous_block_hash: BlockHash(
                        00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
                    ),
                    transactions: [],
                    difficulty_target: 28,
                    height: 0,
                    public_key_address: PublicKeyAddress,
                    current_time: 1638118246,
                },
            ),
        ),
    },
)
[127.0.0.1:57618] Recv: BlockRelay(
    Block {
        id: BlockHash(
            00000007f8528228ba31c716139b697836a249c21ad2f8b07b4e8a60ef49f0c7,
        ),
        header: BlockHeader {
            previous_block_hash: BlockHash(
                00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
            ),
            merkle_root: MerkleHash(
                c6d96f868d2c9068c0865d38d14873a0b62a3d03af1add517af1b494a527baf8,
            ),
            timestamp: 1638118159,
            difficulty_target: 28,
            nonce: 21073642,
        },
        transactions: [
            Transaction {
                id: TransactionId(
                    69bce1ce26f67aa8244741000b3ae00dfacb1f20169bb0f3b677ddf29a1da0f8,
                ),
                inputs: [
                    TransactionInput {
                        utxo_id: TransactionId(
                            0000000000000000000000000000000000000000000000000000000000000000,
                        ),
                        output_index: OutputIndex(
                            -1,
                        ),
                        unlocking_script: UnlockingScript,
                    },
                ],
                outputs: [
                    TransactionOutput {
                        locking_script: LockingScript,
                        amount: 50,
                    },
                ],
            },
        ],
    },
)

The node receives the mined block and relays it to its peers. In response, it receives the relay block message from the second node. The node ignores the block.

And finally, let’s have a look at the output of the second node.

# ... second node output continuation

[127.0.0.1:8333] Recv: BlockRelay(
    Block {
        id: BlockHash(
            00000007f8528228ba31c716139b697836a249c21ad2f8b07b4e8a60ef49f0c7,
        ),
        header: BlockHeader {
            previous_block_hash: BlockHash(
                00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
            ),
            merkle_root: MerkleHash(
                c6d96f868d2c9068c0865d38d14873a0b62a3d03af1add517af1b494a527baf8,
            ),
            timestamp: 1638118159,
            difficulty_target: 28,
            nonce: 21073642,
        },
        transactions: [
            Transaction {
                id: TransactionId(
                    69bce1ce26f67aa8244741000b3ae00dfacb1f20169bb0f3b677ddf29a1da0f8,
                ),
                inputs: [
                    TransactionInput {
                        utxo_id: TransactionId(
                            0000000000000000000000000000000000000000000000000000000000000000,
                        ),
                        output_index: OutputIndex(
                            -1,
                        ),
                        unlocking_script: UnlockingScript,
                    },
                ],
                outputs: [
                    TransactionOutput {
                        locking_script: LockingScript,
                        amount: 50,
                    },
                ],
            },
        ],
    },
)
[127.0.0.1:8333] Send: BlockRelay(
    Block {
        id: BlockHash(
            00000007f8528228ba31c716139b697836a249c21ad2f8b07b4e8a60ef49f0c7,
        ),
        header: BlockHeader {
            previous_block_hash: BlockHash(
                00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1,
            ),
            merkle_root: MerkleHash(
                c6d96f868d2c9068c0865d38d14873a0b62a3d03af1add517af1b494a527baf8,
            ),
            timestamp: 1638118159,
            difficulty_target: 28,
            nonce: 21073642,
        },
        transactions: [
            Transaction {
                id: TransactionId(
                    69bce1ce26f67aa8244741000b3ae00dfacb1f20169bb0f3b677ddf29a1da0f8,
                ),
                inputs: [
                    TransactionInput {
                        utxo_id: TransactionId(
                            0000000000000000000000000000000000000000000000000000000000000000,
                        ),
                        output_index: OutputIndex(
                            -1,
                        ),
                        unlocking_script: UnlockingScript,
                    },
                ],
                outputs: [
                    TransactionOutput {
                        locking_script: LockingScript,
                        amount: 50,
                    },
                ],
            },
        ],
    },
)

The second node receives the mined block and relays it to its peers.

At this point, all the nodes have the same view of the blockchain, which was the goal.

Here’s the source code for relaying blocks.

Consensus algorithm - update the active chain

You may have already noticed a problem in the output of the first node. The miner requests a new block template after mining a block, and the node responds with the 00440e2238ebe96bf7013d7b86b4ec8293bb1bc0294444607817e73d8eecf1d1 block hash, which is the genesis block. The response looks wrong, because the miner has just found the block that comes after the genesis block.

The block template message includes the tip of the active chain, which we haven’t updated. The active chain may change when the node receives and validates a new block, i.e. the valid block may become new tip of the active chain.

Let’s introduce a function that tries to update the active chain given a new block. The function is called when a node validates a new block. The node doesn’t implement validation yet, so it calls the function when the full block data is available.

// learncoin/src/learncoin_node.rs

// ...

impl LearnCoinNode {
    // ...

    fn on_block(&mut self, peer_address: &str, block: Block) {
        // ...
        self.maybe_update_active_chain(block.id());
    }

    fn on_submit_block(&mut self, peer_address: &str, id: u64, block: Block) {
        // ...
        } else if !self.block_index.exists(block.id()) {
            // ...
            self.maybe_update_active_chain(block.id());
        }
    }

    fn on_relay_block(&mut self, peer_address: &str, block: Block) {
        // ...

        // The block connects.
        self.block_storage.insert(block.clone()); // existing line
        self.maybe_update_active_chain(block.id());
    }
}

Active chain update algorithm

Let’s describe the active chain update algorithm implementing the maybe_update_active_chain function.

Consensus algorithm - Update the active chain

The above diagram describes the following steps:

  1. Let’s say that there are two competing chains in the blockchain. Chain A ending with A6, and chain B ending with B6. The chain A, ending with block A6 is the current tip with the most chain work.
  2. The node receives block B7.
  3. Block B7 has more chain work than block A6. Therefore, the node wants to change the active chain to chain B, ending with block B7.
  4. The node finds a fork, which is block Block 3 in the diagram, and removes all the blocks that come after the fork, i.e. blocks A4, A5 and A6.
  5. Finally, the node appends all blocks from the chain B to the active chain, i.e. blocks B4, B5, B6 and B7.
// learncoin/src/learncoin_node.rs

// ...
impl LearnCoinNode {
    // ...

    fn maybe_update_active_chain(&mut self, candidate_block_hash: &BlockHash) {
        let active_tip = self
            .block_index
            .get_block_index_node(self.active_chain.tip().id())
            .unwrap();
        let candidate = self
            .block_index
            .get_block_index_node(candidate_block_hash)
            .unwrap();

        if active_tip.chain_work < candidate.chain_work {
            // TODO: Implement the above algorithm
        }
    }
}

To implement the above algorithm, we need to find the fork block for the given active tip and the candidate blocks. Let’s assume that BlockIndex provides a function that given two blocks A and B returns a fork block which is the lowest common ancestor as described in the above algorithm. Furthermore, it returns paths from A to the fork and from B to the fork, i.e. sorted by height in descending order.

fn maybe_update_active_chain(&mut self, candidate_block_hash: &BlockHash) {
    /// ...
    let (lowest_common_ancestor, active_path, mut candidate_path) = self
        .block_index
        .find_fork(
            &active_tip.block_header.hash(),
            &candidate.block_header.hash(),
        )
        .unwrap();

The next step is to remove the blocks which come after the fork block.

fn maybe_update_active_chain(&mut self, candidate_block_hash: &BlockHash) {
    //...

    // Remove blocks from the active chain until the LCA becomes the new tip.
    while self.active_chain.tip().header().hash() != lowest_common_ancestor {
        let removed = self.active_chain.remove_tip();
        assert_eq!(*removed.id(), *active_path.last().unwrap());
    }
    assert_eq!(
        self.active_chain.tip().header().hash(),
        lowest_common_ancestor
    );

Finally, let’s append the blocks between the fork block and the candidate block to the active chain. It’s important to reverse the path to append blocks with smaller height first.

fn maybe_update_active_chain(&mut self, candidate_block_hash: &BlockHash) {
    //...

    // Add new blocks to the active chain.
    candidate_path.reverse();
    for new_tip_hash in candidate_path {
        let new_tip_block = self.block_storage.get(&new_tip_hash).unwrap();
        self.active_chain.accept_block(new_tip_block.clone());
    }
}

Awesome. Now we have an algorithm that updates the active chain.

Finding a fork

Let’s implement the function to find a fork for the two blocks. The fork is the lowest common ancestor in the tree of blocks.

Let’s imagine that a function takes two blocks A and B.

The algorithm to find a fork works as follows:

  1. Create two pointers, each pointing to one of the two blocks A and B.
  2. If pointers are not at the same height, update the pointer at the heigher height such that it now points to the parent block. Repeat this step until both pointers are at the same height.
  3. Update both pointers to their corresponding parent blocks. Repeat until both point to the same block.

The block that both pointers point to is the fork.

An algorithm that finds a fork block in the blockchain

The above diagram illustrates the algorithm to find the fork.

Let’s implement a find_fork function, starting with the two pointers pointing to blocks A and B.

// learncoin/src/block_index.rs

// ...
impl BlockIndex {
    // ...

    pub fn find_fork(
        &self,
        hash_a: &BlockHash,
        hash_b: &BlockHash,
    ) -> Option<(BlockHash, Vec<BlockHash>, Vec<BlockHash>)> {
        let mut path_a = vec![];
        let mut path_b = vec![];

        let mut hash_a = *hash_a;
        let mut hash_b = *hash_b;
    }
}

Next step is to implement the part where the pointer at the higher height is updated until both pointers are at the same height. If any of the blocks don’t exist, the functions returns None. In addition to bringing the pointers at the same height, the function stores the visited blocks in the path.

pub fn find_fork(
    &self,
    hash_a: &BlockHash,
    hash_b: &BlockHash,
) -> Option<(BlockHash, Vec<BlockHash>, Vec<BlockHash>)> {
    // ...

    // Bring them to the same height.
    loop {
        match (self.tree.get(&hash_a), self.tree.get(&hash_b)) {
            // If any of the nodes doesn't exist in the tree, then fork doesn't exist neither.
            (None, _) | (_, None) => return None,
            (Some(a), Some(b)) => match a.height.cmp(&b.height) {
                Ordering::Less => {
                    path_b.push(hash_b);
                    hash_b = b.block_header.previous_block_hash();
                }
                Ordering::Equal => break,
                Ordering::Greater => {
                    path_a.push(hash_a);
                    hash_a = a.block_header.previous_block_hash();
                }
            },
        };
    }
}

Finally, update both pointers until they are pointing to the same block, which is the fork.

pub fn find_fork(
    &self,
    hash_a: &BlockHash,
    hash_b: &BlockHash,
) -> Option<(BlockHash, Vec<BlockHash>, Vec<BlockHash>)> {
    // ...

    // Go to the parent block, until pointers are the same.
    while hash_a != hash_b {
        match (self.tree.get(&hash_a), self.tree.get(&hash_b)) {
            (None, _) | (_, None) => return None,
            (Some(a), Some(b)) => {
                path_a.push(hash_a);
                path_b.push(hash_b);

                hash_a = a.block_header.previous_block_hash();
                hash_b = b.block_header.previous_block_hash();
            }
        }
    }

    Some((hash_a, path_a, path_b))
}

That’s it! Here’s the source code for updating the active chain.

Can a miner keep the block to itself for an advantage?

An interesting side effect of miner finding a new block is that all other miners start from scratch, using that new block as the block template. However, it may take some time before other miners find out about the block.

With this idea in mind, the miner could withhold the block and gain a time advantage by submitting the block later. However, the miner risks that some other miner finds a block in the meantime and lose the reward.

This problem is more present in pools when a malicious miner wants the profitability of the mining pool to drop. You can read more about the Block Withholding Attack here.

Summary

In this post, we have:

  • Learned how the mined blocks are propagated through the network.
  • Started the blockchain network and observed nodes relay mined blocks.
  • Implemented the algorithm to update the active blockchain.

Here’s the source code produced in this blog post.

However, it’s not very easy to see that all nodes have the same view of the active blockchain. Therefore, in the next blog post, we will visualize the blockchain.

comments powered by Disqus

Join my mailing list

Subscribe to get new notifications about new content. It takes time to write quality posts, so you can expect at most 1-2 emails per month.