2018-12-28 15:24:46 -08:00
|
|
|
import stdrpc from "stdrpc";
|
|
|
|
import bitcore from "zcash-bitcore-lib";
|
2019-01-18 16:34:11 -08:00
|
|
|
import { captureException } from "@sentry/node";
|
2018-12-28 15:24:46 -08:00
|
|
|
import env from "./env";
|
2019-01-18 16:34:11 -08:00
|
|
|
import log from "./log";
|
2019-03-19 12:56:58 -07:00
|
|
|
import { extractErrMessage } from "./util";
|
2018-12-28 15:24:46 -08:00
|
|
|
|
|
|
|
export interface BlockChainInfo {
|
|
|
|
chain: string;
|
|
|
|
blocks: number;
|
|
|
|
headers: number;
|
|
|
|
bestblockhash: string;
|
|
|
|
difficulty: number;
|
|
|
|
// Much much more, but not necessary
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ScriptPubKey {
|
|
|
|
asm: string;
|
|
|
|
hex: string;
|
|
|
|
type: string;
|
2019-03-28 08:35:45 -07:00
|
|
|
reqSigs?: number;
|
|
|
|
addresses?: string[];
|
2018-12-28 15:24:46 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface VIn {
|
|
|
|
sequence: number;
|
|
|
|
coinbase?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface VOut {
|
|
|
|
value: number;
|
|
|
|
valueZat: number;
|
|
|
|
n: number;
|
|
|
|
scriptPubKey: ScriptPubKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Transaction {
|
|
|
|
txid: string;
|
|
|
|
hex: string;
|
|
|
|
version: number;
|
|
|
|
locktime: number;
|
|
|
|
expiryheight: number;
|
|
|
|
blockhash: string;
|
|
|
|
blocktime: number;
|
|
|
|
confirmations: number;
|
|
|
|
time: number;
|
|
|
|
vin: VIn[];
|
|
|
|
vout: VOut[];
|
2019-03-13 14:39:50 -07:00
|
|
|
// unclear what vjoinsplit is
|
2018-12-28 15:24:46 -08:00
|
|
|
vjoinsplit: any[];
|
|
|
|
}
|
|
|
|
|
2019-03-20 11:52:47 -07:00
|
|
|
export interface RawTransaction {
|
|
|
|
txid: string;
|
|
|
|
hex: string;
|
|
|
|
overwintered: boolean;
|
|
|
|
version: number;
|
|
|
|
versiongroupid: number;
|
|
|
|
locktime: number;
|
|
|
|
expiryheight: string;
|
|
|
|
vin: VIn[];
|
|
|
|
vout: VOut[];
|
|
|
|
valueBalance: string;
|
|
|
|
blockhash: string;
|
|
|
|
blocktime: number;
|
|
|
|
confirmations: number;
|
|
|
|
time: number;
|
|
|
|
// unclear what these are
|
|
|
|
vjoinsplit: any[];
|
|
|
|
vShieldedSpend: any[];
|
|
|
|
vShieldedOutput: any[];
|
|
|
|
}
|
|
|
|
|
2018-12-28 15:24:46 -08:00
|
|
|
export interface Block {
|
|
|
|
hash: string;
|
|
|
|
confirmations: number;
|
|
|
|
size: number;
|
|
|
|
height: number;
|
|
|
|
version: number;
|
|
|
|
merkleroot: string;
|
|
|
|
finalsaplingroot: string;
|
|
|
|
time: number;
|
|
|
|
nonce: string;
|
|
|
|
solution: string;
|
|
|
|
bits: string;
|
|
|
|
difficulty: number;
|
|
|
|
chainwork: string;
|
|
|
|
anchor: string;
|
|
|
|
// valuePools ?
|
|
|
|
previousblockhash?: string;
|
|
|
|
nextblockhash?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface BlockWithTransactionIds extends Block {
|
|
|
|
tx: string[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface BlockWithTransactions extends Block {
|
|
|
|
tx: Transaction[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Receipt {
|
|
|
|
txid: string;
|
|
|
|
amount: number;
|
|
|
|
memo: string;
|
|
|
|
change: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface DisclosedPayment {
|
|
|
|
txid: string;
|
|
|
|
jsIndex: number;
|
|
|
|
outputIndex: number;
|
|
|
|
version: number;
|
|
|
|
onetimePrivKey: string;
|
|
|
|
joinSplitPubKey: string;
|
|
|
|
signatureVerified: boolean;
|
|
|
|
paymentAddress: string;
|
|
|
|
memo: string;
|
|
|
|
value: number;
|
|
|
|
commitmentMatch: boolean;
|
|
|
|
valid: boolean;
|
|
|
|
message?: string;
|
|
|
|
}
|
|
|
|
|
2019-02-05 12:26:37 -08:00
|
|
|
// Actually comes back with a bunch of args, but this is all we need
|
|
|
|
export interface ValidationResponse {
|
|
|
|
isvalid: boolean;
|
|
|
|
}
|
|
|
|
|
2018-12-28 15:24:46 -08:00
|
|
|
// https://github.com/zcash/zcash/blob/master/doc/payment-api.md
|
|
|
|
interface ZCashNode {
|
|
|
|
getblockchaininfo: () => Promise<BlockChainInfo>;
|
|
|
|
getblockcount: () => Promise<number>;
|
|
|
|
getblock: {
|
2019-03-13 14:39:50 -07:00
|
|
|
(numberOrHash: string | number, verbosity?: 1): Promise<
|
|
|
|
BlockWithTransactionIds
|
|
|
|
>;
|
|
|
|
(numberOrHash: string | number, verbosity: 2): Promise<
|
|
|
|
BlockWithTransactions
|
|
|
|
>;
|
2018-12-28 15:24:46 -08:00
|
|
|
(numberOrHash: string | number, verbosity: 0): Promise<string>;
|
2019-03-13 14:39:50 -07:00
|
|
|
};
|
2018-12-28 15:24:46 -08:00
|
|
|
gettransaction: (txid: string) => Promise<Transaction>;
|
2019-03-20 11:52:47 -07:00
|
|
|
getrawtransaction: {
|
|
|
|
(numberOrHash: string | number, verbosity: 1): Promise<RawTransaction>;
|
|
|
|
(numberOrHash: string | number, verbosity?: 0): Promise<string>;
|
|
|
|
};
|
2019-02-05 12:26:37 -08:00
|
|
|
validateaddress: (address: string) => Promise<ValidationResponse>;
|
2018-12-28 15:24:46 -08:00
|
|
|
z_getbalance: (address: string, minConf?: number) => Promise<number>;
|
2019-03-13 14:39:50 -07:00
|
|
|
z_getnewaddress: (type?: "sprout" | "sapling") => Promise<string>;
|
2018-12-28 15:24:46 -08:00
|
|
|
z_listaddresses: () => Promise<string[]>;
|
2019-03-13 14:39:50 -07:00
|
|
|
z_listreceivedbyaddress: (
|
|
|
|
address: string,
|
|
|
|
minConf?: number
|
|
|
|
) => Promise<Receipt[]>;
|
|
|
|
z_importviewingkey: (
|
|
|
|
key: string,
|
|
|
|
rescan?: "yes" | "no" | "whenkeyisnew",
|
|
|
|
startHeight?: number
|
|
|
|
) => Promise<void>;
|
2018-12-28 15:24:46 -08:00
|
|
|
z_exportviewingkey: (zaddr: string) => Promise<string>;
|
2019-03-13 14:39:50 -07:00
|
|
|
z_validatepaymentdisclosure: (
|
|
|
|
disclosure: string
|
|
|
|
) => Promise<DisclosedPayment>;
|
2019-02-05 12:26:37 -08:00
|
|
|
z_validateaddress: (address: string) => Promise<ValidationResponse>;
|
2018-12-28 15:24:46 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
export const rpcOptions = {
|
|
|
|
url: env.ZCASH_NODE_URL,
|
|
|
|
username: env.ZCASH_NODE_USERNAME,
|
2019-03-13 14:39:50 -07:00
|
|
|
password: env.ZCASH_NODE_PASSWORD
|
2018-12-28 15:24:46 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
const node: ZCashNode = stdrpc(rpcOptions);
|
|
|
|
|
|
|
|
export default node;
|
|
|
|
|
|
|
|
let network: any;
|
|
|
|
export async function initNode() {
|
|
|
|
// Check if node is available & setup network
|
|
|
|
try {
|
|
|
|
const info = await node.getblockchaininfo();
|
2019-01-18 16:34:11 -08:00
|
|
|
log.info(`Connected to ${info.chain} node at block height ${info.blocks}`);
|
2018-12-28 15:24:46 -08:00
|
|
|
|
|
|
|
if (info.chain === "regtest") {
|
|
|
|
bitcore.Networks.enableRegtest();
|
|
|
|
}
|
2019-01-21 09:51:49 -08:00
|
|
|
if (info.chain.includes("test")) {
|
2018-12-28 15:24:46 -08:00
|
|
|
network = bitcore.Networks.testnet;
|
2019-03-13 14:39:50 -07:00
|
|
|
} else {
|
2018-12-28 15:24:46 -08:00
|
|
|
network = bitcore.Networks.mainnet;
|
|
|
|
}
|
2019-03-13 14:39:50 -07:00
|
|
|
} catch (err) {
|
2019-01-18 16:34:11 -08:00
|
|
|
captureException(err);
|
2019-03-19 12:56:58 -07:00
|
|
|
log.error(extractErrMessage(err));
|
|
|
|
log.error(`Failed to connect to zcash node with the following credentials: ${JSON.stringify(rpcOptions, null, 2)}`);
|
2018-12-28 15:24:46 -08:00
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if sprout address is readable
|
2019-03-19 12:56:58 -07:00
|
|
|
// NOTE: Replace with sapling when ready
|
|
|
|
// try {
|
|
|
|
// if (!env.SPROUT_ADDRESS) {
|
|
|
|
// console.error("Missing SPROUT_ADDRESS environment variable, exiting");
|
|
|
|
// process.exit(1);
|
|
|
|
// }
|
|
|
|
// await node.z_getbalance(env.SPROUT_ADDRESS as string);
|
|
|
|
// } catch (err) {
|
|
|
|
// if (!env.SPROUT_VIEWKEY) {
|
|
|
|
// log.error(
|
|
|
|
// "Unable to view SPROUT_ADDRESS and missing SPROUT_VIEWKEY environment variable, exiting"
|
|
|
|
// );
|
|
|
|
// process.exit(1);
|
|
|
|
// }
|
|
|
|
// await node.z_importviewingkey(env.SPROUT_VIEWKEY as string);
|
|
|
|
// await node.z_getbalance(env.SPROUT_ADDRESS as string);
|
|
|
|
// }
|
2018-12-28 15:24:46 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getNetwork() {
|
|
|
|
if (!network) {
|
2019-03-13 14:39:50 -07:00
|
|
|
throw new Error("Called getNetwork before initNode");
|
2018-12-28 15:24:46 -08:00
|
|
|
}
|
|
|
|
return network;
|
|
|
|
}
|
2019-01-21 09:51:49 -08:00
|
|
|
|
|
|
|
// Relies on initNode being called first
|
|
|
|
export async function getBootstrapBlockHeight(txid: string | undefined) {
|
|
|
|
if (txid) {
|
|
|
|
try {
|
2019-03-20 11:52:47 -07:00
|
|
|
const tx = await node.getrawtransaction(txid, 1);
|
2019-01-21 09:51:49 -08:00
|
|
|
const block = await node.getblock(tx.blockhash);
|
2019-03-13 14:39:50 -07:00
|
|
|
const height =
|
|
|
|
block.height - parseInt(env.MINIMUM_BLOCK_CONFIRMATIONS, 10);
|
2019-03-04 11:40:37 -08:00
|
|
|
return height.toString();
|
2019-03-13 14:39:50 -07:00
|
|
|
} catch (err) {
|
2019-03-19 12:56:58 -07:00
|
|
|
log.warn(`Attempted to get block height for tx ${txid} but failed with the following error: ${extractErrMessage(err)}`);
|
2019-01-21 09:51:49 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we can't find the latest tx block, fall back to when the grant
|
2019-03-19 12:56:58 -07:00
|
|
|
// system first launched, and scan from there. Regtest or unknown networks
|
|
|
|
// start from the bottom.
|
2019-01-21 09:51:49 -08:00
|
|
|
const net = getNetwork();
|
2019-03-19 12:56:58 -07:00
|
|
|
let height = "0";
|
2019-01-21 09:51:49 -08:00
|
|
|
if (net === bitcore.Networks.mainnet) {
|
2019-03-19 12:56:58 -07:00
|
|
|
height = env.MAINNET_START_BLOCK;
|
2019-03-13 14:39:50 -07:00
|
|
|
} else if (net === bitcore.Networks.testnet && !net.regtestEnabled) {
|
2019-03-19 12:56:58 -07:00
|
|
|
height = env.TESTNET_START_BLOCK;
|
2019-01-21 09:51:49 -08:00
|
|
|
}
|
|
|
|
|
2019-03-19 12:56:58 -07:00
|
|
|
log.info(`Falling back to hard-coded starter block height ${height}`);
|
|
|
|
return height;
|
2019-01-21 09:51:49 -08:00
|
|
|
}
|