@ecash/lib
Version:
Library for eCash transaction building
183 lines • 7.79 kB
JavaScript
;
// Copyright (c) 2024 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestRunner = void 0;
const ecc_js_1 = require("../ecc.js");
const hash_js_1 = require("../hash.js");
const hex_js_1 = require("../io/hex.js");
const op_js_1 = require("../op.js");
const opcode_js_1 = require("../opcode.js");
const script_js_1 = require("../script.js");
const tx_js_1 = require("../tx.js");
const txBuilder_js_1 = require("../txBuilder.js");
const OP_TRUE_SCRIPT = script_js_1.Script.fromOps([opcode_js_1.OP_1]);
const OP_TRUE_SCRIPT_SIG = script_js_1.Script.fromOps([
(0, op_js_1.pushBytesOp)(OP_TRUE_SCRIPT.bytecode),
]);
// Like OP_TRUE_SCRIPT but much bigger to avoid undersize
const ANYONE_SCRIPT = script_js_1.Script.fromOps([(0, op_js_1.pushBytesOp)((0, hex_js_1.fromHex)('01'.repeat(100)))]);
const ANYONE_SCRIPT_SIG = script_js_1.Script.fromOps([(0, op_js_1.pushBytesOp)(ANYONE_SCRIPT.bytecode)]);
class TestRunner {
constructor(ecc, runner, chronik) {
this.ecc = ecc;
this.runner = runner;
this.chronik = chronik;
this.coinsTxid = undefined;
this.lastUsedOutIdx = 0;
}
static async setup(setupScript = 'setup_scripts/ecash-lib_base') {
const { ChronikClient } = await Promise.resolve().then(() => __importStar(require('chronik-client')));
const { spawn } = await Promise.resolve().then(() => __importStar(require('node:child_process')));
const events = await Promise.resolve().then(() => __importStar(require('node:events')));
const statusEvent = new events.EventEmitter();
const runner = spawn('python3', [
'test/functional/test_runner.py',
// Place the setup in the python file
setupScript,
], {
stdio: ['ipc'],
// Needs to be set dynamically and the Bitcoin ABC
// node has to be built first.
cwd: process.env.BUILD_DIR || '.',
});
// Redirect stdout so we can see the messages from the test runner
runner.stdout?.pipe(process.stdout);
runner.stderr?.pipe(process.stderr);
runner.on('error', function (error) {
console.log('Test runner error, aborting: ' + error);
runner.kill();
process.exit(-1);
});
runner.on('exit', function (code, signal) {
// The test runner failed, make sure to propagate the error
if (code !== null && code !== undefined && code != 0) {
console.log('Test runner completed with code ' + code);
process.exit(code);
}
// The test runner was aborted by a signal, make sure to return an
// error
if (signal !== null && signal !== undefined) {
console.log('Test runner aborted by signal ' + signal);
process.exit(-2);
}
// In all other cases, let the test return its own status as
// expected
});
runner.on('spawn', function () {
console.log('Test runner started');
});
let chronik = undefined;
runner.on('message', async function (message) {
if (message && message.test_info && message.test_info.chronik) {
console.log('Setting chronik url to ', message.test_info.chronik);
chronik = new ChronikClient(message.test_info.chronik);
}
if (message && message.status) {
while (!statusEvent.emit(message.status)) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
});
const ecc = new ecc_js_1.Ecc();
// We got the coins, can fan out now
await events.once(statusEvent, 'ready');
if (chronik === undefined) {
throw new Event('Chronik is undefined');
}
return new TestRunner(ecc, runner, chronik);
}
async setupCoins(numCoins, coinValue) {
const opTrueScriptHash = (0, hash_js_1.shaRmd160)(OP_TRUE_SCRIPT.bytecode);
const utxos = (await this.chronik.script('p2sh', (0, hex_js_1.toHex)(opTrueScriptHash)).utxos()).utxos;
const anyoneScriptHash = (0, hash_js_1.shaRmd160)(ANYONE_SCRIPT.bytecode);
const anyoneP2sh = script_js_1.Script.p2sh(anyoneScriptHash);
const tx = new tx_js_1.Tx({
inputs: utxos.map(utxo => ({
prevOut: utxo.outpoint,
script: OP_TRUE_SCRIPT_SIG,
sequence: 0xffffffff,
})),
});
const utxosValue = utxos.reduce((a, b) => a + b.value, 0);
for (let i = 0; i < numCoins; ++i) {
tx.outputs.push({
value: coinValue,
script: anyoneP2sh,
});
}
tx.outputs.push({
value: 0,
script: script_js_1.Script.fromOps([opcode_js_1.OP_RETURN]),
});
tx.outputs[tx.outputs.length - 1].value =
utxosValue - numCoins * coinValue - tx.serSize();
this.coinsTxid = (await this.chronik.broadcastTx(tx.ser())).txid;
this.coinValue = coinValue;
}
getOutpoint() {
if (this.coinsTxid === undefined) {
throw new Error('TestRunner.coinsTxid undefined, call setupCoins');
}
return {
txid: this.coinsTxid,
outIdx: this.lastUsedOutIdx++, // use value, then increment
};
}
async sendToScript(value, script) {
const coinValue = this.coinValue;
const values = Array.isArray(value) ? value : [value];
const setupTxBuilder = new txBuilder_js_1.TxBuilder({
inputs: [
{
input: {
prevOut: this.getOutpoint(),
script: ANYONE_SCRIPT_SIG,
sequence: 0xffffffff,
signData: {
value: coinValue,
},
},
},
],
outputs: [
...values.map(value => ({ value, script })),
script_js_1.Script.fromOps([opcode_js_1.OP_RETURN]), // burn leftover
],
});
const setupTx = setupTxBuilder.sign(this.ecc, 1000, 546);
return (await this.chronik.broadcastTx(setupTx.ser())).txid;
}
generate() {
this.runner.send('generate');
}
stop() {
this.runner.send('stop');
}
}
exports.TestRunner = TestRunner;
//# sourceMappingURL=testRunner.js.map