@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
JavaScript
"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