Connect two local nodes

When we start two nodes on the regtest network (loopback network, 127.0.0.1) with manually set ports, they don't get connected automatically because they don't know each other's port. We thus have to connect them manually. In this example we will fire up one SPV and one full node and initiate a connection from the SPV side. The SPV node is given the address of the full node and attempts to connect. If everything goes well, the two nodes connect.

To demonstrate that the connection was successful, we'll have full node broadcast two transactions. The first tx pays to an address contained in the bloom filter of the SPV node, the second tx to another address which the SPV node doesn't watch. If everything goes well, the first transaction is received by the SPV node and the second isn't. Finally the two nodes are gracefully closed.

We use the library p-event which promisifies events in order to await for connection of the nodes and the reception of the transaction cleanly.

Requirements: bcoin (duh), p-event (npm install p-event)

// necessary for portability
const os = require('os');
const path = require('path');

const bcoin = require('bcoin').set('regtest');
const NetAddress = bcoin.net.NetAddress;
const Network = bcoin.Network;
const pEvent = require('p-event'); // tool to await for events

async function delay(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

const regtest = Network.get().toString();
const logPrefix = path.join(os.homedir(), 'connect-test');

// create nodes
const spvNode = new bcoin.SPVNode({
  network: regtest,
  httpPort: 48449, // avoid clash of ports

  // write log file and chain data to specific directory
  prefix: path.join(logPrefix, 'SPV'),
  memory: false,
  logFile: true,
  logConsole: false,
  logLevel: 'spam',

  // reduce log spam on SPV node (cannot reduce to 0 for full node)
  maxOutbound: 1,
});

const fullNode = new bcoin.FullNode({
  network: regtest,
  port: 48445,
  bip37: true, // accept SPV nodes
  listen: true,

  // write log file and chain data to specific directory
  prefix: path.join(logPrefix, 'FULL'),
  memory: false,
  logFile: true,
  logConsole: false,
  logLevel: 'spam'
});
// nodes created!


(async () => {
  // creates directory at `prefix`
  await spvNode.ensure();
  await fullNode.ensure();

  // start nodes
  await spvNode.open();
  await fullNode.open();

  await spvNode.connect();
  await fullNode.connect();
  // nodes started!


  // start the SPV node's blockchain sync
  spvNode.startSync();

  // SPV node: watch this address
  const address = bcoin.Address.fromString('R9M3aUWCcKoiqDPusJvqNkAbjffLgCqYip', spvNode.network);
  spvNode.pool.watchAddress(address);

  // SPV node: catch tx events
  spvNode.on('tx', (tx) => {
    console.log('-- SPV node received tx: --\n', tx);
  });

  // allow some time for spvNode to figure
  // out that its peer list is empty
  await delay(800);

  // no peers for the spvNode yet :(
  console.log('spvNode\'s peers before connection:', spvNode.pool.peers.head());

  // get peer from known address
  const addr = new NetAddress({
    host: '127.0.0.1',
    port: fullNode.pool.options.port
  });

  // connect spvNode with fullNode
  const peer = spvNode.pool.createOutbound(addr);
  spvNode.pool.peers.add(peer);

  // await to establish connection
  await pEvent(spvNode.pool, 'peer connect');
  // nodes are now connected!

  console.log('spvNode\'s peers after connection:', spvNode.pool.peers.head());

  // broadcast a tx that DOESN'T pay out to SPV node's watch address
  const tx1 = bcoin.TX.fromRaw('01000000011d06bce42b67f1de811a3444353fab5d400d82728a5bbf9c89978be37ad3eba9000000006a47304402200de4fd4ecc365ea90f93dbc85d219d7f1bd92ec8743648acb48b6602977e0b4302203ca2eeabed8e6f457234652a92711d66dd8eda71ed90fb6c49b3c12ce809a5d401210257654e1b0de2d8b08d514e51af5d770e9ef617ca2b254d84dd26685fbc609ec3ffffffff0280969800000000001976a914a4ecde9642f8070241451c5851431be9b658a7fe88acc4506a94000000001976a914b9825cafc838c5b5befb70ecded7871d011af89d88ac00000000', 'hex');
  await fullNode.sendTX(tx1);
  // broadcast a tx that pays out to SPV node's watch address
  const tx2 = bcoin.TX.fromRaw('010000000106b014e37704109fefe2c5c9f4227d68840c3497fc89a9832db8504df039a6c7000000006a47304402207dc8173fbd7d23c3950aaf91b1bc78c0ed9bf910d47a977b24a8478a91b28e69022024860f942a16bc67ec54884e338b5b87f4a9518a80f9402564061a3649019319012103cb25dc2929ea58675113e60f4c08d084904189ab44a9a142179684c6cdd8d46affffffff0280c3c901000000001976a91400ba915c3d18907b79e6cfcd8b9fdf69edc7a7db88acc41c3c28010000001976a91437f306a0154e1f0de4e54d6cf9d46e07722b722688ac00000000', 'hex');
  fullNode.sendTX(tx2);
  // await for the second transaction to be received by the SPV node
  await pEvent(spvNode, 'tx');


  // closing nodes
  await fullNode.disconnect();
  await spvNode.disconnect();

  await fullNode.close();
  await spvNode.close();
  // nodes closed
})();