Developer Fundamentals

-- V. Beats

Learn how to use beats and Bloom filters to detect relevant blockchain activity without downloading full blocks or receipts.

Using Beats to Detect Blockchain Activity

Beats are messages that are sent on each new block, containing tiny bits of data that can be used to detect changes happening on the blockchain. Beats provide as little data as possible to remove the need to load full block and receipt data every time.

Connection

The connection is managed using WebSockets, which connect directly to a VeChain node.

A simple connection can be established with this snippet:

import WebSocket from 'ws';
const ws = new WebSocket('wss://mainnet.vechain.org/subscriptions/beat2');
ws.onmessage = (message) => {
    console.log('New block', message.data);
}

This will receive a new block as soon as it is added to the blockchain, in the form of JSON-encoded strings with a Bloom filter, which can be used to check if the block contains an account.

To resume listening from a specific block position, the options can include a blockID to continue from where a previous listener may have disconnected.

For additional details on the options, refer to the documentation of BlockSubscriptionOptions.

Block Details

The blocks are received as JSON-encoded strings. These strings must be parsed into usable objects.

An example result is:

{
  number: 18342209,
  id: '0x0117e1411afb526f813370417d23d1757e03c47887d73de999bb178919d41f96',
  parentID: '0x0117e1408ea3161e8561868dbae1828841588954bd71ae845866d9b67ec07e83',
  timestamp: 1713949880,
  txsFeatures: 1,
  gasLimit: 30146568,
  bloom: '0x990c2f3331b75f955af09180665aadf3f017',
  k: 13,
  obsolete: false
}

When working with beats, we receive minimal block data in real time. But how can we quickly determine if a new block is relevant to a specific address without parsing full transactions?

This is where the Bloom filter becomes a critical tool. It provides a fast, lightweight way to check for potential interactions with a specific address.

It enables the verification of interactions involving a specific address, potentially eliminating the need for further transaction or block lookups if the block lacks information related to that address.

The bloomUtils offer a straightforward test function to determine whether an address has had interactions within a block:

import { bloomUtils } from '@vechain/sdk-core'

const addressToTest = '0x0000000000000000000000000000000000000000'
console.log('Interaction found', bloomUtils.isAddressInBloom(block.bloom, block.k, addressToTest))

The bloom filter is used for testing and includes:

  • The Gas Payer of a transaction

  • The emitters of all events within transactions

  • The topics of all events in transactions that include an address or are shorter than an address

  • The sender and receiver of transfers

  • The origin of the transaction

  • The signer of the block

  • The beneficiary of the block

For more details on the implementation, you can view the code on GitHub.

bloomUtils.isInBloom provides an additional filter to check for the presence of non-address data, such as numbers emitted within events.

It can also be used to check for the VTHO contract, whose address is special because it is the bytes32-encoded version of the word "Energy" (0x456e65726779).

const dataToTest = "0x456e65726779"
console.log('Data found', bloomUtils.isInBloom(block.bloom, block.k, dataToTest))

Try It Yourself

The snippet below puts it all together:

import { subscriptions } from '@vechain/sdk-network';
import pkg from '@vechain/sdk-core';
const { bloomUtils } = pkg;
import WebSocket from 'ws';

const textEncoder = new TextEncoder();

const addressToTest = '0x0000000000000000000000000000000000000000';

// encode the word "Energy" that maps to the VTHO Energy contract
const dataToTest = `0x${Buffer.from(textEncoder.encode('Energy')).toString(
  'hex'
)}`;

// build a subscription url for the WebSocket connection
const wsUrl = subscriptions.getBeatSubscriptionUrl(
  'https://mainnet.vechain.org'
);

const ws = new WebSocket(wsUrl);

ws.onopen = () => {
  console.log('Connected to', wsUrl);
};

ws.onmessage = async (message) => {
  const block = JSON.parse(message.data);
  console.log('New block', block);

  console.log(
    'Interaction with address found',
    bloomUtils.isAddressInBloom(block.bloom, block.k, addressToTest)
  );

  console.log(
    'Interaction with data found',
    bloomUtils.isInBloom(block.bloom, block.k, dataToTest)
  );
};