Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"strict": false,
"module": true,
"browser": true,
"node": true,
"esversion": 11,
Expand All @@ -16,7 +18,6 @@
"plusplus": true,
"undef": true,
"unused": "vars",
"strict": true,
"maxdepth": 4,
"maxstatements": 100,
"maxcomplexity": 20
Expand Down
224 changes: 97 additions & 127 deletions bin/gobject-prepare.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
#!/usr/bin/env node
"use strict";

let DotEnv = require("dotenv");
DotEnv.config({ path: ".env" });
DotEnv.config({ path: ".env.secret" });
import DashGov from "dashgov";
import DashKeys from "dashkeys";
import DashTx from "dashtx";
import * as Secp256k1 from "@dashincubator/secp256k1";

let DashGov = require("../");
let DashRpc = require("dashrpc");
let DashKeys = require("dashkeys");
let DashTx = require("dashtx");
let Secp256k1 = require("@dashincubator/secp256k1");

let Fs = require("node:fs/promises");
import Fs from "node:fs/promises";

async function main() {
/* jshint maxcomplexity: 100 */
Expand All @@ -20,7 +15,7 @@ async function main() {
console.info("");
console.info("USAGE");
console.info(
" dashgov draft-proposal [start period] [num periods] <DASH-per-period> <proposal-url> <name> <payment-addr> <./collateral-key.wif> [network]",
" dashgov draft-proposal [start period] [num periods] <DASH-per-period> <proposal-url> <name> <payment-addr> <./burn-key.wif> [network]",
);
console.info("");
console.info("EXAMPLE");
Expand All @@ -31,8 +26,14 @@ async function main() {

/** @type {"mainnet"|"testnet"} */
let network = "mainnet";
let rpcBasicAuth = `api:null`;
let rpcBaseUrl = `https://${rpcBasicAuth}@rpc.digitalcash.dev/`;
let rpcExplorer = "https://rpc.digitalcash.dev/";

let isTestnet = takeFlag(process.argv, ["--testnet"]);
if (isTestnet) {
rpcBaseUrl = `https://${rpcBasicAuth}@trpc.digitalcash.dev/`;
rpcExplorer = "https://trpc.digitalcash.dev/";
network = "testnet";
}

Expand All @@ -42,61 +43,38 @@ async function main() {
let proposalUrl = process.argv[5] || "";
let proposalName = process.argv[6] || "";
let paymentAddr = process.argv[7] || "";
let collateralWifPath = process.argv[8] || "";
let collateralWif = "";
if (collateralWifPath) {
collateralWif = await Fs.readFile(collateralWifPath, "utf8");
collateralWif = collateralWif.trim();
let burnWifPath = process.argv[8] || "";
let burnWif = "";
if (burnWifPath) {
burnWif = await Fs.readFile(burnWifPath, "utf8");
burnWif = burnWif.trim();
}

let rpcConfig = {
protocol: process.env.DASHD_RPC_PROTOCOL || "",
user: process.env.DASHD_RPC_USER || process.env.DASHD_RPC_USERNAME || "",
pass: process.env.DASHD_RPC_PASS || process.env.DASHD_RPC_PASSWORD || "",
host: process.env.DASHD_RPC_HOST || process.env.DASHD_RPC_HOSTNAME || "",
port: parseInt(process.env.DASHD_RPC_PORT || "", 10),
onconnected: function () {
console.info(
`[dashrpc] connected to '${rpcConfig.host}:${rpcConfig.port}'`,
);
},
};

if (!rpcConfig.protocol) {
throw new Error(`not set: export DASHD_RPC_PROTOCOL=`);
}
if (!rpcConfig.user) {
throw new Error(`not set: export DASHD_RPC_USERNAME=`);
}
if (!rpcConfig.pass) {
throw new Error(`not set: export DASHD_RPC_PASSWORD=`);
}
if (!rpcConfig.host) {
throw new Error(`not set: export DASHD_RPC_HOSTNAME=`);
}
if (!rpcConfig.port) {
throw new Error(`not set: export DASHD_RPC_PORT=`);
/**
* @param {String} method
* @param {...any} params
*/
async function rpc(method, ...params) {
let result = await DashTx.utils.rpc(rpcBaseUrl, method, ...params);
return result;
}

let rpc = DashRpc.create(rpcConfig);
void (await rpc.init());

let tipsResult = await rpc.getBestBlockHash();
let blockInfoResult = await rpc.getBlock(tipsResult.result, 1);
let blockHeight = blockInfoResult.result.height;
let blockMs = blockInfoResult.result.time * 1000;
// console.log(rootInfoResult.result, blockInfoResult.result, blockMs);
let tipsResult = await rpc("getbestblockhash");
let blockInfoResult = await rpc("getblock", tipsResult, 1);
let blockHeight = blockInfoResult.height;
let blockMs = blockInfoResult.time * 1000;
// console.log(rootInfoResult, blockInfoResult, blockMs);
// let blockTime = new Date(blockMs);

// for testnet
let blockDelta = 25000;
let rootHeight = blockInfoResult.result.height - blockDelta;
let rootResult = await rpc.getBlockHash(rootHeight);
let rootInfoResult = await rpc.getBlock(rootResult.result, 1);
let rootHeight = blockInfoResult.height - blockDelta;
let rootResult = await rpc("getblockhash", rootHeight);
let rootInfoResult = await rpc("getblock", rootResult, 1);

let root = {
block: rootHeight,
ms: rootInfoResult.result.time * 1000,
ms: rootInfoResult.time * 1000,
};
// let rootTime = new Date(root.ms);

Expand Down Expand Up @@ -138,7 +116,7 @@ async function main() {
}

/**
* @param {DashGov.Estimate} estimate
* @param {import('../dashgov.js').Estimate} estimate
* @param {Number} i
*/
function show(estimate, i) {
Expand Down Expand Up @@ -238,7 +216,6 @@ async function main() {
return;
}

/** @type {DashGov.GObjectData} */
let gobjData = DashGov.proposal.draftJson(selected, {
name: proposalName,
payment_address: paymentAddr,
Expand All @@ -250,38 +227,38 @@ async function main() {
let gobj = DashGov.proposal.draft(now, selected.start.startMs, gobjData, {});
console.log(gobj);

let gobjCollateralBytes = DashGov.serializeForCollateralTx(gobj);
let gobjCollateralHex = DashGov.utils.bytesToHex(gobjCollateralBytes);
let gobjBurnBytes = DashGov.serializeForBurnTx(gobj);
let gobjBurnHex = DashGov.utils.bytesToHex(gobjBurnBytes);

let gobjHashBytes = await DashGov.utils.doubleSha256(gobjCollateralBytes);
let gobjHashBytes = await DashGov.utils.doubleSha256(gobjBurnBytes);
let gobjId = DashGov.utils.hashToId(gobjHashBytes);

let gobjHashBytesReverse = gobjHashBytes.slice();
gobjHashBytesReverse = gobjHashBytesReverse.reverse();
let gobjIdForward = DashGov.utils.hashToId(gobjHashBytesReverse);

console.log("");
console.log("GObject Serialization (for hash for collateral memo)");
console.log(gobjCollateralHex);
console.log("GObject Serialization (for hash for burn memo)");
console.log(gobjBurnHex);

console.log("");
console.log("(Collateralized) GObject ID (for op return memo)");
console.log("(Burnable) GObject ID (for op return memo)");
console.log(gobjIdForward);
console.log("GObject ID (for 'gobject get <gobj-id>')");
console.log(gobjId);

let keyUtils = {
/**
* @param {DashTx.TxInputForSig} txInput
* @param {Number} i
* @param {Number} [i]
*/
getPrivateKey: async function (txInput, i) {
return DashKeys.wifToPrivKey(collateralWif, { version: network });
return DashKeys.wifToPrivKey(burnWif, { version: network });
},

/**
* @param {DashTx.TxInputForSig} txInput
* @param {Number} i
* @param {Number} [i]
*/
getPublicKey: async function (txInput, i) {
let privKeyBytes = await keyUtils.getPrivateKey(txInput, i);
Expand All @@ -295,7 +272,8 @@ async function main() {
* @param {Uint8Array} txHashBytes
*/
sign: async function (privKeyBytes, txHashBytes) {
let sigOpts = { canonical: true, extraEntropy: true };
// extraEntropy set to null to make gobject transactions idempotent
let sigOpts = { canonical: true, extraEntropy: null };
let sigBytes = await Secp256k1.sign(txHashBytes, privKeyBytes, sigOpts);

return sigBytes;
Expand All @@ -314,76 +292,87 @@ async function main() {
let dashTx = DashTx.create(keyUtils);

// dash-cli -testnet getaddressutxos '{"addresses":["yT6GS8qPrhsiiLHEaTWPYJMwfPPVt2SSFC"]}'
let collateralAddr = await DashKeys.wifToAddr(collateralWif, {
let burnAddr = await DashKeys.wifToAddr(burnWif, {
version: network,
});

console.log("");
console.log("Collateral Address (source of 1 DASH network fee):");
console.log(collateralAddr);
console.log("Burn Address (source of 1 DASH network fee):");
console.log(burnAddr);

// we can set txid to short circuit for testing
let txid = "";
// ./bin/gobject-prepare.js 1 3 100 https://example.com/proposal-00 proposal-00 yPPy7Z5RQj46SnFtuFXyT6DFAygxESPR7K ./yjZxu7SJAwgSm1JtWybuQRYQDx34z8P2Z7.wif
// txid = "10d9862feb6eac6cf6fa653589e39b60a0ed640bae4140c51c35401ffe019479";
if (!txid) {
let utxosResult = await rpc.getaddressutxos({
addresses: [collateralAddr],
let txInfoSigned;
{
let utxosResult = await rpc("getaddressutxos", {
addresses: [burnAddr],
});
// TODO make sure there's just 1
// @type {Array<DashTx.TxInput>} */
let inputs = [utxosResult.result[0]];
let inputs = [utxosResult[0]];
// TODO the hash bytes may be reversed
// @type {Array<DashTx.TxOutput>} */
let outputs = [{ memo: gobjIdForward, satoshis: 100000000 }];
let txInfo = { inputs, outputs };
let txInfoSigned = await dashTx.hashAndSignAll(txInfo);
txInfoSigned = await dashTx.hashAndSignAll(txInfo);
console.log(utxosResult);
//

console.log("");
console.log("Signed Collateral Transaction (ready for broadcast):");
console.log("Signed Burn Transaction (ready for broadcast):");
console.log(txInfoSigned.transaction);

console.log("");
console.log("Signed Collateral Transaction ID:");
console.log("Signed Burn Transaction ID:");
txid = await DashTx.getId(txInfoSigned.transaction);
console.log(txid);
}

let txResult = await rpc.request("/", {
method: "sendrawtransaction",
params: [txInfoSigned.transaction],
});
async function check() {
let gobjResult = await rpc("gobject", "check", gobj.dataHex).catch(
/** @param {any} err */ function (err) {
console.error(err.message);
console.error(err.code);
console.error(err);
// invalid burn hash
return null;
},
);

// { result: { 'Object status': 'OK' }, error: null, id: 5542 }
if (gobjResult?.["Object status"] !== "OK") {
throw new Error(`gobject failed: ${gobjResult.error}`);
}
return gobjResult;
}

await check();

// ./bin/gobject-prepare.js 1 3 100 https://example.com/proposal-00 proposal-00 yPPy7Z5RQj46SnFtuFXyT6DFAygxESPR7K ./yjZxu7SJAwgSm1JtWybuQRYQDx34z8P2Z7.wif
// set to false to short circuit for testing
if (true) {
let txResult = await rpc("sendrawtransaction", txInfoSigned.transaction);
console.log("");
console.log("Transaction sent:");
console.log(txResult);
}

for (;;) {
let txResult = await rpc
.request("/", {
method: "gettxoutproof",
params: [[txid]],
})
.catch(
/** @param {Error} err */ function (err) {
const E_NOT_IN_BLOCK = -5;
// @ts-ignore - code exists
let code = err.code;
if (code === E_NOT_IN_BLOCK) {
return null;
}
throw err;
},
);
let txResult = await rpc("gettxoutproof", [txid]).catch(
/** @param {Error} err */ function (err) {
const E_NOT_IN_BLOCK = -5;
// @ts-ignore - code exists
let code = err.code;
if (code === E_NOT_IN_BLOCK) {
return null;
}
throw err;
},
);
if (txResult) {
console.log("");
console.log(`TxOutProof`);
console.log(txResult);
let jsonResult = await rpc.request("/", {
method: "getrawtransaction",
params: [txid, 1],
});
let jsonResult = await rpc("getrawtransaction", txid, 1);
console.log("");
console.log(`Tx`);
console.log(jsonResult);
Expand All @@ -394,46 +383,27 @@ async function main() {
await DashGov.utils.sleep(5000);
}

// async function check() {
// let gobjResult = await rpc
// .request("/", {
// method: "gobject",
// params: ["check", gobj.dataHex],
// })
// .catch(
// /** @param {Error} err */ function (err) {
// console.error(err.message);
// console.error(err.code);
// console.error(err);
// // invalid collateral hash
// return null;
// },
// );

// return gobjResult;
// }

async function submit() {
let req = {
method: "gobject",
params: [
"submit",
gobj.hashParent.toString(), // '0' must be a string for some reason
gobj.revision.toString(),
gobj.hashParent?.toString() || "0", // '0' must be a string for some reason
gobj.revision?.toString() || "1",
gobj.time.toString(),
gobj.dataHex,
txid,
],
};
let args = req.params.join(" ");
console.log(`${req.method} ${args}`);
let gobjResult = await rpc.request("/", req).catch(
let gobjResult = await rpc("gobject", ...req.params).catch(
/** @param {Error} err */ function (err) {
const E_INVALID_COLLATERAL = -32603;
// @ts-ignore - code exists
let code = err.code;
if (code === E_INVALID_COLLATERAL) {
// wait for collateral to become valid
// wait for burn to become valid
console.error(code, err.message);
return null;
}
Expand Down
Loading
Loading