Syndicode
Contact Us
Andrey Makovenko
May 6, 2020

Blockchain monitoring in the example of Bitcoin and Ruby

About the author:

Andrey Makovenko is a Ruby Developer at Syndicode with a strong Blockchain/Cryptocurrency engineering background. He is passionate about all the layers of software: backend, frontend, and operations, but mostly focused on backend development.


What is blockchain?

Blockchain (by its idea) is a decentralized distributed digital network(chain) used for storing the information(blocks) safely. It can be used in many ways, but in this article, we’ll cover the blockchain of Bitcoin.

What is Bitcoin?

Bitcoin is a cryptocurrency. Bitcoin transactions are cryptographically verified and saved by blockchain.

Why would you need to monitor the blockchain?

You need to monitor blockchain if you want to analyze it for some reason (e.g., money movement) or if you want to receive/send bitcoin payments (deposits and withdraws) in your application.

Let’s begin!

We need to install bitcoin-core and synchronize it with testnet blockchain, run:

➜ bitcoind -testnet -server -rpcuser=user -rpcpassword=password

(It requires authentication, so I set “user” and “password” for example)

It will start synchronizing your local blockchain node. Currently (May 4, 2020) it requires 26 Gb of disk space for testnet* and around 127 Gb of disk space for mainnet**).

*testnet is like a staging server of blockchain. As a software, it is identical to the original blockchain, but it is widely used for testing new software and ideas without having a potential problem of losing “real” bitcoins. 

**mainnet is like a production of blockchain. It is the original software used by Bitcoin.

We’ll start with a simple “daemon” script doing some cyclic work:

# monitoring logic goes here
class Monitor
  def self.start!
    # continuosly do the same work with 5 seconds pause
    loop do
      puts 'awake...'
      # do something
      puts 'sleep...'
      sleep(5)
    end
  end
end

Monitor.start!

Let’s try to start it and see what happens:

➜ ruby bitcoin_monitor.rb
awake...
sleep...
awake...
sleep...
awake...
sleep...
…

Cool! It runs the same code with 5 seconds pause just like we wanted!

Let’s add some basic logic to communicate with “bitcoind”:

# Bitcoin module
module Bitcoin
  # Bitcoin blockchain interaction
  class Blockchain
    def initialize
      # connection to the bitcoind JSON rpc server
      @connection = Faraday.new('http://127.0.0.1:18332/').tap do |connection|
        # authentication variables we declared when starting bitcoind
        connection.basic_auth('user', 'password')
      end
    end

    # get latest block number
    def latest_block
      rpc_call(:getblockcount)
    end

    private

    # json rpc call wrapper
    def rpc_call(method, params = [])
      response = @connection.post do |req|
        req.body = { jsonrpc: '1.0', method: method, params: params }.to_json
      end
      JSON.parse(response.body)['result']
    end
  end
end

NOTICE: In real application you should not set credentials directly in the code, better is to set them as environment variables. Also, they should me much stronger than ‘user’ and ‘password’.

We’ve added a Bitcoin::Blockchain class, basic JSON RPC communication logic and a “latest_block” method. It will be used to get the latest block number in the blockchain.

Let’s call it from Monitor class and print to the screen:

class Monitor
  def self.start!
    # define a Bitcoin::Blockchain class instance
    blockchain = Bitcoin::Blockchain.new
    # continuosly do the same work with 5 seconds pause
    loop do
      puts 'awake...'
      puts blockchain.latest_block
      puts 'sleep...'
      sleep(5)
    end
  end
end

Let’s run it again!

...
awake...
1722237
sleep...
awake...
1722238
sleep...
awake...
1722238
sleep…
...

Awesome!! Now we are getting the latest block number from Bitcoin blockchain every 5 seconds and already can monitor when new blocks are added.

Let’s change our Monitor class code this way:

# monitoring logic goes here
class Monitor
  def initialize
    @latest_block = nil
  end

  def start!
    # define a Bitcoin::Blockchain class instance
    @blockchain = Bitcoin::Blockchain.new
    @latest_block = nil
    # continuously lookup for new blocks with 5 seconds pause
    loop do
      puts 'awake...'
      # check if current @latest_block is blockchain_latest_block
      if latest_block?(@blockchain.latest_block_number)
        puts 'No new blocks found'
        puts 'sleep...'
        sleep(5)
        next
      end
    # print the new block number without any pause
    puts "New block found: #{@latest_block}"
    end
  end

  # a method to check if provided block is the latest in local blockchain
  def latest_block?(blockchain_latest_block)
    return true if @latest_block == blockchain_latest_block

    @latest_block = @blockchain.latest_block_number
    false
  end
end

Alright, now we only print the new block number and have no 5 seconds pause for new blocks.

So how do we get some useful information from Bitcoin blockchain?

Let’s add this new method to our Bitcoin::Blockchain class:

# gets a block with a particular header hash
def get_block(block_number)
  # returns the header hash of a block at the given height
  block_hash = rpc_call(:getblockhash, [block_number])
  rpc_call(:getblock, [block_hash, 2])
end

This will return us all the information about a block at a given height(or block number as we named it before). It consists of many fields, but most useful for us are transactions(‘tx’), so let’s print their information for each new block appearing in blockchain(all new transactions are added to the blockchain with new blocks).

Let’s add this code to “Monitor” class:

# a wrapper to extract transactions information
def block_transactions(transactions)
  return nil if transactions.empty?

  transactions_formatted = []
  transactions.each do |transaction|
    transaction['vout'].each do |subtransaction|
      tx_formatted = {}
      next if subtransaction['scriptPubKey']['addresses'].nil?
      tx_formatted[:amount]  = subtransaction['value']
      tx_formatted[:receiver] = subtransaction['scriptPubKey']['addresses'][0]
      transactions_formatted << tx_formatted
    end
  end
  transactions_formatted
end

And call it at the end of our loop in “.start!” method:

# print block transactions
puts' Block transactions:'
puts block_transactions(@blockchain.get_block(@latest_block)['tx'])

And let’s rerun our script:

➜  blockchain_monitoring ruby bitcoin_monitor.rb
awake...
"New block found: 1722894"
[{:amount=>0.19637722, :receiver=>["2N5oMBJB1mmNnr3UrQrEzazXBqwagRAs8vG"]},
 {:amount=>1.0e-05, :receiver=>["2N2jYboVb42pY2L8o7Md48agKpZQocfKRg4"]},
 {:amount=>0.13315513, :receiver=>["mupisbLSNHtzqcYeDpZKU6L12q7nE4Mhez"]},
 {:amount=>0.0064, :receiver=>["mq3Qjwc4N6EstUVaSviu1nnAv8L8uExtof"]},
 {:amount=>5.1e-05, :receiver=>["mk8aUY9DgFMx7VfDck5oQ7FjJNhn8u3snP"]},
 {:amount=>0.00038238, :receiver=>["msRFQV9qwQr4R1h3PfVudvT6cTce7EhZnz"]},
 {:amount=>0.00025098, :receiver=>["2NFZqS93SaZJYPcSpERDEpTcQkDVSGMeUXg"]},
...

That’s it! Now we’re aware about all new transactions appearing in blockchain.

To sum up

As you have already seen, it is pretty easy to communicate with blockchain and monitor it. The only problem could be a disk space required for the mainnet (the best would be to have it stored in an SSD).

What can you do with this information? It depends on your needs… You can analyze the “money” movement across the blockchain, daily, weekly and monthly volumes, etc.

It can also be used as a part of the code to receive bitcoin payments in your application, but we will cover this topic more in-depth in the next articles.