Blockchain Note – BTC Transaction Relaying

In this chapter we will implement the relaying of such transactions, that are not yet included in the blockchain. In bitcoin, these transaction are also known as “unconfirmed transactions”.
Typically, when someone wants to include a transaction to the blockchain (= send coins to some address ) he broadcasts the transaction to the network and hopefully some node will mine the transaction to the blockchain.
As a consequence, the nodes will now share two types of data when they communicate with each other:
  • the state of the blockchain ( =the blocks and transactions that are included to the blockchain)
  • unconfirmed transactions ( =the transactions that are not yet included in the blockchain)
  1. Transaction Pool

We will store our unconfirmed transactions in a new entity called “transaction pool” (also known as “mempool” in bitcoin). Transaction pool is a structure that contains all of the “unconfirmed transactions” our node know of. In this simple implementation we will just use a list.
let transactionPool:Transaction[]=[];
  1. Add Transaction to the Pool

We create the transaction just like we did in chapter4. We just add the created transaction to the pool instead of instantly trying to mine a block:
const sendTransaction=(address:string, amount:number):Transaction=>{
   const tx:Transaction=createTransaction(address, amount, getPrivateFromWallet(), getUnspentTxOuts(), getTransactionPool());
   addToTransactionPool(tx, getUnspentTxOuts());
   return tx;
};
  1. Broadcasting

The whole point of the unconfirmed transactions are that they will spread throughout the network and eventually some node will mine the transaction to the blockchain. To handle this we will introduce the following simple rules for the networking of unconfirmed transactions:
  • When a node receives an unconfirmed transaction it has not seen before, it will broadcast the full transaction pool to all peers.
  • When a node first connects to another node, it will query for the transaction pool of that node.
We will add two new MessageTypes to serve this purpose:QUERY_TRANSACTION_POOL and RESPONSE_TRANSACTION_POOL. The MessageType enum will now look now like this:
enum MessageType {
   QUERY_LATEST=0,
   QUERY_ALL=1,
   RESPONSE_BLOCKCHAIN=2,
   QUERY_TRANSACTION_POOL=3,
   RESPONSE_TRANSACTION_POOL=4
}

The transaction pool messages will be created in the following way:

const responseTransactionPoolMsg=():Message=>({
   'type':MessageType.RESPONSE_TRANSACTION_POOL,
   'data':JSON.stringify(getTransactionPool())
});

const queryTransactionPoolMsg=():Message=>({
   'type':MessageType.QUERY_TRANSACTION_POOL,
   'data':null
});
Whenever, we receive unconfirmed transactions, we try to add those to our transaction pool. If we manage to add a transaction to our pool, it means that the transaction is valid and our node has not seen the transaction before. In this case we broadcast our own transaction pool to all peers.
case MessageType.RESPONSE_TRANSACTION_POOL:
   const receivedTransactions:Transaction[]=JSONToObject<Transaction[]>(message.data);
   receivedTransactions.forEach((transaction:Transaction)=>{
       try{
           handleReceivedTransaction(transaction);
           //if no error is thrown, transaction was indeed added to the pool
           //let's broadcast transaction pool
           broadCastTransactionPool();
       }catch(e) {
           //unconfirmed transaction not valid (we probably already have it in our pool)
       }
   });
  1. Validate transaction

As the peers can send us any kind of transactions, we must validate the transactions before we can add them to the transaction pool. All of the existing transaction validation rules apply. For instance, the transaction must be correctly formatted, and the transaction inputs, outputs and signatures must match.
In addition to the existing rules, we add a new rule: a transaction cannot be added to the pool if any of the transaction inputs are already found in the existing transaction pool. This new rule is embodied in the following code:
const isValidTxForPool=(tx:Transaction, aTtransactionPool:Transaction[]):boolean=>{
   const txPoolIns:TxIn[]=getTxPoolIns(aTtransactionPool);
   const containsTxIn=(txIns:TxIn[], txIn:TxIn)=>{
       return _.find(txPoolIns, (txPoolIn=>{
           returntxIn.txOutIndex===txPoolIn.txOutIndex&&txIn.txOutId===txPoolIn.txOutId;
       }))
   };

   for (consttxIn of tx.txIns) {
       if (containsTxIn(txPoolIns, txIn)) {
           console.log('txIn already found in the txPool');
           returnfalse;
       }
   }
   return true;
};
There is no explicit way to remove a transaction from the transaction pool. The transaction pool will however be updated each time a new block is found.
  1. From transaction pool to blockchain

Let’s next implement a way for the unconfirmed transaction to find its way from the local transaction pool to a block mined by the same node. This is simple: when a node starts to mine a block, it will include the transactions from the transaction pool to the new block candidate.
const generateNextBlock=()=>{
   const coinbaseTx:Transaction=getCoinbaseTransaction(getPublicFromWallet(), getLatestBlock().index+1);
   const blockData:Transaction[]=[coinbaseTx].concat(getTransactionPool());
   return generateRawNextBlock(blockData);
};
As the transactions are already validated, before they are added to the pool, we are not doing any further validations at this points.
  1. Updating the transaction pool

As new blocks with transactions are mined to the blockchain, we must revalidate the transaction pool every time a new block is found. It is possible that the new block contains transactions that makes some of the transactions in the pool invalid. This can happen if for instance:
  • The transaction that was in the pool was mined (by the node itself or by someone else)
  • The unspent transaction output that is referred in the unconfirmed transaction is spent by some other transaction
The transaction pool will be updated with the following code:
const updateTransactionPool=(unspentTxOuts:UnspentTxOut[])=>{
   const invalidTxs=[];
   for (consttx of transactionPool) {
       for (consttxIn of tx.txIns) {
           if (!hasTxIn(txIn, unspentTxOuts)) {
               invalidTxs.push(tx);
               break;
           }
       }
   }

   if (invalidTxs.length>0) {
       console.log('removing the following transactions from txPool: %s', JSON.stringify(invalidTxs));
       transactionPool=_.without(transactionPool, ...invalidTxs)
   }
};
As it can be seen, we need to know only the current unspent transaction outputs to make the decision if a transaction should be removed from the pool.

Reference

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s