UNPKG

regtest-client

Version:

A client for regtest bitcoin usage. Requires regtest-server from bitcoinjs github.

164 lines (163 loc) 4.91 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.RegtestUtils = void 0; const assert = require('assert'); const rng = require('randombytes'); const bs58check = require('bs58check'); const dhttpCallback = require('dhttp/200'); let RANDOM_ADDRESS; class RegtestUtils { constructor(_opts) { this._APIURL = (_opts || {}).APIURL || process.env.APIURL || 'http://127.0.0.1:8080/1'; this._APIPASS = (_opts || {}).APIPASS || process.env.APIPASS || 'satoshi'; // regtest network parameters this.network = { messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'bcrt', bip32: { public: 0x043587cf, private: 0x04358394, }, pubKeyHash: 0x6f, scriptHash: 0xc4, wif: 0xef, }; } get RANDOM_ADDRESS() { if (RANDOM_ADDRESS === undefined) { RANDOM_ADDRESS = this.randomAddress(); } return RANDOM_ADDRESS; } // use Promises async dhttp(options) { return new Promise((resolve, reject) => { return dhttpCallback(options, (err, data) => { if (err) return reject(err); else return resolve(data); }); }); } async broadcast(txHex) { return this.dhttp({ method: 'POST', url: this._APIURL + '/t/push', body: txHex, }); } async mine(count) { return this.dhttp({ method: 'POST', url: `${this._APIURL}/r/generate?count=${count}&key=${this._APIPASS}`, }); } async height() { return this.dhttp({ method: 'GET', url: this._APIURL + '/b/best/height', }); } async fetch(txId) { return this.dhttp({ method: 'GET', url: `${this._APIURL}/t/${txId}/json`, }); } async unspents(address) { return this.dhttp({ method: 'GET', url: `${this._APIURL}/a/${address}/unspents`, }); } async faucet(address, value) { const requester = _faucetRequestMaker( 'faucet', 'address', this.dhttp, this._APIURL, this._APIPASS, ); const faucet = _faucetMaker(this, requester); return faucet(address, value); } async faucetComplex(output, value) { const outputString = output.toString('hex'); const requester = _faucetRequestMaker( 'faucetScript', 'script', this.dhttp, this._APIURL, this._APIPASS, ); const faucet = _faucetMaker(this, requester); return faucet(outputString, value); } async verify(txo) { const tx = await this.fetch(txo.txId); const txoActual = tx.outs[txo.vout]; if (txo.address) assert.strictEqual(txoActual.address, txo.address); if (txo.value) assert.strictEqual(txoActual.value, txo.value); } randomAddress() { // Fake P2PKH address with regtest/testnet version byte return bs58check.encode(Buffer.concat([Buffer.from([0x6f]), rng(20)])); } } exports.RegtestUtils = RegtestUtils; function _faucetRequestMaker(name, paramName, dhttp, url, pass) { return async (address, value) => dhttp({ method: 'POST', url: `${url}/r/${name}?${paramName}=${address}&value=${value}&key=${pass}`, }); } function _faucetMaker(self, _requester) { return async (address, value) => { let count = 0; let _unspents = []; const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const randInt = (min, max) => min + Math.floor((max - min + 1) * Math.random()); const txId = await _requester(address, value).then( v => v, // Pass success value as is async err => { // Bad Request error is fixed by making sure height is >= 432 const currentHeight = await self.height(); if (err.message === 'Bad Request' && currentHeight < 432) { await self.mine(432 - currentHeight); return _requester(address, value); } else if (err.message === 'Bad Request' && currentHeight >= 432) { return _requester(address, value); } else { throw err; } }, ); while (_unspents.length === 0) { if (count > 0) { if (count >= 5) { // Sometimes indexd takes more than 60 secs to sync. console.log('WARNING: Indexd is busy. Using getrawtransaction RPC.'); const tx = await self.fetch(txId); const outs = tx.outs.filter(x => x.address === address); const out = outs.pop(); if (out) { const vout = tx.outs.indexOf(out); const v = out.value; return { txId, vout, value: v }; } else { throw new Error('Missing Inputs'); } } console.log('Missing Inputs, retry #' + count); await sleep(randInt(150, 250)); } await sleep(randInt(50, 150)); const results = await self.unspents(address); _unspents = results.filter(x => x.txId === txId); count++; } return _unspents.pop(); }; }