UNPKG

@muirglacier/testcontainers

Version:

A collection of TypeScript + JavaScript tools and libraries for DeFi Blockchain developers to build decentralized finance for Bitcoin

290 lines 10.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DeFiDRpcError = exports.DeFiDContainer = void 0; const cross_fetch_1 = __importDefault(require("cross-fetch")); const DockerContainer_1 = require("./DockerContainer"); const utils_1 = require("../utils"); /** * DeFiChain defid node managed in docker */ class DeFiDContainer extends DockerContainer_1.DockerContainer { /** * @param {Network} network of the container * @param {string} image docker image name * @param {DockerOptions} options */ constructor(network, image = DeFiDContainer.image, options) { super(image, options); this.network = network; this.image = image; } static get image() { var _a; if (((_a = process === null || process === void 0 ? void 0 : process.env) === null || _a === void 0 ? void 0 : _a.DEFICHAIN_DOCKER_IMAGE) !== undefined) { return process.env.DEFICHAIN_DOCKER_IMAGE; } return 'defi/defichain:2.2.1'; } /** * Convenience Cmd builder with StartOptions */ getCmd(opts) { return [ 'defid', '-printtoconsole', '-rpcallowip=0.0.0.0/0', '-rpcbind=0.0.0.0', '-rpcworkqueue=512', `-rpcuser=${opts.user}`, `-rpcpassword=${opts.password}` ]; } /** * Create container and start it immediately waiting for defid to be ready */ start(startOptions = {}) { return __awaiter(this, void 0, void 0, function* () { yield this.tryPullImage(); this.startOptions = Object.assign(DeFiDContainer.DefaultStartOptions, startOptions); this.container = yield this.docker.createContainer({ name: this.generateName(), Image: this.image, Tty: true, Cmd: this.getCmd(this.startOptions), HostConfig: { PublishAllPorts: true } }); yield this.container.start(); yield this.waitForRpc(startOptions.timeout); }); } /** * Set contents of ~/.defi/defi.conf * @param {string[]} options to set */ setDeFiConf(options) { return __awaiter(this, void 0, void 0, function* () { if (options.length > 0) { const fileContents = options.join('\n') + '\n'; yield this.exec({ Cmd: ['bash', '-c', `echo "${fileContents}" > ~/.defi/defi.conf`] }); } }); } /** * Generate a name for a new docker container with network type and random number */ generateName() { const rand = Math.floor(Math.random() * 10000000); return `${DeFiDContainer.PREFIX}-${this.network}-${rand}`; } /** * Get host machine url used for defid rpc calls with auth * TODO(fuxingloh): not a great design when network config changed, the url and ports get refresh */ getCachedRpcUrl() { return __awaiter(this, void 0, void 0, function* () { if (this.cachedRpcUrl === undefined) { const port = yield this.getRpcPort(); const user = this.startOptions.user; const password = this.startOptions.password; this.cachedRpcUrl = `http://${user}:${password}@127.0.0.1:${port}/`; } return this.cachedRpcUrl; }); } /** * For convenience sake, utility rpc for the current node. * JSON 'result' is parsed and returned * @throws DeFiDRpcError is raised for RPC errors */ call(method, params = []) { return __awaiter(this, void 0, void 0, function* () { const body = JSON.stringify({ jsonrpc: '1.0', id: Math.floor(Math.random() * 100000000000000), method: method, params: params }); const text = yield this.post(body); const { result, error } = JSON.parse(text); if (error !== undefined && error !== null) { throw new DeFiDRpcError(error); } return result; }); } /** * For convenience sake, HTTP post to the RPC URL for the current node. * Not error checked, returns the raw JSON as string. */ post(body) { return __awaiter(this, void 0, void 0, function* () { const url = yield this.getCachedRpcUrl(); const response = yield cross_fetch_1.default(url, { method: 'POST', body: body }); return yield response.text(); }); } /** * Convenience method to getmininginfo, typing mapping is non exhaustive */ getMiningInfo() { return __awaiter(this, void 0, void 0, function* () { return yield this.call('getmininginfo', []); }); } /** * Convenience method to getblockcount, typing mapping is non exhaustive */ getBlockCount() { return __awaiter(this, void 0, void 0, function* () { return yield this.call('getblockcount', []); }); } /** * Convenience method to getbestblockhash, typing mapping is non exhaustive */ getBestBlockHash() { return __awaiter(this, void 0, void 0, function* () { return yield this.call('getbestblockhash', []); }); } /** * Connect another node * @param {string} ip * @return {Promise<void>} */ addNode(ip) { return __awaiter(this, void 0, void 0, function* () { return yield this.call('addnode', [ip, 'onetry']); }); } /** * Wait for rpc to be ready * @param {number} [timeout=20000] in millis */ waitForRpc(timeout = 20000) { return __awaiter(this, void 0, void 0, function* () { yield utils_1.waitForCondition(() => __awaiter(this, void 0, void 0, function* () { this.cachedRpcUrl = undefined; yield this.getMiningInfo(); return true; }), timeout, 200, 'waitForRpc'); }); } /** * @deprecated as container.start() will automatically wait for ready now, you don't need to call this anymore */ waitForReady(timeout = 20000) { return __awaiter(this, void 0, void 0, function* () { return yield this.waitForRpc(timeout); }); } /** * Stop and remove the current node and their associated volumes. * * This method will also automatically stop and removes nodes that are stale. * Stale nodes are nodes that are running for more than 1 hour */ stop() { var _a, _b; return __awaiter(this, void 0, void 0, function* () { try { yield ((_a = this.container) === null || _a === void 0 ? void 0 : _a.stop()); } finally { try { yield ((_b = this.container) === null || _b === void 0 ? void 0 : _b.remove({ v: true })); } finally { yield cleanUpStale(DeFiDContainer.PREFIX, this.docker); } } }); } /** * Restart container and wait for defid to be ready. * This will stop the container and start it again with old data intact. * @param {number} [timeout=30000] in millis */ restart(timeout = 30000) { var _a; return __awaiter(this, void 0, void 0, function* () { yield ((_a = this.container) === null || _a === void 0 ? void 0 : _a.restart()); yield this.waitForRpc(timeout); }); } } exports.DeFiDContainer = DeFiDContainer; /* eslint-disable @typescript-eslint/no-non-null-assertion, no-void */ DeFiDContainer.PREFIX = 'defichain-testcontainers-'; DeFiDContainer.DefaultStartOptions = { user: 'testcontainers-user', password: 'testcontainers-password' }; /** * RPC error from container */ class DeFiDRpcError extends Error { constructor(error) { super(`DeFiDRpcError: '${error.message}', code: ${error.code}`); } } exports.DeFiDRpcError = DeFiDRpcError; /** * Clean up stale nodes are nodes that are running for 1 hour */ function cleanUpStale(prefix, docker) { return __awaiter(this, void 0, void 0, function* () { /** * Same prefix and created more than 1 hour ago */ function isStale(containerInfo) { if (containerInfo.Names.filter((value) => value.startsWith(prefix)).length > 0) { return containerInfo.Created + 60 * 60 < Date.now() / 1000; } return false; } /** * Stop container that are running, remove them after and their associated volumes */ function tryStopRemove(containerInfo) { return __awaiter(this, void 0, void 0, function* () { const container = docker.getContainer(containerInfo.Id); if (containerInfo.State === 'running') { yield container.stop(); } yield container.remove({ v: true }); }); } return yield new Promise((resolve, reject) => { docker.listContainers({ all: true }, (error, result) => { if (error instanceof Error) { return reject(error); } const promises = (result !== null && result !== void 0 ? result : []) .filter(isStale) .map(tryStopRemove); Promise.all(promises).finally(resolve); }); }); }); } //# sourceMappingURL=DeFiDContainer.js.map