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";
|
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;
|
|
|
|
reqSigs: number;
|
|
|
|
type: string;
|
|
|
|
addresses: string[];
|
|
|
|
}
|
|
|
|
|
|
|
|
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[];
|
|
|
|
// TODO: fill me out, what is this?
|
|
|
|
vjoinsplit: any[];
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
// TODO: Type all methods with signatures from
|
|
|
|
// https://github.com/zcash/zcash/blob/master/doc/payment-api.md
|
|
|
|
interface ZCashNode {
|
|
|
|
getblockchaininfo: () => Promise<BlockChainInfo>;
|
|
|
|
getblockcount: () => Promise<number>;
|
|
|
|
getblock: {
|
|
|
|
(numberOrHash: string | number, verbosity?: 1): Promise<BlockWithTransactionIds>;
|
|
|
|
(numberOrHash: string | number, verbosity: 2): Promise<BlockWithTransactions>;
|
|
|
|
(numberOrHash: string | number, verbosity: 0): Promise<string>;
|
|
|
|
}
|
|
|
|
gettransaction: (txid: string) => Promise<Transaction>;
|
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>;
|
|
|
|
z_getnewaddress: (type?: 'sprout' | 'sapling') => Promise<string>;
|
|
|
|
z_listaddresses: () => Promise<string[]>;
|
|
|
|
z_listreceivedbyaddress: (address: string, minConf?: number) => Promise<Receipt[]>;
|
|
|
|
z_importviewingkey: (key: string, rescan?: 'yes' | 'no' | 'whenkeyisnew', startHeight?: number) => Promise<void>;
|
|
|
|
z_exportviewingkey: (zaddr: string) => Promise<string>;
|
|
|
|
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,
|
|
|
|
password: env.ZCASH_NODE_PASSWORD,
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
network = bitcore.Networks.mainnet;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch(err) {
|
2019-01-18 16:34:11 -08:00
|
|
|
captureException(err);
|
|
|
|
log.error(err.response ? err.response.data : err);
|
|
|
|
log.error('Failed to connect to zcash node with the following credentials:\r\n', rpcOptions);
|
2018-12-28 15:24:46 -08:00
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if sprout address is readable
|
|
|
|
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) {
|
2019-01-18 16:34:11 -08:00
|
|
|
log.error('Unable to view SPROUT_ADDRESS and missing SPROUT_VIEWKEY environment variable, exiting');
|
2018-12-28 15:24:46 -08:00
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
await node.z_importviewingkey(env.SPROUT_VIEWKEY as string);
|
|
|
|
await node.z_getbalance(env.SPROUT_ADDRESS as string);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getNetwork() {
|
|
|
|
if (!network) {
|
|
|
|
throw new Error('Called getNetwork before initNode');
|
|
|
|
}
|
|
|
|
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 {
|
|
|
|
const tx = await node.gettransaction(txid);
|
|
|
|
const block = await node.getblock(tx.blockhash);
|
2019-03-04 11:40:37 -08:00
|
|
|
const height = block.height - parseInt(env.MINIMUM_BLOCK_CONFIRMATIONS, 10);
|
|
|
|
return height.toString();
|
2019-01-21 09:51:49 -08:00
|
|
|
} catch(err) {
|
|
|
|
console.warn(`Attempted to get block height for tx ${txid} but failed with the following error:\n`, err);
|
|
|
|
console.warn('Falling back to hard-coded starter blocks');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we can't find the latest tx block, fall back to when the grant
|
|
|
|
// system first launched, and scan from there.
|
|
|
|
const net = getNetwork();
|
|
|
|
if (net === bitcore.Networks.mainnet) {
|
|
|
|
return env.MAINNET_START_BLOCK;
|
|
|
|
}
|
|
|
|
else if (net === bitcore.Networks.testnet && !net.regtestEnabled) {
|
|
|
|
return env.TESTNET_START_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Regtest or otherwise unknown networks should start at the bottom
|
|
|
|
return '0';
|
|
|
|
}
|