From eee0a617c2be8e1169bb6a6108b2436da441820f Mon Sep 17 00:00:00 2001 From: ltardivo Date: Tue, 14 Jan 2025 20:20:07 -0300 Subject: [PATCH 1/2] Improves logs. Creates error and application log files. --- packages/node/chain-setup/BTC/mainnet/.env.example | 12 ++++++------ packages/node/chain-setup/BTC/regtest/.env.example | 12 ++++++------ packages/node/chain-setup/BTC/testnet/.env.example | 12 ++++++------ packages/node/chain-setup/DOGE/mainnet/.env.example | 12 ++++++------ packages/node/chain-setup/DOGE/regtest/.env.example | 12 ++++++------ packages/node/chain-setup/DOGE/testnet/.env.example | 12 ++++++------ packages/node/chain-setup/LTC/mainnet/.env.example | 12 ++++++------ packages/node/chain-setup/LTC/regtest/.env.example | 12 ++++++------ packages/node/chain-setup/LTC/testnet/.env.example | 12 ++++++------ packages/node/chain-setup/PEPE/mainnet/.env.example | 12 ++++++------ packages/node/chain-setup/PEPE/regtest/.env.example | 12 ++++++------ packages/node/chain-setup/PEPE/testnet/.env.example | 12 ++++++------ packages/node/dist/bcn.es.mjs | 2 +- packages/node/dist/bcn.sync.es.mjs | 2 +- 14 files changed, 74 insertions(+), 74 deletions(-) diff --git a/packages/node/chain-setup/BTC/mainnet/.env.example b/packages/node/chain-setup/BTC/mainnet/.env.example index 9f34e8612..ba901a629 100644 --- a/packages/node/chain-setup/BTC/mainnet/.env.example +++ b/packages/node/chain-setup/BTC/mainnet/.env.example @@ -34,8 +34,6 @@ BCN_PORT='1031' # Enable to launch with fixed number of parallel workers # BCN_NUM_WORKERS='6' -# Setup the environment to prod or dev -BCN_ENV=dev BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -47,14 +45,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -65,8 +66,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/chain-setup/BTC/regtest/.env.example b/packages/node/chain-setup/BTC/regtest/.env.example index 4ba87555d..94e092293 100644 --- a/packages/node/chain-setup/BTC/regtest/.env.example +++ b/packages/node/chain-setup/BTC/regtest/.env.example @@ -32,8 +32,6 @@ BITCOIN_DEFAULT_WALLET='defaultwallet' # Port for Bitcoin Computer Node BCN_PORT='1031' -# Setup the environment to prod or dev -BCN_ENV=dev BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -45,14 +43,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -63,8 +64,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/chain-setup/BTC/testnet/.env.example b/packages/node/chain-setup/BTC/testnet/.env.example index e483b1228..1c5f92b16 100644 --- a/packages/node/chain-setup/BTC/testnet/.env.example +++ b/packages/node/chain-setup/BTC/testnet/.env.example @@ -36,8 +36,6 @@ BCN_PORT='1031' # Enable to launch with fixed number of parallel workers # BCN_NUM_WORKERS='6' -# Setup the environment to prod or dev -BCN_ENV=dev BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -49,14 +47,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -67,8 +68,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/chain-setup/DOGE/mainnet/.env.example b/packages/node/chain-setup/DOGE/mainnet/.env.example index 367b0f68b..b469c42cf 100644 --- a/packages/node/chain-setup/DOGE/mainnet/.env.example +++ b/packages/node/chain-setup/DOGE/mainnet/.env.example @@ -34,8 +34,6 @@ BCN_PORT='1031' # Enable to launch with fixed number of parallel workers # BCN_NUM_WORKERS='6' -# Setup the environment to prod or dev -BCN_ENV=dev BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -48,14 +46,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -66,8 +67,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/chain-setup/DOGE/regtest/.env.example b/packages/node/chain-setup/DOGE/regtest/.env.example index 17dee71d4..3148314b8 100644 --- a/packages/node/chain-setup/DOGE/regtest/.env.example +++ b/packages/node/chain-setup/DOGE/regtest/.env.example @@ -35,8 +35,6 @@ BCN_PORT='1031' # Enable to launch with fixed number of parallel workers # BCN_NUM_WORKERS='6' -# Setup the environment to prod or dev -BCN_ENV=dev BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -49,14 +47,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -67,8 +68,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/chain-setup/DOGE/testnet/.env.example b/packages/node/chain-setup/DOGE/testnet/.env.example index b2741fdc4..022e7554a 100644 --- a/packages/node/chain-setup/DOGE/testnet/.env.example +++ b/packages/node/chain-setup/DOGE/testnet/.env.example @@ -36,8 +36,6 @@ BCN_PORT='1031' # Enable to launch with fixed number of parallel workers # BCN_NUM_WORKERS='6' -# Setup the environment to prod or dev -BCN_ENV=dev BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -49,14 +47,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -67,8 +68,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/chain-setup/LTC/mainnet/.env.example b/packages/node/chain-setup/LTC/mainnet/.env.example index 06f230fe7..88aacf294 100644 --- a/packages/node/chain-setup/LTC/mainnet/.env.example +++ b/packages/node/chain-setup/LTC/mainnet/.env.example @@ -35,8 +35,6 @@ BCN_PORT='1031' # Enable to launch with fixed number of parallel workers # BCN_NUM_WORKERS='6' -# Setup the environment to prod or dev -BCN_ENV=dev BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -48,14 +46,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -66,8 +67,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/chain-setup/LTC/regtest/.env.example b/packages/node/chain-setup/LTC/regtest/.env.example index 2769a6125..e59d72b8a 100644 --- a/packages/node/chain-setup/LTC/regtest/.env.example +++ b/packages/node/chain-setup/LTC/regtest/.env.example @@ -36,8 +36,6 @@ BCN_PORT='1031' # Enable to launch with fixed number of parallel workers # BCN_NUM_WORKERS='6' -# Setup the environment to prod or dev -BCN_ENV=dev BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -49,14 +47,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -67,8 +68,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/chain-setup/LTC/testnet/.env.example b/packages/node/chain-setup/LTC/testnet/.env.example index 52a2f8162..90ad87bd6 100644 --- a/packages/node/chain-setup/LTC/testnet/.env.example +++ b/packages/node/chain-setup/LTC/testnet/.env.example @@ -36,8 +36,6 @@ BCN_PORT='1031' # Enable to launch with fixed number of parallel workers # BCN_NUM_WORKERS='6' -# Setup the environment to prod or dev -BCN_ENV=dev BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -49,14 +47,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -67,8 +68,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/chain-setup/PEPE/mainnet/.env.example b/packages/node/chain-setup/PEPE/mainnet/.env.example index 7488d51c3..9dbf829b7 100644 --- a/packages/node/chain-setup/PEPE/mainnet/.env.example +++ b/packages/node/chain-setup/PEPE/mainnet/.env.example @@ -34,8 +34,6 @@ BCN_PORT='1031' # Enable to launch with fixed number of parallel workers # BCN_NUM_WORKERS='6' -# Setup the environment to prod or dev -BCN_ENV=dev BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -48,14 +46,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -66,8 +67,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/chain-setup/PEPE/regtest/.env.example b/packages/node/chain-setup/PEPE/regtest/.env.example index abade312a..abe97c04b 100644 --- a/packages/node/chain-setup/PEPE/regtest/.env.example +++ b/packages/node/chain-setup/PEPE/regtest/.env.example @@ -35,8 +35,6 @@ BCN_PORT='1031' # Enable to launch with fixed number of parallel workers # BCN_NUM_WORKERS='6' -# Setup the environment to prod or dev -BCN_ENV=dev BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -49,14 +47,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -67,8 +68,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/chain-setup/PEPE/testnet/.env.example b/packages/node/chain-setup/PEPE/testnet/.env.example index bb7acab56..18d236037 100644 --- a/packages/node/chain-setup/PEPE/testnet/.env.example +++ b/packages/node/chain-setup/PEPE/testnet/.env.example @@ -36,8 +36,6 @@ BCN_PORT='1031' # Enable to launch with fixed number of parallel workers # BCN_NUM_WORKERS='6' -# Setup the environment to prod or dev -BCN_ENV=dev BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -49,14 +47,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -67,8 +68,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/dist/bcn.es.mjs b/packages/node/dist/bcn.es.mjs index ca1718630..0b316241e 100644 --- a/packages/node/dist/bcn.es.mjs +++ b/packages/node/dist/bcn.es.mjs @@ -1 +1 @@ -import t from"body-parser";import e from"cors";import s from"express";import r from"http";import*as a from"zeromq";import n from"express-rate-limit";import*as o from"@bitcoin-computer/secp256k1";import{crypto as i,networks as c,bufferUtils as u,script as l,address as p,payments as d,Psbt as m,initEccLib as h}from"@bitcoin-computer/nakamotojs";import g from"dotenv";import y from"winston";import w from"winston-daily-rotate-file";import v from"pg-promise";import E from"pg-monitor";import{backOff as f}from"exponential-backoff";import T from"fs";import{ECPairFactory as O}from"ecpair";import{Computer as S,Transaction as $}from"@bitcoin-computer/lib";import I from"bitcoind-rpc";import R from"util";import b from"elliptic";import x from"hash.js";import N,{dirname as M}from"path";import{fileURLToPath as C}from"url";g.config();const B=process.env.BCN_CHAIN;const L=process.env.BCN_NETWORK;const{BCN_PORT:A}=process.env;const{BCN_ZMQ_URL:P}=process.env;const{BCN_ALLOWED_RPC_METHODS:H}=process.env;const{BCN_DEBUG_MODE:k}=process.env;const{BCN_LOG_MAX_FILES:j}=process.env;const{BCN_LOG_MAX_SIZE:_}=process.env;const{BCN_LOG_ZIP:U}=process.env;const{BCN_SHOW_CONSOLE_LOGS:D}=process.env;const{BCN_SHOW_DB_LOGS:F}=process.env;const{BCN_RATE_LIMIT_ENABLED:W}=process.env;const{BCN_RATE_LIMIT_WINDOW:G}=process.env;const{BCN_RATE_LIMIT_MAX:Y}=process.env;const{BCN_RATE_LIMIT_STANDARD_HEADERS:K}=process.env;const{BCN_RATE_LIMIT_LEGACY_HEADERS:q}=process.env;process.env,process.env,process.env;const{BCN_OFFCHAIN_PROTOCOL:J}=process.env;const V=process.env.BCN_QUERY_LIMIT||"1000";const z=process.env.BCN_URL||`http://127.0.0.1:${A}`;const Z=process.env.BCN_ENV||"dev";const X=process.env.BCN_ZMQ_ACTIVATION_HEIGHT||"1";const{BITCOIN_RPC_USER:Q}=process.env;const{BITCOIN_RPC_PASSWORD:tt}=process.env;const{BITCOIN_RPC_HOST:et}=process.env;const{BITCOIN_RPC_PORT:st}=process.env;const{BITCOIN_RPC_PROTOCOL:rt}=process.env;const{BITCOIN_DEFAULT_WALLET:at}=process.env;const{POSTGRES_USER:nt}=process.env;const{POSTGRES_PASSWORD:ot}=process.env;const{POSTGRES_DB:it}=process.env;const{POSTGRES_HOST:ct}=process.env;const{POSTGRES_PORT:ut}=process.env;const{POSTGRES_MAX_CONNECTIONS:lt}=process.env;const{POSTGRES_IDLE_TIMEOUT_MILLIS:pt}=process.env;y.addColors({error:"red",warn:"yellow",info:"green",http:"magenta",debug:"white"});const dt=y.format.combine(y.format.colorize(),y.format.timestamp({format:"YYYY-MM-DD HH:mm:ss:ms"}),y.format.json(),y.format.printf((t=>`${t.timestamp} [${t.level.slice(5).slice(0,-5)}] ${t.message}`)));const mt={zippedArchive:"true"===U,maxSize:_,maxFiles:j,dirname:"logs"};const ht=[];"true"===D&&ht.push(new y.transports.Console({format:y.format.combine(y.format.colorize(),y.format.timestamp({format:"MM-DD-YYYY HH:mm:ss"}),y.format.printf((t=>`${t.timestamp} ${t.level} ${t.message}`)))}));const gt=parseInt(k,10);gt>=0&&ht.push(new w({filename:"error-%DATE%.log",datePattern:"YYYY-MM-DD",level:"error",...mt})),gt>=1&&ht.push(new w({filename:"warn-%DATE%.log",datePattern:"YYYY-MM-DD",level:"warn",...mt})),gt>=2&&ht.push(new w({filename:"info-%DATE%.log",datePattern:"YYYY-MM-DD",level:"info",...mt})),gt>=3&&ht.push(new w({filename:"http-%DATE%.log",datePattern:"YYYY-MM-DD",level:"http",...mt})),gt>=4&&ht.push(new w({filename:"debug-%DATE%.log",datePattern:"YYYY-MM-DD",level:"debug",...mt}));const yt=y.createLogger({levels:{error:0,warn:1,info:2,http:3,debug:4},format:dt,transports:ht,exceptionHandlers:[new y.transports.File({filename:"logs/exceptions.log"})],rejectionHandlers:[new y.transports.File({filename:"logs/rejections.log"})]});g.config();const{version:wt}=JSON.parse(T.readFileSync("package.json","utf8"));const vt=wt||process.env.BCN_SERVER_VERSION;const Et=parseInt(process.env.MWEB_HEIGHT||"",10)||432;const ft={error:(t,e)=>{if(e.cn){const{host:s,port:r,database:a,user:n,password:o}=e.cn;yt.debug(`Waiting for db to start { message:${t.message} host:${s}, port:${r}, database:${a}, user:${n}, password: ${o}`)}},noWarnings:!0};"true"===F&&(E.isAttached()?E.detach():(E.attach(ft),E.setTheme("matrix")));const Tt=v(ft)({host:ct,port:parseInt(ut,10),database:it,user:nt,password:ot,max:parseInt(lt,10),allowExitOnIdle:!0,idleTimeoutMillis:parseInt(pt,10)});const{PreparedStatement:Ot}=v;class St{static async select(t){const e=new Ot({name:`OffChain.select.${Math.random()}`,text:'SELECT "data" FROM "OffChain" WHERE "id" = $1',values:[t]});return Tt.oneOrNone(e)}static async insert({id:t,data:e}){const s=new Ot({name:`OffChain.insert.${Math.random()}`,text:'INSERT INTO "OffChain" ("id", "data") VALUES ($1, $2) ON CONFLICT DO NOTHING',values:[t,e]});return Tt.none(s)}static async delete(t){const e=new Ot({name:`OffChain.delete.${Math.random()}`,text:'WITH deleted AS (DELETE FROM "OffChain" WHERE "id" = $1 RETURNING *) SELECT count(*) FROM deleted;',values:[t]});return(await Tt.any(e))[0].count>0}}class $t{static async select(t){const e=await St.select(t);return e?.data||null}static async insert(t){return St.insert(t)}static async delete(t){return St.delete(t)}}const It=s.Router();It.get("/:id",(async({params:{id:t},url:e},s)=>{try{const e=await $t.select(t);e?s.status(200).json(e):s.status(403).json({error:"No entry found."})}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),It.post("/",(async(t,e)=>{const{body:{data:s},url:r}=t;try{const r=i.sha256(Buffer.from(s)).toString("hex");await $t.insert({id:r,data:s});const a=`${J||t.protocol}://${t.get("host")}/store/${r}`;e.status(201).json({_url:a})}catch(t){yt.error(`POST ${r} failed with error '${t.message}'`),e.status(500).json({error:t.message})}})),It.delete("/:id",(async(t,e)=>{e.status(500).json({error:"Deletions are not supported yet."})}));const{PreparedStatement:Rt}=v;class bt{static async getBalance(t){const e=new Rt({name:`Utxos.getBalance.${Math.random()}`,text:'SELECT sum("satoshis") as "satoshis" FROM "Utxos" WHERE "address" = $1 and "blockHash" is not null',values:[t]});const s=await Tt.oneOrNone(e);const r=new Rt({name:`Utxos.getBalance.${Math.random()}`,text:'SELECT sum("satoshis") as "satoshis" FROM "Utxos" WHERE "address" = $1 and "blockHash" is null',values:[t]});const a=await Tt.oneOrNone(r);return{confirmed:parseInt(s.satoshis,10)||0,unconfirmed:parseInt(a.satoshis,10)||0,balance:(parseInt(s.satoshis,10)||0)+(parseInt(a.satoshis,10)||0)}}static async select(t){const e=new Rt({name:`Utxos.select.${Math.random()}`,text:'SELECT "address", "satoshis", "asm", "rev", split_part(rev, \':\', 1) AS "txId", cast(split_part(rev, \':\', 2) as INTEGER) AS "vout" FROM "Utxos" WHERE "address" = $1',values:[t]});return(await Tt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)||0})))}static async selectByScriptASM(t){const e=new Rt({name:`Utxos.select.${Math.random()}`,text:'SELECT "address", "satoshis", "asm", "rev", split_part(rev, \':\', 1) AS "txId", cast(split_part(rev, \':\', 2) as INTEGER) AS "vout" FROM "Utxos" WHERE "asm" = $1',values:[t]});return(await Tt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)||0})))}static async selectByPk(t){const e=new Rt({name:`Utxos.selectByPk.${Math.random()}`,text:`SELECT "address", "satoshis", "asm", "rev", split_part(rev, ':', 1) AS "txId", cast(split_part(rev, ':', 2) as INTEGER) AS "vout" FROM "Utxos" WHERE "asm" LIKE '%${t}%'`});return(await Tt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)})))}}class xt{static async getBalance(t){return bt.getBalance(t)}static async select(t){return bt.select(t)}static async selectByPk(t){return bt.selectByPk(t)}static async selectByScriptASM(t){return bt.selectByScriptASM(t)}}class Nt{static getBalance=async t=>xt.getBalance(t);static selectByAddress=async t=>xt.select(t);static selectByPk=async t=>xt.selectByPk(t);static selectByScriptASM=async t=>xt.selectByScriptASM(t)}const Mt={protocol:rt,user:Q,pass:tt,host:et,port:parseInt(st,10)};const Ct=new I(Mt);const Bt=R.promisify(I.prototype.createwallet.bind(Ct));const Lt=R.promisify(I.prototype.generateToAddress.bind(Ct));const At=R.promisify(I.prototype.getaddressinfo.bind(Ct));const Pt=R.promisify(I.prototype.getBlock.bind(Ct));const Ht=R.promisify(I.prototype.getBlockchainInfo.bind(Ct));const kt=R.promisify(I.prototype.getBlockHash.bind(Ct));const jt=R.promisify(I.prototype.getRawTransaction.bind(Ct));const _t=R.promisify(I.prototype.getRawTransaction.bind(Ct));const Ut=R.promisify(I.prototype.getTransaction.bind(Ct));const Dt=R.promisify(I.prototype.getNewAddress.bind(Ct));const Ft={createwallet:Bt,generateToAddress:Lt,getaddressinfo:At,getBlock:Pt,getBlockchainInfo:Ht,getBlockHash:kt,getRawTransaction:jt,getTransaction:Ut,importaddress:R.promisify(I.prototype.importaddress.bind(Ct)),invalidateBlock:R.promisify(I.prototype.invalidateBlock.bind(Ct)),listunspent:R.promisify(I.prototype.listunspent.bind(Ct)),sendRawTransaction:R.promisify(I.prototype.sendRawTransaction.bind(Ct)),getNewAddress:Dt,sendToAddress:R.promisify(I.prototype.sendToAddress.bind(Ct)),getRawTransactionJSON:_t};const Wt=(t,e)=>{const s=[];for(let r=0;r{const e=[];for(let s=1;s<=t;s+=3){const t=`($${s},$${s+1},$${s+2})`;e.push(t)}return e.join(",")};const Yt=t=>{const e=[];for(let s=1;s<=t;s+=9){const t=`($${s},$${s+1},$${s+2},$${s+3},$${s+4},$${s+5},$${s+6},$${s+7},$${s+8})`;e.push(t)}return e.join(",")};const Kt=t=>{try{return t()}catch{return null}};class qt{static async getTransaction(t){const{result:e}=await Ft.getTransaction(t);return e}static async getBulkTransactions(t){return(await Promise.all(t.map((t=>Ft.getRawTransaction(t,0))))).map((t=>t.result))}static async getRawTransaction(t,e){const{result:s}=await Ft.getRawTransaction(t,e);return s}static async getRawTransactionsJSON(t){return{txId:(e=(await Ft.getRawTransactionJSON(t,1)).result).txid,txHex:e.hex,vsize:e.vsize,version:e.version,locktime:e.locktime,ins:e.vin.map((t=>t.coinbase?{coinbase:t.coinbase,sequence:t.sequence}:{txId:t.txid,vout:t.vout,script:t.scriptSig.hex,sequence:t.sequence})),outs:e.vout.map((t=>{let e;return t.scriptPubKey.addresses?[e]=t.scriptPubKey.addresses:e=t.scriptPubKey.address?t.scriptPubKey.address:void 0,{address:e,script:t.scriptPubKey.hex,value:Math.round(1e8*t.value)}}))};var e}static async sendRawTransaction(t){const{result:e,error:s}=await Ft.sendRawTransaction(t);if(s)throw yt.error(s),new Error("Error sending transaction");return e}static getUtxos=async t=>(void 0===(await Ft.getaddressinfo(t)).result.timestamp&&(yt.info(`Importing address: ${t}`),await Ft.importaddress(t,!1)),(await Ft.listunspent(0,999999,[t])).result);static walletSetup=async()=>{if("regtest"===L){if(yt.info(`Node is starting for chain ${B} and network ${L}, \n\n. Starting Wallet setup.`),"LTC"===B){const{result:t}=await Ft.getBlockchainInfo();const e=t.blocks;if(e{try{await Ft.createwallet(at,!1,!1,"",!1,!1)}catch(t){if(t.message.includes("already exists"))return void yt.info(`Wallet ${at} already exists`);yt.error(`Wallet creation failed with error '${t.message}'`)}};static checkBlockchainProgress=async t=>{const e=await f((async()=>{const e=await Ft.getBlockchainInfo();const s=(100*parseFloat(e.result.verificationprogress)).toFixed(4);const{blocks:r}=e.result;if(yt.info(`Zmq. Bitcoind { percentage:${s}%, blocks:${r} }`),parseFloat(e.result.verificationprogress)<=t)throw new Error("Node not ready yet");return e}),{startingDelay:6e4,timeMultiple:1,numOfAttempts:8760});const s=(100*parseFloat(e.result.verificationprogress)).toFixed(4);const r=e.result.blocks;yt.info(`BCN reaches sync end...at { bitcoind.progress:${s}%, bitcoindSyncedHeight:${r} }`)}}class Jt{static get=async t=>qt.getTransaction(t);static getRaw=async t=>qt.getBulkTransactions(t);static getRawJSON=async t=>qt.getRawTransactionsJSON(t);static sendRaw=async t=>qt.sendRawTransaction(t);static getUtxos=async t=>qt.getUtxos(t);static parseTransactions=async(t,e,s,r)=>{let a=t;"LTC"===e&&(a=t.filter((t=>"08"!==t.hex.slice(10,12))));const n=[];for(const t of a)try{let{hex:e}=t;e||(e=(await qt.getRawTransaction(t.txid,1)).hex);const s=S.txFromHex({hex:e});s&&n.push(s)}catch(e){yt.error(`[wid ${s} pid: ${process.pid}: failed to parse transaction in block ${r}\n error message: ${e.message}\n transaction: ${JSON.stringify(t)}`)}return n};static walletSetup=async()=>qt.walletSetup()}const Vt={protocol:rt,user:Q,pass:tt,host:et,port:parseInt(st,10)};const zt=new I(Vt);const Zt={};const Xt=JSON.parse(JSON.stringify(I.callspec));Object.keys(Xt).forEach((t=>{Xt[t.toLowerCase()]=Xt[t]}));const Qt={str:t=>t.toString(),string:t=>t.toString(),int:t=>parseFloat(t),float:t=>parseFloat(t),bool:t=>!0===t||"1"===t||1===t||"true"===t||"true"===t.toString().toLowerCase(),obj:t=>"string"==typeof t?JSON.parse(t):t};try{Object.keys(I.prototype).forEach((t=>{if(t&&"function"==typeof I.prototype[t]){const e=t.toLowerCase();Zt[t]=R.promisify(I.prototype[t].bind(zt)),Zt[e]=R.promisify(I.prototype[e].bind(zt))}}))}catch(t){yt.error(`Error occurred while binding RPC methods: ${t.message}`)}function te(t){return/^[0-9A-Fa-f]{64}:\d+$/.test(t)}function ee(t){if(!te(t))throw new Error("Invalid rev")}const{PreparedStatement:se}=v;class re{static async listSentOutputs(t){const e=new se({name:`Output.listSentTxs.${Math.random()}`,text:'SELECT "Input"."spendingInput" AS "output", "Output"."satoshis" AS "amount"\n FROM "Output" INNER JOIN "Input" ON "Output".rev = "Input"."outputSpent" \n WHERE "Output"."address" = $1',values:[t]});return(await Tt.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listReceivedOutputs(t){const e=new se({name:`Output.listReceivedTxs.${Math.random()}`,text:'SELECT "Output"."rev" as "output", "Output"."satoshis" as "amount" FROM "Output" WHERE "address" = $1',values:[t]});return(await Tt.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listTxs(t){const e=new se({name:`Output.listTxs.${Math.random()}`,text:'WITH\n -- List all txs sent from a given address\n SENT AS (\n SELECT split_part("Input"."spendingInput",\':\',1) as "txId", SUM("Output".satoshis) as "satoshis"\n FROM "Output" INNER JOIN "Input" ON "Output".rev = "Input"."outputSpent"\n WHERE "Output".address = $1\n GROUP BY split_part("Input"."spendingInput",\':\',1)\n ),\n -- List all tx received from a given address\n RECEIVED AS (\n SELECT SPLIT_PART("Output"."rev",\':\',1) as "txId", SUM("Output"."satoshis") as "satoshis" \n FROM "Output" \n WHERE "address" = $1\n GROUP BY "txId"\n )\n\n SELECT\n RECEIVED."txId", \n coalesce(SENT."satoshis", 0) as "inputsSatoshis", \n coalesce(RECEIVED."satoshis", 0) as "outputsSatoshis", \n coalesce(RECEIVED."satoshis",0) - coalesce(SENT."satoshis",0) as "satoshis"\n FROM\n SENT RIGHT JOIN RECEIVED ON SENT."txId" = RECEIVED."txId";',values:[t]});const s=(await Tt.any(e)).map((t=>({...t,inputsSatoshis:parseInt(t.inputsSatoshis,10)||0,outputsSatoshis:parseInt(t.outputsSatoshis,10)||0,satoshis:parseInt(t.satoshis,10)||0})));return{sentTxs:s.filter((t=>t.satoshis<0)).map((t=>({...t,satoshis:Math.abs(t.satoshis)}))),receivedTxs:s.filter((t=>t.satoshis>=0))}}static async select(t){const e=new se({name:`Output.select.${Math.random()}`,text:'SELECT "address", "satoshis", "asm", "rev", "hash", "mod", "isTbcOutput", "previous", "blockHash" FROM "Output" WHERE "address" = $1',values:[t]});return Tt.any(e)}static async insert(t){await Promise.all(Wt(t,1111).map((t=>{const e=t.flatMap((({rev:t,address:e,satoshis:s,asm:r,isTbcOutput:a,mod:n,previous:o,hash:i,blockHash:c})=>[t,e,s,r,a,n,o,i,c]));return Tt.none(new se({name:`Output.insert.${Math.random()}`,text:`INSERT INTO "Output"("rev", "address", "satoshis", "asm", "isTbcOutput",\n "mod", "previous", "hash", "blockHash") VALUES ${Yt(e.length)} ON CONFLICT ("rev") \n DO UPDATE SET "blockHash" = COALESCE("Output"."blockHash", EXCLUDED."blockHash")`,values:e}))})))}static async eraseBlockHash(t){await Promise.all(Wt(t,1e4).map((t=>{const e=t.join("','");return Tt.none(new se({name:`Output.eraseBlockHash.${Math.random()}`,text:`UPDATE "Output" SET "blockHash" = NULL WHERE "blockHash" IN ('${e}')`}))})))}static async getIdByRev(t){const e=new se({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON r."previous" = o."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=(await Tt.any(e)).filter((t=>null===t.previous));return s[0]?.rev}static async getIdsByRevs(t){return Promise.all(t.map((t=>this.getIdByRev(t))))}static async getLatestRev(t){const e=new se({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON o."previous" = r."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=await Tt.any(e);const r=Object.fromEntries(s.map((t=>[t.previous,t.rev])));let a=t;for(;r[a];)a=r[a];return a}static async getLatestRevs(t){return Promise.all(t.map(this.getLatestRev))}static async getIdsByMod(t){const e=new se({name:`Output.getIdsByMod.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1',values:[t]});return(await Tt.any(e)).map((t=>t.rev))}static sqlSuffix(t,e,s){let r="";return s&&(r+=` order by "timestamp" ${s}`),r+=` limit ${t||V}`,e&&(r+=` offset ${e}`),r}static async getUnspentRevsByMod(t,e,s,r){const a=await this.getIdsByMod(t);const n=await this.getLatestRevs(a);const o=new se({name:`Output.getUnspentRevsByMod.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(e,s,r)}`,values:[n]});return(await Tt.any(o)).map((t=>t.rev))}static async getUnspentRevsByPublicKey(t,e,s,r){const a=new se({name:`Output.getUnspentRevsByPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE asm LIKE '%' || $1 || '%' AND "isTbcOutput" = true \n AND NOT EXISTS (SELECT 1 FROM "Input" ip WHERE "ip"."outputSpent" = "Output"."rev") \n ${this.sqlSuffix(e,s,r)}`,values:[t]});return(await Tt.any(a)).map((t=>t.rev))}static async getUnspentRevsByModAndPublicKey(t,e,s,r,a){const n=await this.getUnspentRevsByPublicKey(e,s,r,a);const o=await this.getIdsByRevs(n);const i=new se({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1 AND "rev" = ANY($2)',values:[t,o]});const c=(await Tt.any(i)).map((t=>t.rev));const u=await this.getLatestRevs(c);const l=new se({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(s,r,a)}`,values:[u]});return(await Tt.any(l)).map((t=>t.rev))}static async getUnspentTbcOutputs(t,e,s){const r=new se({name:`Output.getUnspentTbcOutputs.${Math.random()}`,text:`SELECT "rev", "address", "satoshis", "asm", "timestamp"\n FROM "Output" WHERE "isTbcOutput" = true AND NOT EXISTS\n (SELECT 1 FROM "Input" ip WHERE "ip"."outputSpent" = "Output"."rev") ${this.sqlSuffix(t,e,s)}`});return(await Tt.any(r)).map((t=>t.rev))}static async query(t){const{publicKey:e,limit:s,offset:r,ids:a,mod:n,order:o}=t;const i=parseInt(V||"",10);if(s&&parseInt(s||"",10)>i||a&&a.length>i)throw new Error(`Can't fetch more than ${V} revs.`);if(o&&"ASC"!==o&&"DESC"!==o)throw new Error("Invalid order. Should be ASC or DESC.");return a?(a.map(ee),this.getLatestRevs(a)):n&&!e?this.getUnspentRevsByMod(n,s,r,o):!n&&e?this.getUnspentRevsByPublicKey(e,s,r,o):n&&e?this.getUnspentRevsByModAndPublicKey(n,e,s,r,o):this.getUnspentTbcOutputs(s,r,o)}static async selectNext(t){const e=new se({name:`Output.selectNext.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "previous" = $1',values:[t]});return Tt.any(e)}static async selectPrev(t){const e=new se({name:`Output.selectPrev.${Math.random()}`,text:'SELECT "previous" FROM "Output" WHERE "rev" = $1',values:[t]});return Tt.any(e)}}class ae{static async select(t){return re.select(t)}static async insert(t){return re.insert(t)}static async eraseBlockHash(t){return re.eraseBlockHash(t)}static async listSentOutputs(t){return re.listSentOutputs(t)}static async listReceivedOutputs(t){return re.listReceivedOutputs(t)}static async listTxs(t){return re.listTxs(t)}static async getLatestRev(t){return re.getLatestRev(t)}static async getLatestRevs(t){return re.getLatestRevs(t)}static async getIdByRev(t){return re.getIdByRev(t)}static async query(t){return re.query(t)}static async selectNext(t){return re.selectNext(t)}static async selectPrev(t){return re.selectPrev(t)}}const ne=function(t=B,e=L){switch(t){case"BTC":switch(e){case"mainnet":return c.bitcoin;case"testnet":return c.testnet;case"regtest":return c.regtest;default:throw new Error(`Invalid network ${e}`)}case"LTC":switch(e){case"mainnet":return c.litecoin;case"testnet":return c.litecointestnet;case"regtest":return c.litecoinregtest;default:throw new Error(`Invalid network ${e}`)}case"PEPE":switch(e){case"mainnet":return c.pepecoin;case"testnet":return c.pepecointestnet;case"regtest":return c.pepecoinregtest;default:throw new Error(`Invalid network ${e}`)}case"DOGE":switch(e){case"mainnet":return c.dogecoin;case"testnet":return c.dogecointestnet;case"regtest":return c.dogecoinregtest;default:throw new Error(`Invalid network ${e}`)}default:throw new Error(`Invalid chain ${t}`)}}(B,L);class oe{static insert(t){return ae.insert(t)}static getOutputs=(t,e=null)=>t.flatMap((({outs:t,txId:s,zip:r,onChainMetaData:a})=>{const{exp:n="",mod:o=""}=a;return t.map((({script:t,value:a},c)=>{const u=cp.fromOutputScript(t,ne))),satoshis:Math.round(a),asm:l.toASM(t),isTbcOutput:u,mod:u?o:"",previous:u?r[c][0]:null,hash:u?i.sha256(Buffer.from(n)).toString("hex"):null,blockHash:e}}))}));static select=async t=>ae.select(t);static eraseBlockHash=async t=>{await ae.eraseBlockHash(t)};static listSentOutputs=async t=>ae.listSentOutputs(t);static listReceivedOutputs=async t=>ae.listReceivedOutputs(t);static listTxs=async t=>ae.listTxs(t);static getLatestRev=async t=>ae.getLatestRev(t);static getLatestRevs=async t=>ae.getLatestRevs(t);static getIdByRev=async t=>ae.getIdByRev(t);static query=async t=>ae.query(t)}const ie=t=>new Promise((e=>{setTimeout(e,t)}));const ce=O(o);const ue=c.regtest;const{PreparedStatement:le}=v;class pe{static async select(t){const e=new le({name:`Input.select.${Math.random()}`,text:'SELECT "outputSpent", "spendingInput", "blockHash" FROM "Input" WHERE "outputSpent" = $1',values:[t]});return Tt.any(e)}static async insert(t){await Promise.all(Wt(t,3333).map((t=>{const e=t.flatMap((({outputSpent:t,spendingInput:e,blockHash:s})=>[t,e,s]));return Tt.none(new le({name:`Input.insert.${Math.random()}`,text:`INSERT INTO "Input"("outputSpent", "spendingInput", "blockHash") VALUES ${Gt(e.length)} \n ON CONFLICT ("spendingInput") \n DO UPDATE SET "blockHash" = COALESCE("Input"."blockHash", EXCLUDED."blockHash")`,values:e}))})))}static async eraseBlockHash(t){await Promise.all(Wt(t,1e4).map((t=>{const e=t.join("','");return Tt.none(new le({name:`Input.eraseBlockHash.${Math.random()}`,text:`UPDATE "Input" SET "blockHash" = NULL WHERE "blockHash" IN ('${e}')`}))})))}static async count(t){const e=t.map((t=>t.outputSpent));const s=new le({name:`Input.belong.${Math.random()}`,text:'SELECT count(*) FROM "Input" WHERE "outputSpent" LIKE ANY ($1)',values:[[e]]});const r=await Tt.oneOrNone(s);return parseInt(r?.count,10)||0}}class de{static async select(t){return pe.select(t)}static async insert(t){return pe.insert(t)}static async eraseBlockHash(t){return pe.eraseBlockHash(t)}}class me{static insert=async t=>{await de.insert(t)};static getInputs=(t,e=null)=>t.flatMap((({ins:t,txId:e})=>t.map(((t,s)=>({input:t,index:s,txId:e}))))).filter((({input:t})=>!$.isCoinbaseHash(t.hash))).map((({input:t,index:s,txId:r})=>{return{outputSpent:`${a=t.hash,u.reverseBuffer(Buffer.from(a)).toString("hex")}:${t.index}`,spendingInput:`${r}:${s}`,blockHash:e};var a}));static select=async t=>de.select(t);static eraseBlockHash=async t=>{await de.eraseBlockHash(t)}}class he{static rawTxSubscriber=async t=>{const e=t.toString("hex");if(yt.info(`ZMQ message { hex:${e} }`),"08"!==e.slice(10,12))try{const t=S.txFromHex({hex:e});await oe.insert(oe.getOutputs([t])),await me.insert(me.getInputs([t]))}catch(t){yt.error(`[zmq] Error parsing transaction ${e}\n${t.stack}`)}};static sub=async t=>{try{yt.info(`Bitcoin Computer Node ${vt} is starting on ${L} ${B}.`),await qt.createWallet(),"regtest"!==L&&await qt.checkBlockchainProgress(.7),await qt.walletSetup(),yt.info(`Bitcoin Computer Node ${vt} is ready. ZQM activation height: ${X}`),await qt.checkBlockchainProgress(.9);for await(const[,e]of t)await this.rawTxSubscriber(e)}catch(t){yt.error(`ZMQ subscription failed with error '${t.message}'`)}}}const{PreparedStatement:ge}=v;class ye{static async select(t){const e=new ge({name:`User.select.${Math.random()}`,text:'SELECT "publicKey", "clientTimestamp" FROM "User" WHERE "publicKey" = $1',values:[t]});const s=await Tt.oneOrNone(e);return s?{publicKey:s.publicKey,clientTimestamp:parseInt(s.clientTimestamp,10)||0}:null}static async insert({publicKey:t,clientTimestamp:e}){const s=new ge({name:`User.insert.${Math.random()}`,text:'INSERT INTO "User"("publicKey", "clientTimestamp") VALUES ($1, $2)',values:[t,e]});await Tt.none(s)}static async update({publicKey:t,clientTimestamp:e}){const s=new ge({name:`User.update.${Math.random()}`,text:'UPDATE "User" SET "clientTimestamp"=$1 WHERE "publicKey"=$2',values:[e,t]});await Tt.none(s)}}class we{static async select(t){return ye.select(t)}static async insert(t){return ye.insert(t)}static async update(t){return ye.update(t)}}const{ec:ve}=b;const Ee=new ve("secp256k1");const fe=s();const Te=new class{configFile;loaded=!1;load=()=>{try{const t="dev"===Z?"bcn.test.config.json":"bcn.config.json";const e=M(C(import.meta.url));this.configFile=T.readFileSync(N.join(e,"..","..",t)),this.loaded=!0}catch(t){if(t.message.includes("ENOENT: no such file or directory"))return void(this.loaded=!0);throw yt.error(`Access-list failed with error '${t.message}'`),t}};middleware=({url:t},e,s)=>{if(void 0!==e.locals.authToken)if(this.loaded||(yt.warn("Access-list failed with error 'AccessList not loaded.'. Loading now."),this.load()),void 0!==this.configFile)try{const{blacklist:t,whitelist:r}=JSON.parse(this.configFile.toString());if(t&&r)return void e.status(403).json({error:"Cannot enforce blacklist and whitelist at the same time."});const{publicKey:a}=e.locals.authToken;if(r&&!r.includes(a)||t&&t.includes(a))return void e.status(403).json({error:`Public key ${a} is not allowed.`});s()}catch(s){yt.error(`Authorization failed at ${t} with error: '${s.message}'`),e.status(403).json({error:s.message})}else s();else s()}};let Oe;h(o);try{Oe=r.createServer(fe)}catch(t){throw yt.error(`Starting server failed with error '${t.message}'`),t}if(yt.info(`Server listening on port ${A}`),fe.use(e()),"true"===W){const t=n({windowMs:parseInt(G,10),max:parseInt(Y,10),standardHeaders:"true"===K,legacyHeaders:"true"===q});fe.use(t)}fe.use(t.json({limit:"100mb"})),fe.use(t.urlencoded({limit:"100mb",extended:!0})),fe.get("/",((t,e)=>e.status(200).send(`\n

Bitcoin Computer Node

\n Status: Healthy
\n Version: ${vt}
\n Chain: ${B}
\n Network: ${L}\n `))),Te.loaded&&(fe.use((async(t,e,s)=>{try{const r=t.get("Authentication");if(!r){const{method:s,url:r}=t;const a=`Auth failed with error 'no Authentication key provided' ${s} ${t.get("Host")} ${r}`;return yt.error(a),void e.status(401).json({error:a})}const a=(t=>{const e=t.split(" ");if(2!==e.length||"Bearer"!==e[0])throw new Error("Authentication header is invalid.");const s=Buffer.from(e[1],"base64").toString().split(":");if(3!==s.length)throw new Error;return{signature:s[0],publicKey:s[1],timestamp:parseInt(s[2],10)}})(r);const{signature:n,publicKey:o,timestamp:i}=a;if(Date.now()-i>18e4)return void e.status(401).json({error:"Signature is too old."});const c=x.sha256().update(z+i).digest("hex");if(!Ee.keyFromPublic(o,"hex").verify(c,n)){const t="The origin and public key pair doesn't match the signature.";return void e.status(401).json({error:t})}const u=await we.select(o);if(u){if(u.clientTimestamp>=i)return void e.status(401).json({error:"Please use a fresh authentication token."});await we.update({publicKey:o,clientTimestamp:i})}else await we.insert({publicKey:o,clientTimestamp:i});e.locals.authToken=a,s()}catch(t){yt.error(`Auth failed with error '${t.message}'`),e.status(401).json({error:t.message})}})),fe.use(Te.middleware));const Se=(()=>{const t=s.Router();return t.get("/wallet/:address/utxos",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await Nt.selectByAddress(e))}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/wallet/:address/sent-outputs",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await oe.listSentOutputs(e))}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/wallet/:address/received-outputs",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await oe.listReceivedOutputs(e))}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/wallet/:address/list-txs",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await oe.listTxs(e))}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/non-standard-utxos",(async(t,e)=>{try{const s=new URLSearchParams(t.url.split("?")[1]);const r={mod:s.get("mod"),publicKey:s.get("publicKey"),limit:s.get("limit"),order:s.get("order"),offset:s.get("offset"),ids:JSON.parse(s.get("ids"))};const a=await oe.query(r);e.status(200).json(a)}catch(s){yt.error(`GET ${t.url} failed with error '${s.messages}'`),e.status(500).json({error:s.message})}})),t.get("/address/:address/balance",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await Nt.getBalance(e))}catch(t){yt.error(`GET ${e} failed with error '${t.message||t}'`),s.status(500).json({error:t.message})}})),t.post("/tx/bulk",(async({body:{txIds:t},url:e},s)=>{try{if(void 0===t||0===t.length)return void s.status(400).json({error:"Missing input txIds."});const e=await Jt.getRaw(t);e?s.status(200).json(e):s.status(404).json({error:"Not found"})}catch(t){yt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/tx/post",(async({body:{hex:t},url:e},s)=>{try{if(!t)return void s.status(400).json({error:"Missing input hex."});const e=await Jt.sendRaw(t);e?s.status(200).json(e):s.status(404).json({error:"Error Occured"})}catch(r){yt.error(`POST ${e} failed with error '${r.message}\ntxHex: ${t}`),s.status(500).json({error:r.message})}})),t.get("/mine",(async({query:{count:t},url:e},s)=>{try{const{result:e}=await Zt.getnewaddress();if("string"!=typeof t)throw new Error("Please provide appropriate count");return await Zt.generatetoaddress(parseInt(t,10)||1,e),s.status(200).json({success:!0})}catch(t){return yt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/:id/height",(async({params:{id:t},url:e},s)=>{try{let e=t;if("best"===t){const{result:t}=await Zt.getbestblockhash();e=t}const{result:r}=await Zt.getblockheader(e,!0);return s.status(200).json({height:r.height})}catch(t){return yt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/faucet",(async({body:{address:t,value:e},url:s},r)=>{try{const s=parseInt(e,10)/1e8;const{result:a}=await Zt.sendtoaddress(t,s);await Zt.generateToAddress(1,"mvFeNF9DAR7WMuCpBPbKuTtheihLyxzj8i");const{result:n}=await Zt.getrawtransaction(a,1);const o=n.vout.findIndex((t=>1e8*t.value===parseInt(e,10)));return r.status(200).json({txId:a,vout:o,height:-1,satoshis:e})}catch(t){return yt.error(`POST ${s} failed with error '${t.message}'`),r.status(500).json({error:t.message})}})),t.post("/faucetScript",(async({body:{script:t,value:e},url:s},r)=>{try{const s=ce.makeRandom({network:ue});const a=d.p2pkh({pubkey:s.publicKey,network:ue});const{address:n}=a;const o=(await Zt.sendtoaddress(n,2*parseInt(e,10)/1e8,"","")).result;let i;let c=10;for(;!i;)if(i=(await Nt.selectByAddress(n)).filter((t=>t.txId===o))[0],!i){if(c-=1,c<=0)throw new Error("No outputs");await ie(10)}const u=(await Zt.getrawtransaction(i.txId,1)).result;const p=new m({network:ue});p.addInput({hash:i.txId,index:i.vout,nonWitnessUtxo:Buffer.from(u.hex,"hex")}),p.addOutput({script:Buffer.from(t,"hex"),value:parseInt(e,10)}),p.signInput(0,s),p.finalizeAllInputs();const h=p.extractTransaction(!0);let g;for(await Zt.sendrawtransaction(h.toHex()),c=5;!g;){const e=l.toASM(Buffer.from(t,"hex"));if(g=(await Nt.selectByScriptASM(e)).filter((t=>t.txId===h.getId()))[0],!g){if(c-=1,c<=0)throw new Error("No outputs");await ie(10)}}return r.status(200).json({txId:h.getId(),vout:g.vout,height:-1,satoshis:g.satoshis})}catch(t){return yt.error(`POST ${s} failed with error '${t.message}'`),r.status(500).json({error:t.message})}})),t.get("/tx/:txId/json",(async({params:{txId:t},url:e},s)=>{try{if(!t)return void s.status(400).json({error:"Missing input txId."});const e=await Jt.getRawJSON(t);e?s.status(200).json(e):s.status(404).json({error:"Not found"})}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/revs",(async({body:{ids:t},url:e},s)=>{try{if(void 0===t||0===t.length)return void s.status(400).json({error:"Missing input object ids."});const e=await oe.getLatestRevs(t);s.status(200).json(e)}catch(t){yt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/revToId",(async({body:{rev:t},url:e},s)=>{try{if(!te(t))return void s.status(400).json({error:"Invalid rev id"});const e=await oe.getIdByRev(t);e&&s.status(200).json(e),s.status(404).json()}catch(t){yt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/rpc",(async({body:t,url:e},s)=>{try{if(!t||!t.method)throw new Error("Please provide appropriate RPC method name");if(!new RegExp(H).test(t.method))throw new Error("Method is not allowed");const e=function(t,e){if(void 0===Xt[t]||null===Xt[t])throw new Error("This RPC method does not exist, or not supported");const s=e.trim().split(" ");const r=Xt[t].trim().split(" ");if(0===e.trim().length&&0!==Xt[t].trim().length)throw new Error(`Too few params provided. Expected ${r.length} Provided 0`);if(0!==e.trim().length&&0===Xt[t].trim().length)throw new Error(`Too many params provided. Expected 0 Provided ${s.length}`);if(s.lengthr.length)throw new Error(`Too many params provided. Expected ${r.length} Provided ${s.length}`);return 0===e.length?[]:s.map(((t,e)=>Qt[r[e]](t)))}(t.method,t.params);const r=e.length?await Zt[t.method](...e):await Zt[t.method]();s.status(200).json({result:r})}catch(t){yt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/next/:rev",(async({params:{rev:t},url:e},s)=>{try{if(!t)return void s.status(400).json({error:"Missing rev."});const e=await ae.selectNext(t);e?s.status(200).json({rev:e&&e.length?e[0].rev:void 0}):s.status(404).json({error:"Not found"})}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/prev/:rev",(async({params:{rev:t},url:e},s)=>{try{if(!t)return void s.status(400).json({error:"Missing rev."});const e=await ae.selectPrev(t);e?s.status(200).json({rev:e&&e.length?e[0].previous:void 0}):s.status(404).json({error:"Not found"})}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/non-standard-utxo",(async(t,e)=>{e.status(500).json({error:"Please upgrade to @bitcoin-computer/lib to the latest version."})})),t})();fe.use(`/v1/${B}/${L}`,Se),fe.use("/v1/store",It),Oe.listen(A,(()=>{yt.info(`\nStarted Bitcoin Computer Node Version ${vt}\nPORT ${A} \n`)})).on("error",(t=>{yt.error(t.message),process.exit(1)}));const $e=new a.Subscriber;$e.connect(P),$e.subscribe("rawtx"),yt.info(`ZMQ Subscriber connected to ${P}`),(async()=>{await(async()=>{await f((()=>Tt.connect()),{startingDelay:500})})(),await he.sub($e)})(); +import t from"body-parser";import e from"cors";import s from"express";import r from"http";import*as a from"zeromq";import n from"express-rate-limit";import*as o from"@bitcoin-computer/secp256k1";import{crypto as i,networks as c,bufferUtils as u,script as l,address as p,payments as d,Psbt as m,initEccLib as h}from"@bitcoin-computer/nakamotojs";import g from"dotenv";import y from"winston";import w from"winston-daily-rotate-file";import v from"pg-promise";import f from"pg-monitor";import{backOff as E}from"exponential-backoff";import T from"fs";import{ECPairFactory as O}from"ecpair";import{Computer as $,Transaction as S}from"@bitcoin-computer/lib";import I from"bitcoind-rpc";import R from"util";import b from"elliptic";import x from"hash.js";import N,{dirname as M}from"path";import{fileURLToPath as C}from"url";g.config();const B=process.env.BCN_CHAIN;const L=process.env.BCN_NETWORK;const{BCN_PORT:A}=process.env;const{BCN_ZMQ_URL:P}=process.env;const{BCN_ALLOWED_RPC_METHODS:H}=process.env;const{BCN_LOG_LEVEL:k}=process.env;const{BCN_LOG_MAX_FILES:j}=process.env;const{BCN_LOG_MAX_SIZE:_}=process.env;const{BCN_LOG_ZIP:U}=process.env;const{BCN_SHOW_DB_LOGS:F}=process.env;const{BCN_RATE_LIMIT_ENABLED:D}=process.env;const{BCN_RATE_LIMIT_WINDOW:W}=process.env;const{BCN_RATE_LIMIT_MAX:G}=process.env;const{BCN_RATE_LIMIT_STANDARD_HEADERS:K}=process.env;const{BCN_RATE_LIMIT_LEGACY_HEADERS:Y}=process.env;process.env,process.env,process.env;const{BCN_OFFCHAIN_PROTOCOL:q}=process.env;const V=process.env.BCN_QUERY_LIMIT||"1000";const J=process.env.BCN_URL||`http://127.0.0.1:${A}`;const z=process.env.BCN_ENV||"dev";const Z=process.env.BCN_ZMQ_ACTIVATION_HEIGHT||"1";const{BITCOIN_RPC_USER:X}=process.env;const{BITCOIN_RPC_PASSWORD:Q}=process.env;const{BITCOIN_RPC_HOST:tt}=process.env;const{BITCOIN_RPC_PORT:et}=process.env;const{BITCOIN_RPC_PROTOCOL:st}=process.env;const{BITCOIN_DEFAULT_WALLET:rt}=process.env;const{POSTGRES_USER:at}=process.env;const{POSTGRES_PASSWORD:nt}=process.env;const{POSTGRES_DB:ot}=process.env;const{POSTGRES_HOST:it}=process.env;const{POSTGRES_PORT:ct}=process.env;const{POSTGRES_MAX_CONNECTIONS:ut}=process.env;const{POSTGRES_IDLE_TIMEOUT_MILLIS:lt}=process.env;y.addColors({error:"red",warn:"yellow",info:"green",http:"magenta",debug:"blue"});const pt=y.format.combine(y.format.colorize(),y.format.timestamp({format:"YYYY-MM-DD HH:mm:ss:ms"}),y.format.json(),y.format.printf((t=>`${t.timestamp} [${t.level.slice(5).slice(0,-5)}] ${t.message}`)));const dt=y.format.combine(y.format.colorize(),y.format.timestamp({format:"MM-DD-YYYY HH:mm:ss"}),y.format.printf((t=>`${t.timestamp} ${t.level} ${t.message}`)));const mt={zippedArchive:"true"===U,maxSize:_,maxFiles:j,dirname:"logs"};const ht=[];const gt={0:"error",1:"warn",2:"info",3:"http",4:"debug"}[k];"dev"===z&&ht.push(new y.transports.Console({format:dt,level:gt})),ht.push(new w({filename:"logs/application-%DATE%.log",datePattern:"YYYY-MM-DD",level:gt,...mt})),ht.push(new w({filename:"logs/error-%DATE%.log",datePattern:"YYYY-MM-DD",level:"error",...mt}));const yt=y.createLogger({levels:{error:0,warn:1,info:2,http:3,debug:4},format:pt,transports:ht,exceptionHandlers:[new y.transports.File({filename:"logs/exceptions.log"})],rejectionHandlers:[new y.transports.File({filename:"logs/rejections.log"})]});g.config();const{version:wt}=JSON.parse(T.readFileSync("package.json","utf8"));const vt=wt||process.env.BCN_SERVER_VERSION;const ft=parseInt(process.env.MWEB_HEIGHT||"",10)||432;const Et={error:(t,e)=>{if(e.cn){const{host:s,port:r,database:a,user:n,password:o}=e.cn;yt.debug(`Waiting for db to start { message:${t.message} host:${s}, port:${r}, database:${a}, user:${n}, password: ${o}`)}},noWarnings:!0};"true"===F&&(f.isAttached()?f.detach():(f.attach(Et),f.setTheme("matrix")));const Tt=v(Et)({host:it,port:parseInt(ct,10),database:ot,user:at,password:nt,max:parseInt(ut,10),allowExitOnIdle:!0,idleTimeoutMillis:parseInt(lt,10)});const{PreparedStatement:Ot}=v;class $t{static async select(t){const e=new Ot({name:`OffChain.select.${Math.random()}`,text:'SELECT "data" FROM "OffChain" WHERE "id" = $1',values:[t]});return Tt.oneOrNone(e)}static async insert({id:t,data:e}){const s=new Ot({name:`OffChain.insert.${Math.random()}`,text:'INSERT INTO "OffChain" ("id", "data") VALUES ($1, $2) ON CONFLICT DO NOTHING',values:[t,e]});return Tt.none(s)}static async delete(t){const e=new Ot({name:`OffChain.delete.${Math.random()}`,text:'WITH deleted AS (DELETE FROM "OffChain" WHERE "id" = $1 RETURNING *) SELECT count(*) FROM deleted;',values:[t]});return(await Tt.any(e))[0].count>0}}class St{static async select(t){const e=await $t.select(t);return e?.data||null}static async insert(t){return $t.insert(t)}static async delete(t){return $t.delete(t)}}const It=s.Router();It.get("/:id",(async({params:{id:t},url:e},s)=>{try{const e=await St.select(t);e?s.status(200).json(e):s.status(403).json({error:"No entry found."})}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),It.post("/",(async(t,e)=>{const{body:{data:s},url:r}=t;try{const r=i.sha256(Buffer.from(s)).toString("hex");await St.insert({id:r,data:s});const a=`${q||t.protocol}://${t.get("host")}/store/${r}`;e.status(201).json({_url:a})}catch(t){yt.error(`POST ${r} failed with error '${t.message}'`),e.status(500).json({error:t.message})}})),It.delete("/:id",(async(t,e)=>{e.status(500).json({error:"Deletions are not supported yet."})}));const{PreparedStatement:Rt}=v;class bt{static async getBalance(t){const e=new Rt({name:`Utxos.getBalance.${Math.random()}`,text:'SELECT sum("satoshis") as "satoshis" FROM "Utxos" WHERE "address" = $1 and "blockHash" is not null',values:[t]});const s=await Tt.oneOrNone(e);const r=new Rt({name:`Utxos.getBalance.${Math.random()}`,text:'SELECT sum("satoshis") as "satoshis" FROM "Utxos" WHERE "address" = $1 and "blockHash" is null',values:[t]});const a=await Tt.oneOrNone(r);return{confirmed:parseInt(s.satoshis,10)||0,unconfirmed:parseInt(a.satoshis,10)||0,balance:(parseInt(s.satoshis,10)||0)+(parseInt(a.satoshis,10)||0)}}static async select(t){const e=new Rt({name:`Utxos.select.${Math.random()}`,text:'SELECT "address", "satoshis", "asm", "rev", split_part(rev, \':\', 1) AS "txId", cast(split_part(rev, \':\', 2) as INTEGER) AS "vout" FROM "Utxos" WHERE "address" = $1',values:[t]});return(await Tt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)||0})))}static async selectByScriptASM(t){const e=new Rt({name:`Utxos.select.${Math.random()}`,text:'SELECT "address", "satoshis", "asm", "rev", split_part(rev, \':\', 1) AS "txId", cast(split_part(rev, \':\', 2) as INTEGER) AS "vout" FROM "Utxos" WHERE "asm" = $1',values:[t]});return(await Tt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)||0})))}static async selectByPk(t){const e=new Rt({name:`Utxos.selectByPk.${Math.random()}`,text:`SELECT "address", "satoshis", "asm", "rev", split_part(rev, ':', 1) AS "txId", cast(split_part(rev, ':', 2) as INTEGER) AS "vout" FROM "Utxos" WHERE "asm" LIKE '%${t}%'`});return(await Tt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)})))}}class xt{static async getBalance(t){return bt.getBalance(t)}static async select(t){return bt.select(t)}static async selectByPk(t){return bt.selectByPk(t)}static async selectByScriptASM(t){return bt.selectByScriptASM(t)}}class Nt{static getBalance=async t=>xt.getBalance(t);static selectByAddress=async t=>xt.select(t);static selectByPk=async t=>xt.selectByPk(t);static selectByScriptASM=async t=>xt.selectByScriptASM(t)}const Mt={protocol:st,user:X,pass:Q,host:tt,port:parseInt(et,10)};const Ct=new I(Mt);const Bt=R.promisify(I.prototype.createwallet.bind(Ct));const Lt=R.promisify(I.prototype.generateToAddress.bind(Ct));const At=R.promisify(I.prototype.getaddressinfo.bind(Ct));const Pt=R.promisify(I.prototype.getBlock.bind(Ct));const Ht=R.promisify(I.prototype.getBlockchainInfo.bind(Ct));const kt=R.promisify(I.prototype.getBlockHash.bind(Ct));const jt=R.promisify(I.prototype.getRawTransaction.bind(Ct));const _t=R.promisify(I.prototype.getRawTransaction.bind(Ct));const Ut=R.promisify(I.prototype.getTransaction.bind(Ct));const Ft=R.promisify(I.prototype.getNewAddress.bind(Ct));const Dt={createwallet:Bt,generateToAddress:Lt,getaddressinfo:At,getBlock:Pt,getBlockchainInfo:Ht,getBlockHash:kt,getRawTransaction:jt,getTransaction:Ut,importaddress:R.promisify(I.prototype.importaddress.bind(Ct)),invalidateBlock:R.promisify(I.prototype.invalidateBlock.bind(Ct)),listunspent:R.promisify(I.prototype.listunspent.bind(Ct)),sendRawTransaction:R.promisify(I.prototype.sendRawTransaction.bind(Ct)),getNewAddress:Ft,sendToAddress:R.promisify(I.prototype.sendToAddress.bind(Ct)),getRawTransactionJSON:_t};const Wt=(t,e)=>{const s=[];for(let r=0;r{const e=[];for(let s=1;s<=t;s+=3){const t=`($${s},$${s+1},$${s+2})`;e.push(t)}return e.join(",")};const Kt=t=>{const e=[];for(let s=1;s<=t;s+=9){const t=`($${s},$${s+1},$${s+2},$${s+3},$${s+4},$${s+5},$${s+6},$${s+7},$${s+8})`;e.push(t)}return e.join(",")};const Yt=t=>{try{return t()}catch{return null}};class qt{static async getTransaction(t){const{result:e}=await Dt.getTransaction(t);return e}static async getBulkTransactions(t){return(await Promise.all(t.map((t=>Dt.getRawTransaction(t,0))))).map((t=>t.result))}static async getRawTransaction(t,e){const{result:s}=await Dt.getRawTransaction(t,e);return s}static async getRawTransactionsJSON(t){return{txId:(e=(await Dt.getRawTransactionJSON(t,1)).result).txid,txHex:e.hex,vsize:e.vsize,version:e.version,locktime:e.locktime,ins:e.vin.map((t=>t.coinbase?{coinbase:t.coinbase,sequence:t.sequence}:{txId:t.txid,vout:t.vout,script:t.scriptSig.hex,sequence:t.sequence})),outs:e.vout.map((t=>{let e;return t.scriptPubKey.addresses?[e]=t.scriptPubKey.addresses:e=t.scriptPubKey.address?t.scriptPubKey.address:void 0,{address:e,script:t.scriptPubKey.hex,value:Math.round(1e8*t.value)}}))};var e}static async sendRawTransaction(t){const{result:e,error:s}=await Dt.sendRawTransaction(t);if(s)throw yt.error(s),new Error("Error sending transaction");return e}static getUtxos=async t=>(void 0===(await Dt.getaddressinfo(t)).result.timestamp&&(yt.info(`Importing address: ${t}`),await Dt.importaddress(t,!1)),(await Dt.listunspent(0,999999,[t])).result);static walletSetup=async()=>{if("regtest"===L){if(yt.info(`Node is starting for chain ${B} and network ${L}, \n\n. Starting Wallet setup.`),"LTC"===B){const{result:t}=await Dt.getBlockchainInfo();const e=t.blocks;if(e{try{await Dt.createwallet(rt,!1,!1,"",!1,!1)}catch(t){if(t.message.includes("already exists"))return void yt.info(`Wallet ${rt} already exists`);yt.warn(`Wallet creation failed with error '${t.message}'`)}};static checkBlockchainProgress=async t=>{const e=await E((async()=>{const e=await Dt.getBlockchainInfo();const s=(100*parseFloat(e.result.verificationprogress)).toFixed(4);const{blocks:r}=e.result;if(yt.info(`Zmq. Bitcoind { percentage:${s}%, blocks:${r} }`),parseFloat(e.result.verificationprogress)<=t)throw new Error("Node not ready yet");return e}),{startingDelay:6e4,timeMultiple:1,numOfAttempts:8760});const s=(100*parseFloat(e.result.verificationprogress)).toFixed(4);const r=e.result.blocks;yt.info(`BCN reaches sync end...at { bitcoind.progress:${s}%, bitcoindSyncedHeight:${r} }`)}}class Vt{static get=async t=>qt.getTransaction(t);static getRaw=async t=>qt.getBulkTransactions(t);static getRawJSON=async t=>qt.getRawTransactionsJSON(t);static sendRaw=async t=>qt.sendRawTransaction(t);static getUtxos=async t=>qt.getUtxos(t);static parseTransactions=async(t,e,s,r)=>{let a=t;"LTC"===e&&(a=t.filter((t=>"08"!==t.hex.slice(10,12))));const n=[];for(const t of a)try{let{hex:e}=t;e||(e=(await qt.getRawTransaction(t.txid,1)).hex);const s=$.txFromHex({hex:e});s&&n.push(s)}catch(e){yt.error(`[wid ${s} pid: ${process.pid}: failed to parse transaction in block ${r}\n error message: ${e.message}\n transaction: ${JSON.stringify(t)}`)}return n};static walletSetup=async()=>qt.walletSetup()}const Jt={protocol:st,user:X,pass:Q,host:tt,port:parseInt(et,10)};const zt=new I(Jt);const Zt={};const Xt=JSON.parse(JSON.stringify(I.callspec));Object.keys(Xt).forEach((t=>{Xt[t.toLowerCase()]=Xt[t]}));const Qt={str:t=>t.toString(),string:t=>t.toString(),int:t=>parseFloat(t),float:t=>parseFloat(t),bool:t=>!0===t||"1"===t||1===t||"true"===t||"true"===t.toString().toLowerCase(),obj:t=>"string"==typeof t?JSON.parse(t):t};try{Object.keys(I.prototype).forEach((t=>{if(t&&"function"==typeof I.prototype[t]){const e=t.toLowerCase();Zt[t]=R.promisify(I.prototype[t].bind(zt)),Zt[e]=R.promisify(I.prototype[e].bind(zt))}}))}catch(t){yt.error(`Error occurred while binding RPC methods: ${t.message}`)}function te(t){return/^[0-9A-Fa-f]{64}:\d+$/.test(t)}function ee(t){if(!te(t))throw new Error("Invalid rev")}const{PreparedStatement:se}=v;class re{static async listSentOutputs(t){const e=new se({name:`Output.listSentTxs.${Math.random()}`,text:'SELECT "Input"."spendingInput" AS "output", "Output"."satoshis" AS "amount"\n FROM "Output" INNER JOIN "Input" ON "Output".rev = "Input"."outputSpent" \n WHERE "Output"."address" = $1',values:[t]});return(await Tt.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listReceivedOutputs(t){const e=new se({name:`Output.listReceivedTxs.${Math.random()}`,text:'SELECT "Output"."rev" as "output", "Output"."satoshis" as "amount" FROM "Output" WHERE "address" = $1',values:[t]});return(await Tt.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listTxs(t){const e=new se({name:`Output.listTxs.${Math.random()}`,text:'WITH\n -- List all txs sent from a given address\n SENT AS (\n SELECT split_part("Input"."spendingInput",\':\',1) as "txId", SUM("Output".satoshis) as "satoshis"\n FROM "Output" INNER JOIN "Input" ON "Output".rev = "Input"."outputSpent"\n WHERE "Output".address = $1\n GROUP BY split_part("Input"."spendingInput",\':\',1)\n ),\n -- List all tx received from a given address\n RECEIVED AS (\n SELECT SPLIT_PART("Output"."rev",\':\',1) as "txId", SUM("Output"."satoshis") as "satoshis" \n FROM "Output" \n WHERE "address" = $1\n GROUP BY "txId"\n )\n\n SELECT\n RECEIVED."txId", \n coalesce(SENT."satoshis", 0) as "inputsSatoshis", \n coalesce(RECEIVED."satoshis", 0) as "outputsSatoshis", \n coalesce(RECEIVED."satoshis",0) - coalesce(SENT."satoshis",0) as "satoshis"\n FROM\n SENT RIGHT JOIN RECEIVED ON SENT."txId" = RECEIVED."txId";',values:[t]});const s=(await Tt.any(e)).map((t=>({...t,inputsSatoshis:parseInt(t.inputsSatoshis,10)||0,outputsSatoshis:parseInt(t.outputsSatoshis,10)||0,satoshis:parseInt(t.satoshis,10)||0})));return{sentTxs:s.filter((t=>t.satoshis<0)).map((t=>({...t,satoshis:Math.abs(t.satoshis)}))),receivedTxs:s.filter((t=>t.satoshis>=0))}}static async select(t){const e=new se({name:`Output.select.${Math.random()}`,text:'SELECT "address", "satoshis", "asm", "rev", "hash", "mod", "isTbcOutput", "previous", "blockHash" FROM "Output" WHERE "address" = $1',values:[t]});return Tt.any(e)}static async insert(t){await Promise.all(Wt(t,1111).map((t=>{const e=t.flatMap((({rev:t,address:e,satoshis:s,asm:r,isTbcOutput:a,mod:n,previous:o,hash:i,blockHash:c})=>[t,e,s,r,a,n,o,i,c]));return Tt.none(new se({name:`Output.insert.${Math.random()}`,text:`INSERT INTO "Output"("rev", "address", "satoshis", "asm", "isTbcOutput",\n "mod", "previous", "hash", "blockHash") VALUES ${Kt(e.length)} ON CONFLICT ("rev") \n DO UPDATE SET "blockHash" = COALESCE("Output"."blockHash", EXCLUDED."blockHash")`,values:e}))})))}static async eraseBlockHash(t){await Promise.all(Wt(t,1e4).map((t=>{const e=t.join("','");return Tt.none(new se({name:`Output.eraseBlockHash.${Math.random()}`,text:`UPDATE "Output" SET "blockHash" = NULL WHERE "blockHash" IN ('${e}')`}))})))}static async getIdByRev(t){const e=new se({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON r."previous" = o."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=(await Tt.any(e)).filter((t=>null===t.previous));return s[0]?.rev}static async getIdsByRevs(t){return Promise.all(t.map((t=>this.getIdByRev(t))))}static async getLatestRev(t){const e=new se({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON o."previous" = r."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=await Tt.any(e);const r=Object.fromEntries(s.map((t=>[t.previous,t.rev])));let a=t;for(;r[a];)a=r[a];return a}static async getLatestRevs(t){return Promise.all(t.map(this.getLatestRev))}static async getIdsByMod(t){const e=new se({name:`Output.getIdsByMod.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1',values:[t]});return(await Tt.any(e)).map((t=>t.rev))}static sqlSuffix(t,e,s){let r="";return s&&(r+=` order by "timestamp" ${s}`),r+=` limit ${t||V}`,e&&(r+=` offset ${e}`),r}static async getUnspentRevsByMod(t,e,s,r){const a=await this.getIdsByMod(t);const n=await this.getLatestRevs(a);const o=new se({name:`Output.getUnspentRevsByMod.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(e,s,r)}`,values:[n]});return(await Tt.any(o)).map((t=>t.rev))}static async getUnspentRevsByPublicKey(t,e,s,r){const a=new se({name:`Output.getUnspentRevsByPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE asm LIKE '%' || $1 || '%' AND "isTbcOutput" = true \n AND NOT EXISTS (SELECT 1 FROM "Input" ip WHERE "ip"."outputSpent" = "Output"."rev") \n ${this.sqlSuffix(e,s,r)}`,values:[t]});return(await Tt.any(a)).map((t=>t.rev))}static async getUnspentRevsByModAndPublicKey(t,e,s,r,a){const n=await this.getUnspentRevsByPublicKey(e,s,r,a);const o=await this.getIdsByRevs(n);const i=new se({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1 AND "rev" = ANY($2)',values:[t,o]});const c=(await Tt.any(i)).map((t=>t.rev));const u=await this.getLatestRevs(c);const l=new se({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(s,r,a)}`,values:[u]});return(await Tt.any(l)).map((t=>t.rev))}static async getUnspentTbcOutputs(t,e,s){const r=new se({name:`Output.getUnspentTbcOutputs.${Math.random()}`,text:`SELECT "rev", "address", "satoshis", "asm", "timestamp"\n FROM "Output" WHERE "isTbcOutput" = true AND NOT EXISTS\n (SELECT 1 FROM "Input" ip WHERE "ip"."outputSpent" = "Output"."rev") ${this.sqlSuffix(t,e,s)}`});return(await Tt.any(r)).map((t=>t.rev))}static async query(t){const{publicKey:e,limit:s,offset:r,ids:a,mod:n,order:o}=t;const i=parseInt(V||"",10);if(s&&parseInt(s||"",10)>i||a&&a.length>i)throw new Error(`Can't fetch more than ${V} revs.`);if(o&&"ASC"!==o&&"DESC"!==o)throw new Error("Invalid order. Should be ASC or DESC.");return a?(a.map(ee),this.getLatestRevs(a)):n&&!e?this.getUnspentRevsByMod(n,s,r,o):!n&&e?this.getUnspentRevsByPublicKey(e,s,r,o):n&&e?this.getUnspentRevsByModAndPublicKey(n,e,s,r,o):this.getUnspentTbcOutputs(s,r,o)}static async selectNext(t){const e=new se({name:`Output.selectNext.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "previous" = $1',values:[t]});return Tt.any(e)}static async selectPrev(t){const e=new se({name:`Output.selectPrev.${Math.random()}`,text:'SELECT "previous" FROM "Output" WHERE "rev" = $1',values:[t]});return Tt.any(e)}}class ae{static async select(t){return re.select(t)}static async insert(t){return re.insert(t)}static async eraseBlockHash(t){return re.eraseBlockHash(t)}static async listSentOutputs(t){return re.listSentOutputs(t)}static async listReceivedOutputs(t){return re.listReceivedOutputs(t)}static async listTxs(t){return re.listTxs(t)}static async getLatestRev(t){return re.getLatestRev(t)}static async getLatestRevs(t){return re.getLatestRevs(t)}static async getIdByRev(t){return re.getIdByRev(t)}static async query(t){return re.query(t)}static async selectNext(t){return re.selectNext(t)}static async selectPrev(t){return re.selectPrev(t)}}const ne=function(t=B,e=L){switch(t){case"BTC":switch(e){case"mainnet":return c.bitcoin;case"testnet":return c.testnet;case"regtest":return c.regtest;default:throw new Error(`Invalid network ${e}`)}case"LTC":switch(e){case"mainnet":return c.litecoin;case"testnet":return c.litecointestnet;case"regtest":return c.litecoinregtest;default:throw new Error(`Invalid network ${e}`)}case"PEPE":switch(e){case"mainnet":return c.pepecoin;case"testnet":return c.pepecointestnet;case"regtest":return c.pepecoinregtest;default:throw new Error(`Invalid network ${e}`)}case"DOGE":switch(e){case"mainnet":return c.dogecoin;case"testnet":return c.dogecointestnet;case"regtest":return c.dogecoinregtest;default:throw new Error(`Invalid network ${e}`)}default:throw new Error(`Invalid chain ${t}`)}}(B,L);class oe{static insert(t){return ae.insert(t)}static getOutputs=(t,e=null)=>t.flatMap((({outs:t,txId:s,zip:r,onChainMetaData:a})=>{const{exp:n="",mod:o=""}=a;return t.map((({script:t,value:a},c)=>{const u=cp.fromOutputScript(t,ne))),satoshis:Math.round(a),asm:l.toASM(t),isTbcOutput:u,mod:u?o:"",previous:u?r[c][0]:null,hash:u?i.sha256(Buffer.from(n)).toString("hex"):null,blockHash:e}}))}));static select=async t=>ae.select(t);static eraseBlockHash=async t=>{await ae.eraseBlockHash(t)};static listSentOutputs=async t=>ae.listSentOutputs(t);static listReceivedOutputs=async t=>ae.listReceivedOutputs(t);static listTxs=async t=>ae.listTxs(t);static getLatestRev=async t=>ae.getLatestRev(t);static getLatestRevs=async t=>ae.getLatestRevs(t);static getIdByRev=async t=>ae.getIdByRev(t);static query=async t=>ae.query(t)}const ie=t=>new Promise((e=>{setTimeout(e,t)}));const ce=O(o);const ue=c.regtest;const{PreparedStatement:le}=v;class pe{static async select(t){const e=new le({name:`Input.select.${Math.random()}`,text:'SELECT "outputSpent", "spendingInput", "blockHash" FROM "Input" WHERE "outputSpent" = $1',values:[t]});return Tt.any(e)}static async insert(t){await Promise.all(Wt(t,3333).map((t=>{const e=t.flatMap((({outputSpent:t,spendingInput:e,blockHash:s})=>[t,e,s]));return Tt.none(new le({name:`Input.insert.${Math.random()}`,text:`INSERT INTO "Input"("outputSpent", "spendingInput", "blockHash") VALUES ${Gt(e.length)} \n ON CONFLICT ("spendingInput") \n DO UPDATE SET "blockHash" = COALESCE("Input"."blockHash", EXCLUDED."blockHash")`,values:e}))})))}static async eraseBlockHash(t){await Promise.all(Wt(t,1e4).map((t=>{const e=t.join("','");return Tt.none(new le({name:`Input.eraseBlockHash.${Math.random()}`,text:`UPDATE "Input" SET "blockHash" = NULL WHERE "blockHash" IN ('${e}')`}))})))}static async count(t){const e=t.map((t=>t.outputSpent));const s=new le({name:`Input.belong.${Math.random()}`,text:'SELECT count(*) FROM "Input" WHERE "outputSpent" LIKE ANY ($1)',values:[[e]]});const r=await Tt.oneOrNone(s);return parseInt(r?.count,10)||0}}class de{static async select(t){return pe.select(t)}static async insert(t){return pe.insert(t)}static async eraseBlockHash(t){return pe.eraseBlockHash(t)}}class me{static insert=async t=>{await de.insert(t)};static getInputs=(t,e=null)=>t.flatMap((({ins:t,txId:e})=>t.map(((t,s)=>({input:t,index:s,txId:e}))))).filter((({input:t})=>!S.isCoinbaseHash(t.hash))).map((({input:t,index:s,txId:r})=>{return{outputSpent:`${a=t.hash,u.reverseBuffer(Buffer.from(a)).toString("hex")}:${t.index}`,spendingInput:`${r}:${s}`,blockHash:e};var a}));static select=async t=>de.select(t);static eraseBlockHash=async t=>{await de.eraseBlockHash(t)}}class he{static rawTxSubscriber=async t=>{const e=t.toString("hex");if(yt.info(`ZMQ message { hex:${e} }`),"08"!==e.slice(10,12))try{const t=$.txFromHex({hex:e});await oe.insert(oe.getOutputs([t])),await me.insert(me.getInputs([t]))}catch(t){yt.error(`[zmq] Error parsing transaction ${e}\n${t.stack}`)}};static sub=async t=>{try{yt.info(`Bitcoin Computer Node ${vt} is starting on ${L} ${B}.`),await qt.createWallet(),"regtest"!==L&&await qt.checkBlockchainProgress(.7),await qt.walletSetup(),yt.info(`Bitcoin Computer Node ${vt} is ready. ZQM activation height: ${Z}`),await qt.checkBlockchainProgress(.9);for await(const[,e]of t)await this.rawTxSubscriber(e)}catch(t){yt.error(`ZMQ subscription failed with error '${t.message}'`)}}}const{PreparedStatement:ge}=v;class ye{static async select(t){const e=new ge({name:`User.select.${Math.random()}`,text:'SELECT "publicKey", "clientTimestamp" FROM "User" WHERE "publicKey" = $1',values:[t]});const s=await Tt.oneOrNone(e);return s?{publicKey:s.publicKey,clientTimestamp:parseInt(s.clientTimestamp,10)||0}:null}static async insert({publicKey:t,clientTimestamp:e}){const s=new ge({name:`User.insert.${Math.random()}`,text:'INSERT INTO "User"("publicKey", "clientTimestamp") VALUES ($1, $2)',values:[t,e]});await Tt.none(s)}static async update({publicKey:t,clientTimestamp:e}){const s=new ge({name:`User.update.${Math.random()}`,text:'UPDATE "User" SET "clientTimestamp"=$1 WHERE "publicKey"=$2',values:[e,t]});await Tt.none(s)}}class we{static async select(t){return ye.select(t)}static async insert(t){return ye.insert(t)}static async update(t){return ye.update(t)}}const{ec:ve}=b;const fe=new ve("secp256k1");const Ee=s();const Te=new class{configFile;loaded=!1;load=()=>{try{const t="dev"===z?"bcn.test.config.json":"bcn.config.json";const e=M(C(import.meta.url));this.configFile=T.readFileSync(N.join(e,"..","..",t)),this.loaded=!0}catch(t){if(t.message.includes("ENOENT: no such file or directory"))return void(this.loaded=!0);throw yt.error(`Access-list failed with error '${t.message}'`),t}};middleware=({url:t},e,s)=>{if(void 0!==e.locals.authToken)if(this.loaded||(yt.warn("Access-list failed with error 'AccessList not loaded.'. Loading now."),this.load()),void 0!==this.configFile)try{const{blacklist:t,whitelist:r}=JSON.parse(this.configFile.toString());if(t&&r)return void e.status(403).json({error:"Cannot enforce blacklist and whitelist at the same time."});const{publicKey:a}=e.locals.authToken;if(r&&!r.includes(a)||t&&t.includes(a))return void e.status(403).json({error:`Public key ${a} is not allowed.`});s()}catch(s){yt.error(`Authorization failed at ${t} with error: '${s.message}'`),e.status(403).json({error:s.message})}else s();else s()}};let Oe;h(o);try{Oe=r.createServer(Ee)}catch(t){throw yt.error(`Starting server failed with error '${t.message}'`),t}if(yt.info(`Server listening on port ${A}`),Ee.use(e()),"true"===D){const t=n({windowMs:parseInt(W,10),max:parseInt(G,10),standardHeaders:"true"===K,legacyHeaders:"true"===Y});Ee.use(t)}Ee.use(t.json({limit:"100mb"})),Ee.use(t.urlencoded({limit:"100mb",extended:!0})),Ee.get("/",((t,e)=>e.status(200).send(`\n

Bitcoin Computer Node

\n Status: Healthy
\n Version: ${vt}
\n Chain: ${B}
\n Network: ${L}\n `))),Te.loaded&&(Ee.use((async(t,e,s)=>{try{const r=t.get("Authentication");if(!r){const{method:s,url:r}=t;const a=`Auth failed with error 'no Authentication key provided' ${s} ${t.get("Host")} ${r}`;return yt.error(a),void e.status(401).json({error:a})}const a=(t=>{const e=t.split(" ");if(2!==e.length||"Bearer"!==e[0])throw new Error("Authentication header is invalid.");const s=Buffer.from(e[1],"base64").toString().split(":");if(3!==s.length)throw new Error;return{signature:s[0],publicKey:s[1],timestamp:parseInt(s[2],10)}})(r);const{signature:n,publicKey:o,timestamp:i}=a;if(Date.now()-i>18e4)return void e.status(401).json({error:"Signature is too old."});const c=x.sha256().update(J+i).digest("hex");if(!fe.keyFromPublic(o,"hex").verify(c,n)){const t="The origin and public key pair doesn't match the signature.";return void e.status(401).json({error:t})}const u=await we.select(o);if(u){if(u.clientTimestamp>=i)return void e.status(401).json({error:"Please use a fresh authentication token."});await we.update({publicKey:o,clientTimestamp:i})}else await we.insert({publicKey:o,clientTimestamp:i});e.locals.authToken=a,s()}catch(t){yt.error(`Auth failed with error '${t.message}'`),e.status(401).json({error:t.message})}})),Ee.use(Te.middleware)),Ee.use((async function(t,e,s){const r=Date.now();yt.http(`${t.method} ${t.originalUrl}`),e.on("finish",(()=>{const{method:s,originalUrl:a}=t;const{statusCode:n}=e;const o=Date.now()-r;yt.http(`${s} ${a} ${n} - ${o}ms`)})),s()}));const $e=(()=>{const t=s.Router();return t.get("/wallet/:address/utxos",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await Nt.selectByAddress(e))}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/wallet/:address/sent-outputs",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await oe.listSentOutputs(e))}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/wallet/:address/received-outputs",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await oe.listReceivedOutputs(e))}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/wallet/:address/list-txs",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await oe.listTxs(e))}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/non-standard-utxos",(async(t,e)=>{try{const s=new URLSearchParams(t.url.split("?")[1]);const r={mod:s.get("mod"),publicKey:s.get("publicKey"),limit:s.get("limit"),order:s.get("order"),offset:s.get("offset"),ids:JSON.parse(s.get("ids"))};const a=await oe.query(r);e.status(200).json(a)}catch(s){yt.error(`GET ${t.url} failed with error '${s.messages}'`),e.status(500).json({error:s.message})}})),t.get("/address/:address/balance",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await Nt.getBalance(e))}catch(t){yt.error(`GET ${e} failed with error '${t.message||t}'`),s.status(500).json({error:t.message})}})),t.post("/tx/bulk",(async({body:{txIds:t},url:e},s)=>{try{if(void 0===t||0===t.length)return void s.status(400).json({error:"Missing input txIds."});const e=await Vt.getRaw(t);e?s.status(200).json(e):s.status(404).json({error:"Not found"})}catch(t){yt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/tx/post",(async({body:{hex:t},url:e},s)=>{try{if(!t)return void s.status(400).json({error:"Missing input hex."});const e=await Vt.sendRaw(t);e?s.status(200).json(e):s.status(404).json({error:"Error Occured"})}catch(r){yt.error(`POST ${e} failed with error '${r.message}\ntxHex: ${t}`),s.status(500).json({error:r.message})}})),t.get("/mine",(async({query:{count:t},url:e},s)=>{try{const{result:e}=await Zt.getnewaddress();if("string"!=typeof t)throw new Error("Please provide appropriate count");return await Zt.generatetoaddress(parseInt(t,10)||1,e),s.status(200).json({success:!0})}catch(t){return yt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/:id/height",(async({params:{id:t},url:e},s)=>{try{let e=t;if("best"===t){const{result:t}=await Zt.getbestblockhash();e=t}const{result:r}=await Zt.getblockheader(e,!0);return s.status(200).json({height:r.height})}catch(t){return yt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/faucet",(async({body:{address:t,value:e},url:s},r)=>{try{const s=parseInt(e,10)/1e8;const{result:a}=await Zt.sendtoaddress(t,s);await Zt.generateToAddress(1,"mvFeNF9DAR7WMuCpBPbKuTtheihLyxzj8i");const{result:n}=await Zt.getrawtransaction(a,1);const o=n.vout.findIndex((t=>1e8*t.value===parseInt(e,10)));return r.status(200).json({txId:a,vout:o,height:-1,satoshis:e})}catch(t){return yt.error(`POST ${s} failed with error '${t.message}'`),r.status(500).json({error:t.message})}})),t.post("/faucetScript",(async({body:{script:t,value:e},url:s},r)=>{try{const s=ce.makeRandom({network:ue});const a=d.p2pkh({pubkey:s.publicKey,network:ue});const{address:n}=a;const o=(await Zt.sendtoaddress(n,2*parseInt(e,10)/1e8,"","")).result;let i;let c=10;for(;!i;)if(i=(await Nt.selectByAddress(n)).filter((t=>t.txId===o))[0],!i){if(c-=1,c<=0)throw new Error("No outputs");await ie(10)}const u=(await Zt.getrawtransaction(i.txId,1)).result;const p=new m({network:ue});p.addInput({hash:i.txId,index:i.vout,nonWitnessUtxo:Buffer.from(u.hex,"hex")}),p.addOutput({script:Buffer.from(t,"hex"),value:parseInt(e,10)}),p.signInput(0,s),p.finalizeAllInputs();const h=p.extractTransaction(!0);let g;for(await Zt.sendrawtransaction(h.toHex()),c=5;!g;){const e=l.toASM(Buffer.from(t,"hex"));if(g=(await Nt.selectByScriptASM(e)).filter((t=>t.txId===h.getId()))[0],!g){if(c-=1,c<=0)throw new Error("No outputs");await ie(10)}}return r.status(200).json({txId:h.getId(),vout:g.vout,height:-1,satoshis:g.satoshis})}catch(t){return yt.error(`POST ${s} failed with error '${t.message}'`),r.status(500).json({error:t.message})}})),t.get("/tx/:txId/json",(async({params:{txId:t},url:e},s)=>{try{if(!t)return void s.status(400).json({error:"Missing input txId."});const e=await Vt.getRawJSON(t);e?s.status(200).json(e):s.status(404).json({error:"Not found"})}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/revs",(async({body:{ids:t},url:e},s)=>{try{if(void 0===t||0===t.length)return void s.status(400).json({error:"Missing input object ids."});const e=await oe.getLatestRevs(t);s.status(200).json(e)}catch(t){yt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/revToId",(async({body:{rev:t},url:e},s)=>{try{if(!te(t))return void s.status(400).json({error:"Invalid rev id"});const e=await oe.getIdByRev(t);e&&s.status(200).json(e),s.status(404).json()}catch(t){yt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/rpc",(async({body:t,url:e},s)=>{try{if(!t||!t.method)throw new Error("Please provide appropriate RPC method name");if(!new RegExp(H).test(t.method))throw new Error("Method is not allowed");const e=function(t,e){if(void 0===Xt[t]||null===Xt[t])throw new Error("This RPC method does not exist, or not supported");const s=e.trim().split(" ");const r=Xt[t].trim().split(" ");if(0===e.trim().length&&0!==Xt[t].trim().length)throw new Error(`Too few params provided. Expected ${r.length} Provided 0`);if(0!==e.trim().length&&0===Xt[t].trim().length)throw new Error(`Too many params provided. Expected 0 Provided ${s.length}`);if(s.lengthr.length)throw new Error(`Too many params provided. Expected ${r.length} Provided ${s.length}`);return 0===e.length?[]:s.map(((t,e)=>Qt[r[e]](t)))}(t.method,t.params);const r=e.length?await Zt[t.method](...e):await Zt[t.method]();s.status(200).json({result:r})}catch(t){yt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/next/:rev",(async({params:{rev:t},url:e},s)=>{try{if(!t)return void s.status(400).json({error:"Missing rev."});const e=await ae.selectNext(t);e?s.status(200).json({rev:e&&e.length?e[0].rev:void 0}):s.status(404).json({error:"Not found"})}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/prev/:rev",(async({params:{rev:t},url:e},s)=>{try{if(!t)return void s.status(400).json({error:"Missing rev."});const e=await ae.selectPrev(t);e?s.status(200).json({rev:e&&e.length?e[0].previous:void 0}):s.status(404).json({error:"Not found"})}catch(t){yt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/non-standard-utxo",(async(t,e)=>{e.status(500).json({error:"Please upgrade to @bitcoin-computer/lib to the latest version."})})),t})();Ee.use(`/v1/${B}/${L}`,$e),Ee.use("/v1/store",It),Oe.listen(A,(()=>{yt.info(`\nStarted Bitcoin Computer Node Version ${vt}\nPORT ${A} \n`)})).on("error",(t=>{yt.error(t.message),process.exit(1)}));const Se=new a.Subscriber;Se.connect(P),Se.subscribe("rawtx"),yt.info(`ZMQ Subscriber connected to ${P}`),(async()=>{await(async()=>{await E((()=>Tt.connect()),{startingDelay:500})})(),await he.sub(Se)})(); diff --git a/packages/node/dist/bcn.sync.es.mjs b/packages/node/dist/bcn.sync.es.mjs index cdcf6320c..51651c57f 100644 --- a/packages/node/dist/bcn.sync.es.mjs +++ b/packages/node/dist/bcn.sync.es.mjs @@ -1 +1 @@ -import*as t from"@bitcoin-computer/secp256k1";import{networks as e,bufferUtils as s,script as a,crypto as n,address as r,initEccLib as o}from"@bitcoin-computer/nakamotojs";import{availableParallelism as i}from"node:os";import{spawn as c}from"node:child_process";import{exit as p}from"node:process";import l from"dotenv";import u from"winston";import d from"winston-daily-rotate-file";import{backOff as h}from"exponential-backoff";import m from"pg-promise";import y from"pg-monitor";import w from"fs";import{Transaction as g,Computer as E}from"@bitcoin-computer/lib";import $ from"bitcoind-rpc";import v from"util";l.config();const T=process.env.BCN_CHAIN;const S=process.env.BCN_NETWORK;const{BCN_PORT:f}=process.env;process.env,process.env;const{BCN_DEBUG_MODE:O}=process.env;const{BCN_LOG_MAX_FILES:I}=process.env;const{BCN_LOG_MAX_SIZE:R}=process.env;const{BCN_LOG_ZIP:k}=process.env;const{BCN_SHOW_CONSOLE_LOGS:B}=process.env;const{BCN_SHOW_DB_LOGS:b}=process.env;process.env,process.env,process.env,process.env,process.env;const{BCN_NUM_WORKERS:M}=process.env;const{BCN_WORKER_ID:N}=process.env;process.env,process.env;const x=process.env.BCN_QUERY_LIMIT||"1000";const H=process.env.BCN_URL||`http://127.0.0.1:${f}`;process.env.BCN_ENV;const C=process.env.BCN_ZMQ_ACTIVATION_HEIGHT||"1";const{BITCOIN_RPC_USER:A}=process.env;const{BITCOIN_RPC_PASSWORD:L}=process.env;const{BITCOIN_RPC_HOST:P}=process.env;const{BITCOIN_RPC_PORT:D}=process.env;const{BITCOIN_RPC_PROTOCOL:_}=process.env;const{BITCOIN_DEFAULT_WALLET:U}=process.env;const{POSTGRES_USER:W}=process.env;const{POSTGRES_PASSWORD:F}=process.env;const{POSTGRES_DB:Y}=process.env;const{POSTGRES_HOST:G}=process.env;const{POSTGRES_PORT:K}=process.env;const{POSTGRES_MAX_CONNECTIONS:V}=process.env;const{POSTGRES_IDLE_TIMEOUT_MILLIS:j}=process.env;u.addColors({error:"red",warn:"yellow",info:"green",http:"magenta",debug:"white"});const q=u.format.combine(u.format.colorize(),u.format.timestamp({format:"YYYY-MM-DD HH:mm:ss:ms"}),u.format.json(),u.format.printf((t=>`${t.timestamp} [${t.level.slice(5).slice(0,-5)}] ${t.message}`)));const J={zippedArchive:"true"===k,maxSize:R,maxFiles:I,dirname:"logs"};const z=[];"true"===B&&z.push(new u.transports.Console({format:u.format.combine(u.format.colorize(),u.format.timestamp({format:"MM-DD-YYYY HH:mm:ss"}),u.format.printf((t=>`${t.timestamp} ${t.level} ${t.message}`)))}));const X=parseInt(O,10);X>=0&&z.push(new d({filename:"error-%DATE%.log",datePattern:"YYYY-MM-DD",level:"error",...J})),X>=1&&z.push(new d({filename:"warn-%DATE%.log",datePattern:"YYYY-MM-DD",level:"warn",...J})),X>=2&&z.push(new d({filename:"info-%DATE%.log",datePattern:"YYYY-MM-DD",level:"info",...J})),X>=3&&z.push(new d({filename:"http-%DATE%.log",datePattern:"YYYY-MM-DD",level:"http",...J})),X>=4&&z.push(new d({filename:"debug-%DATE%.log",datePattern:"YYYY-MM-DD",level:"debug",...J}));const Z=u.createLogger({levels:{error:0,warn:1,info:2,http:3,debug:4},format:q,transports:z,exceptionHandlers:[new u.transports.File({filename:"logs/exceptions.log"})],rejectionHandlers:[new u.transports.File({filename:"logs/rejections.log"})]});l.config();const{version:Q}=JSON.parse(w.readFileSync("package.json","utf8"));Q||process.env.BCN_SERVER_VERSION;const tt=parseInt(process.env.MWEB_HEIGHT||"",10)||432;const et={error:(t,e)=>{if(e.cn){const{host:s,port:a,database:n,user:r,password:o}=e.cn;Z.debug(`Waiting for db to start { message:${t.message} host:${s}, port:${a}, database:${n}, user:${r}, password: ${o}`)}},noWarnings:!0};"true"===b&&(y.isAttached()?y.detach():(y.attach(et),y.setTheme("matrix")));const st=m(et)({host:G,port:parseInt(K,10),database:Y,user:W,password:F,max:parseInt(V,10),allowExitOnIdle:!0,idleTimeoutMillis:parseInt(j,10)});const{PreparedStatement:at}=m;class nt{static async selectByWorkerId(t){const e=new at({name:`TxStatus.select.${Math.random()}`,text:'SELECT "blockToSync", "workerId" FROM "TxStatus" WHERE "workerId" = $1',values:[t]});return st.oneOrNone(e)}static async update({blockToSync:t,workerId:e}){const s=new at({name:`TxStatus.update.${Math.random()}`,text:'UPDATE "TxStatus" SET "blockToSync" = $1 WHERE "workerId" = $2',values:[t,e]});await st.any(s)}static async count(){const t=new at({name:`TxStatus.count.${Math.random()}`,text:'SELECT COUNT(*) FROM "TxStatus"'});const e=await st.oneOrNone(t);return parseInt(e?.count,10)||0}static async min(){const t=new at({name:`TxStatus.min.${Math.random()}`,text:'SELECT MIN("blockToSync") FROM "TxStatus"'});const e=await st.oneOrNone(t);return parseInt(e?.min,10)||0}static async deleteAll(){const t=new at({name:`TxStatus.delete.${Math.random()}`,text:'DELETE FROM "TxStatus"'});await st.any(t)}static async insertBatch(t){const e=[];for(let s=1;s<=t.length;s+=2)e.push(`($${s}, $${s+1})`);const s=e.join(",");const a=new at({name:`TxStatus.reorg.${Math.random()}`,text:`INSERT INTO "TxStatus"("workerId", "blockToSync") VALUES ${s}`,values:t});await st.any(a)}}class rt{static async selectByWorkerId(t){return nt.selectByWorkerId(t)}static async update(t){await nt.update(t)}static async count(){return nt.count()}static async insertBatch(t){await nt.insertBatch(t)}static async min(){return nt.min()}static async deleteAll(){await nt.deleteAll()}}const{PreparedStatement:ot}=m;class it{static async select(){const t=new ot({name:`BlockStatus.select.${Math.random()}`,text:'SELECT "blockToSync" FROM "BlockStatus"'});const e=await st.oneOrNone(t);return parseInt(e?.blockToSync,10)||null}static async update(t){const e=new ot({name:`BlockStatus.update.${Math.random()}`,text:'UPDATE "BlockStatus" SET "blockToSync" = $1',values:[t]});await st.any(e)}static async insert(t){const e=new ot({name:`BlockStatus.insert.${Math.random()}`,text:'INSERT INTO "BlockStatus"("blockToSync") VALUES ($1)',values:[t]});await st.any(e)}static async count(){const t=new ot({name:`BlockStatus.count.${Math.random()}`,text:'SELECT COUNT(*) FROM "BlockStatus"'});const e=await st.oneOrNone(t);return parseInt(e?.count,10)||0}static async delete(){const t=new ot({name:`BlockStatus.delete.${Math.random()}`,text:'DELETE FROM "BlockStatus"'});await st.any(t)}}class ct{static async select(){return it.select()}static async update(t){await it.update(t)}static async insert(t){await it.insert(t)}static async count(){return it.count()}static async delete(){await it.delete()}}class pt{static update=async t=>ct.update(t);static select=async()=>ct.select();static insert=async t=>ct.insert(t);static count=async()=>ct.count();static delete=async()=>ct.delete()}function lt(t){t.forEach((t=>{Z.info(`[wid 0 pid: ${process.pid}]: killing worker ${t}`);try{Z.info(`[wid 0 pid: ${process.pid}]: killing worker (SIGTERM) ${t}: ${process.kill(t,"SIGTERM")}`),process.kill(t,"SIGTERM")}catch(e){Z.info(`[wid 0 pid: ${process.pid}]: killing worker ${t}: ${process.kill(t)}`),process.kill(t,"SIGKILL")}}))}const ut=t=>new Promise((e=>{setTimeout(e,t)}));const dt=(t,e)=>{const s=[];for(let a=0;a{const e=[];for(let s=1;s<=t;s+=3){const t=`($${s},$${s+1},$${s+2})`;e.push(t)}return e.join(",")};const mt=t=>{const e=[];for(let s=1;s<=t;s+=9){const t=`($${s},$${s+1},$${s+2},$${s+3},$${s+4},$${s+5},$${s+6},$${s+7},$${s+8})`;e.push(t)}return e.join(",")};const yt=t=>{try{return t()}catch{return null}};const{PreparedStatement:wt}=m;class gt{static async selectByHeight(t){const e=new wt({name:`Block.select.${Math.random()}`,text:'SELECT "hash", "height", "previousHash" FROM "Block" WHERE "height" = $1',values:[t]});return st.oneOrNone(e)}static async insert(t){const e=new wt({name:`Block.insert.${Math.random()}`,text:'INSERT INTO "Block" ("hash", "height", "previousHash") VALUES ($1, $2, $3)',values:[t.hash,t.height,t.previousHash]});await st.none(e)}static async insertBatch(t){const e=t.flatMap((({hash:t,height:e,previousHash:s})=>[t,e,s]));const s=new wt({name:`Block.insertBatch.${Math.random()}`,text:`INSERT INTO "Block" ("hash", "height", "previousHash") VALUES ${ht(e.length)}`,values:e});await st.none(s)}static async deleteAll(){const t=new wt({name:`Block.delete.${Math.random()}`,text:'DELETE FROM "Block"',values:[]});await st.none(t)}static async deleteByHash(t){const e=t.map((t=>t)).join("', '");const s=new wt({name:`Block.deleteByHash.${Math.random()}`,text:`DELETE FROM "Block" WHERE "hash" IN ('${e}')`});await st.none(s)}}class Et{static selectByHeight=async t=>gt.selectByHeight(t);static insert=async t=>gt.insert(t);static insertBatch=async t=>gt.insertBatch(t);static deleteAll=async()=>gt.deleteAll();static deleteByHash=async t=>gt.deleteByHash(t);static waitForDbBlockHash=async(t,e)=>(await h((async()=>{let s;try{s=await gt.selectByHeight(t)}catch(s){throw Z.info(`[wid ${e} pid: ${process.pid}]: waiting for DB to get block ${t} ...`),s}return s}),{startingDelay:1e4,timeMultiple:1,numOfAttempts:720})).hash}class $t{static selectByHeight=async t=>Et.selectByHeight(t);static insert=async t=>Et.insert(t);static insertBatch=async t=>Et.insertBatch(t);static deleteAll=async()=>Et.deleteAll();static deleteByHash=async t=>Et.deleteByHash(t);static waitForDbBlockHash=async(t,e)=>Et.waitForDbBlockHash(t,e)}const{PreparedStatement:vt}=m;class Tt{static async selectAll(){const t=new vt({name:`Orphan.select.${Math.random()}`,text:'SELECT * FROM "Orphan"'});return st.any(t)}static async insertAll(t){const e=t.map((t=>`('${t.hash}',${t.height}, ${t.processed})`)).join(",");const s=new vt({name:`Orphan.insert.${Math.random()}`,text:`INSERT INTO "Orphan" (hash, height, processed) VALUES ${e}`});await st.none(s)}static async process(t){const e=t.map((t=>t.height)).join(",");const s=new vt({name:`Orphan.process.${Math.random()}`,text:`UPDATE "Orphan" SET processed = true WHERE height IN (${e})`});await st.none(s)}}class St{static selectAll=async()=>Tt.selectAll();static insertAll=async t=>Tt.insertAll(t);static process=async t=>Tt.process(t)}class ft{static selectAll=async()=>St.selectAll();static insertAll=async t=>St.insertAll(t);static process=async t=>St.process(t)}const{PreparedStatement:Ot}=m;class It{static async select(t){const e=new Ot({name:`Input.select.${Math.random()}`,text:'SELECT "outputSpent", "spendingInput", "blockHash" FROM "Input" WHERE "outputSpent" = $1',values:[t]});return st.any(e)}static async insert(t){await Promise.all(dt(t,3333).map((t=>{const e=t.flatMap((({outputSpent:t,spendingInput:e,blockHash:s})=>[t,e,s]));return st.none(new Ot({name:`Input.insert.${Math.random()}`,text:`INSERT INTO "Input"("outputSpent", "spendingInput", "blockHash") VALUES ${ht(e.length)} \n ON CONFLICT ("spendingInput") \n DO UPDATE SET "blockHash" = COALESCE("Input"."blockHash", EXCLUDED."blockHash")`,values:e}))})))}static async eraseBlockHash(t){await Promise.all(dt(t,1e4).map((t=>{const e=t.join("','");return st.none(new Ot({name:`Input.eraseBlockHash.${Math.random()}`,text:`UPDATE "Input" SET "blockHash" = NULL WHERE "blockHash" IN ('${e}')`}))})))}static async count(t){const e=t.map((t=>t.outputSpent));const s=new Ot({name:`Input.belong.${Math.random()}`,text:'SELECT count(*) FROM "Input" WHERE "outputSpent" LIKE ANY ($1)',values:[[e]]});const a=await st.oneOrNone(s);return parseInt(a?.count,10)||0}}class Rt{static async select(t){return It.select(t)}static async insert(t){return It.insert(t)}static async eraseBlockHash(t){return It.eraseBlockHash(t)}}class kt{static insert=async t=>{await Rt.insert(t)};static getInputs=(t,e=null)=>t.flatMap((({ins:t,txId:e})=>t.map(((t,s)=>({input:t,index:s,txId:e}))))).filter((({input:t})=>!g.isCoinbaseHash(t.hash))).map((({input:t,index:a,txId:n})=>{return{outputSpent:`${r=t.hash,s.reverseBuffer(Buffer.from(r)).toString("hex")}:${t.index}`,spendingInput:`${n}:${a}`,blockHash:e};var r}));static select=async t=>Rt.select(t);static eraseBlockHash=async t=>{await Rt.eraseBlockHash(t)}}function Bt(t){if(!function(t){return/^[0-9A-Fa-f]{64}:\d+$/.test(t)}(t))throw new Error("Invalid rev")}const{PreparedStatement:bt}=m;class Mt{static async listSentOutputs(t){const e=new bt({name:`Output.listSentTxs.${Math.random()}`,text:'SELECT "Input"."spendingInput" AS "output", "Output"."satoshis" AS "amount"\n FROM "Output" INNER JOIN "Input" ON "Output".rev = "Input"."outputSpent" \n WHERE "Output"."address" = $1',values:[t]});return(await st.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listReceivedOutputs(t){const e=new bt({name:`Output.listReceivedTxs.${Math.random()}`,text:'SELECT "Output"."rev" as "output", "Output"."satoshis" as "amount" FROM "Output" WHERE "address" = $1',values:[t]});return(await st.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listTxs(t){const e=new bt({name:`Output.listTxs.${Math.random()}`,text:'WITH\n -- List all txs sent from a given address\n SENT AS (\n SELECT split_part("Input"."spendingInput",\':\',1) as "txId", SUM("Output".satoshis) as "satoshis"\n FROM "Output" INNER JOIN "Input" ON "Output".rev = "Input"."outputSpent"\n WHERE "Output".address = $1\n GROUP BY split_part("Input"."spendingInput",\':\',1)\n ),\n -- List all tx received from a given address\n RECEIVED AS (\n SELECT SPLIT_PART("Output"."rev",\':\',1) as "txId", SUM("Output"."satoshis") as "satoshis" \n FROM "Output" \n WHERE "address" = $1\n GROUP BY "txId"\n )\n\n SELECT\n RECEIVED."txId", \n coalesce(SENT."satoshis", 0) as "inputsSatoshis", \n coalesce(RECEIVED."satoshis", 0) as "outputsSatoshis", \n coalesce(RECEIVED."satoshis",0) - coalesce(SENT."satoshis",0) as "satoshis"\n FROM\n SENT RIGHT JOIN RECEIVED ON SENT."txId" = RECEIVED."txId";',values:[t]});const s=(await st.any(e)).map((t=>({...t,inputsSatoshis:parseInt(t.inputsSatoshis,10)||0,outputsSatoshis:parseInt(t.outputsSatoshis,10)||0,satoshis:parseInt(t.satoshis,10)||0})));return{sentTxs:s.filter((t=>t.satoshis<0)).map((t=>({...t,satoshis:Math.abs(t.satoshis)}))),receivedTxs:s.filter((t=>t.satoshis>=0))}}static async select(t){const e=new bt({name:`Output.select.${Math.random()}`,text:'SELECT "address", "satoshis", "asm", "rev", "hash", "mod", "isTbcOutput", "previous", "blockHash" FROM "Output" WHERE "address" = $1',values:[t]});return st.any(e)}static async insert(t){await Promise.all(dt(t,1111).map((t=>{const e=t.flatMap((({rev:t,address:e,satoshis:s,asm:a,isTbcOutput:n,mod:r,previous:o,hash:i,blockHash:c})=>[t,e,s,a,n,r,o,i,c]));return st.none(new bt({name:`Output.insert.${Math.random()}`,text:`INSERT INTO "Output"("rev", "address", "satoshis", "asm", "isTbcOutput",\n "mod", "previous", "hash", "blockHash") VALUES ${mt(e.length)} ON CONFLICT ("rev") \n DO UPDATE SET "blockHash" = COALESCE("Output"."blockHash", EXCLUDED."blockHash")`,values:e}))})))}static async eraseBlockHash(t){await Promise.all(dt(t,1e4).map((t=>{const e=t.join("','");return st.none(new bt({name:`Output.eraseBlockHash.${Math.random()}`,text:`UPDATE "Output" SET "blockHash" = NULL WHERE "blockHash" IN ('${e}')`}))})))}static async getIdByRev(t){const e=new bt({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON r."previous" = o."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=(await st.any(e)).filter((t=>null===t.previous));return s[0]?.rev}static async getIdsByRevs(t){return Promise.all(t.map((t=>this.getIdByRev(t))))}static async getLatestRev(t){const e=new bt({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON o."previous" = r."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=await st.any(e);const a=Object.fromEntries(s.map((t=>[t.previous,t.rev])));let n=t;for(;a[n];)n=a[n];return n}static async getLatestRevs(t){return Promise.all(t.map(this.getLatestRev))}static async getIdsByMod(t){const e=new bt({name:`Output.getIdsByMod.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1',values:[t]});return(await st.any(e)).map((t=>t.rev))}static sqlSuffix(t,e,s){let a="";return s&&(a+=` order by "timestamp" ${s}`),a+=` limit ${t||x}`,e&&(a+=` offset ${e}`),a}static async getUnspentRevsByMod(t,e,s,a){const n=await this.getIdsByMod(t);const r=await this.getLatestRevs(n);const o=new bt({name:`Output.getUnspentRevsByMod.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(e,s,a)}`,values:[r]});return(await st.any(o)).map((t=>t.rev))}static async getUnspentRevsByPublicKey(t,e,s,a){const n=new bt({name:`Output.getUnspentRevsByPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE asm LIKE '%' || $1 || '%' AND "isTbcOutput" = true \n AND NOT EXISTS (SELECT 1 FROM "Input" ip WHERE "ip"."outputSpent" = "Output"."rev") \n ${this.sqlSuffix(e,s,a)}`,values:[t]});return(await st.any(n)).map((t=>t.rev))}static async getUnspentRevsByModAndPublicKey(t,e,s,a,n){const r=await this.getUnspentRevsByPublicKey(e,s,a,n);const o=await this.getIdsByRevs(r);const i=new bt({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1 AND "rev" = ANY($2)',values:[t,o]});const c=(await st.any(i)).map((t=>t.rev));const p=await this.getLatestRevs(c);const l=new bt({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(s,a,n)}`,values:[p]});return(await st.any(l)).map((t=>t.rev))}static async getUnspentTbcOutputs(t,e,s){const a=new bt({name:`Output.getUnspentTbcOutputs.${Math.random()}`,text:`SELECT "rev", "address", "satoshis", "asm", "timestamp"\n FROM "Output" WHERE "isTbcOutput" = true AND NOT EXISTS\n (SELECT 1 FROM "Input" ip WHERE "ip"."outputSpent" = "Output"."rev") ${this.sqlSuffix(t,e,s)}`});return(await st.any(a)).map((t=>t.rev))}static async query(t){const{publicKey:e,limit:s,offset:a,ids:n,mod:r,order:o}=t;const i=parseInt(x||"",10);if(s&&parseInt(s||"",10)>i||n&&n.length>i)throw new Error(`Can't fetch more than ${x} revs.`);if(o&&"ASC"!==o&&"DESC"!==o)throw new Error("Invalid order. Should be ASC or DESC.");return n?(n.map(Bt),this.getLatestRevs(n)):r&&!e?this.getUnspentRevsByMod(r,s,a,o):!r&&e?this.getUnspentRevsByPublicKey(e,s,a,o):r&&e?this.getUnspentRevsByModAndPublicKey(r,e,s,a,o):this.getUnspentTbcOutputs(s,a,o)}static async selectNext(t){const e=new bt({name:`Output.selectNext.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "previous" = $1',values:[t]});return st.any(e)}static async selectPrev(t){const e=new bt({name:`Output.selectPrev.${Math.random()}`,text:'SELECT "previous" FROM "Output" WHERE "rev" = $1',values:[t]});return st.any(e)}}class Nt{static async select(t){return Mt.select(t)}static async insert(t){return Mt.insert(t)}static async eraseBlockHash(t){return Mt.eraseBlockHash(t)}static async listSentOutputs(t){return Mt.listSentOutputs(t)}static async listReceivedOutputs(t){return Mt.listReceivedOutputs(t)}static async listTxs(t){return Mt.listTxs(t)}static async getLatestRev(t){return Mt.getLatestRev(t)}static async getLatestRevs(t){return Mt.getLatestRevs(t)}static async getIdByRev(t){return Mt.getIdByRev(t)}static async query(t){return Mt.query(t)}static async selectNext(t){return Mt.selectNext(t)}static async selectPrev(t){return Mt.selectPrev(t)}}const xt=function(t=T,s=S){switch(t){case"BTC":switch(s){case"mainnet":return e.bitcoin;case"testnet":return e.testnet;case"regtest":return e.regtest;default:throw new Error(`Invalid network ${s}`)}case"LTC":switch(s){case"mainnet":return e.litecoin;case"testnet":return e.litecointestnet;case"regtest":return e.litecoinregtest;default:throw new Error(`Invalid network ${s}`)}case"PEPE":switch(s){case"mainnet":return e.pepecoin;case"testnet":return e.pepecointestnet;case"regtest":return e.pepecoinregtest;default:throw new Error(`Invalid network ${s}`)}case"DOGE":switch(s){case"mainnet":return e.dogecoin;case"testnet":return e.dogecointestnet;case"regtest":return e.dogecoinregtest;default:throw new Error(`Invalid network ${s}`)}default:throw new Error(`Invalid chain ${t}`)}}(T,S);class Ht{static insert(t){return Nt.insert(t)}static getOutputs=(t,e=null)=>t.flatMap((({outs:t,txId:s,zip:o,onChainMetaData:i})=>{const{exp:c="",mod:p=""}=i;return t.map((({script:t,value:i},l)=>{const u=lr.fromOutputScript(t,xt))),satoshis:Math.round(i),asm:a.toASM(t),isTbcOutput:u,mod:u?p:"",previous:u?o[l][0]:null,hash:u?n.sha256(Buffer.from(c)).toString("hex"):null,blockHash:e}}))}));static select=async t=>Nt.select(t);static eraseBlockHash=async t=>{await Nt.eraseBlockHash(t)};static listSentOutputs=async t=>Nt.listSentOutputs(t);static listReceivedOutputs=async t=>Nt.listReceivedOutputs(t);static listTxs=async t=>Nt.listTxs(t);static getLatestRev=async t=>Nt.getLatestRev(t);static getLatestRevs=async t=>Nt.getLatestRevs(t);static getIdByRev=async t=>Nt.getIdByRev(t);static query=async t=>Nt.query(t)}class Ct{static update=async t=>rt.update(t);static insertBatch=async t=>rt.insertBatch(t);static deleteAll=async()=>rt.deleteAll();static count=async()=>rt.count();static waitUntilSetup=async(t,e)=>{Z.info(`WorkerId ${t} waiting for master worker to reorg up to ${e} workers...`),await h((async()=>{const s=await rt.count();if(s===e)return!0;throw Z.info(`WorkerId ${t} waiting until setup done. Actual ${s}, numWorkers ${e}`),new Error("Not all workers have reorged")}),{startingDelay:500})};static selectByWorkerId=async t=>rt.selectByWorkerId(t);static setup=async t=>{0===await ct.count()&&(await pt.insert(1),Z.info(`[wid 0 pid: ${process.pid}: registering block sync status on block 1`)),await rt.count()===t?Z.info(`[wid 1 pid: ${process.pid}: all workers have already registered`):await Ct.register(t,await rt.min());const e=await ft.selectAll();if(e.length>0){Z.info(`[wid 0 pid: ${process.pid}: found ${e.length} orphans`);const s=e.map((t=>t.hash));await $t.deleteByHash(s),await kt.eraseBlockHash(s),await Ht.eraseBlockHash(s);const a=await rt.min();const n=Math.min(e[0].height-1,a);Z.info(`[wid 0 pid: ${process.pid}: last block synced from workers ${a}`),Z.info(`[wid 0 pid: ${process.pid}: last valid block at ${e[0].height-1}`),Z.info(`[wid 0 pid: ${process.pid}: resuming at ${n}`),await Ct.register(t,n),await ft.process(e)}};static register=async(t,e)=>{const s=[];let a=Math.max(1,e);for(let e=1;e<=t;e+=1,a+=1)s.push(e,a);Z.info(`[wid 0 pid: ${process.pid}: reorging sync status for ${t} workers...${s}`),await rt.deleteAll(),await rt.insertBatch(s)};static min=async()=>rt.min()}const At=new $({protocol:_,user:A,pass:L,host:P,port:parseInt(D,10)});const Lt=v.promisify($.prototype.createwallet.bind(At));const Pt=v.promisify($.prototype.generateToAddress.bind(At));const Dt=v.promisify($.prototype.getaddressinfo.bind(At));const _t=v.promisify($.prototype.getBlock.bind(At));const Ut=v.promisify($.prototype.getBlockchainInfo.bind(At));const Wt=v.promisify($.prototype.getBlockHash.bind(At));const Ft=v.promisify($.prototype.getRawTransaction.bind(At));const Yt=v.promisify($.prototype.getRawTransaction.bind(At));const Gt=v.promisify($.prototype.getTransaction.bind(At));const Kt=v.promisify($.prototype.getNewAddress.bind(At));const Vt={createwallet:Lt,generateToAddress:Pt,getaddressinfo:Dt,getBlock:_t,getBlockchainInfo:Ut,getBlockHash:Wt,getRawTransaction:Ft,getTransaction:Gt,importaddress:v.promisify($.prototype.importaddress.bind(At)),invalidateBlock:v.promisify($.prototype.invalidateBlock.bind(At)),listunspent:v.promisify($.prototype.listunspent.bind(At)),sendRawTransaction:v.promisify($.prototype.sendRawTransaction.bind(At)),getNewAddress:Kt,sendToAddress:v.promisify($.prototype.sendToAddress.bind(At)),getRawTransactionJSON:Yt};class jt{static async getTransaction(t){const{result:e}=await Vt.getTransaction(t);return e}static async getBulkTransactions(t){return(await Promise.all(t.map((t=>Vt.getRawTransaction(t,0))))).map((t=>t.result))}static async getRawTransaction(t,e){const{result:s}=await Vt.getRawTransaction(t,e);return s}static async getRawTransactionsJSON(t){return{txId:(e=(await Vt.getRawTransactionJSON(t,1)).result).txid,txHex:e.hex,vsize:e.vsize,version:e.version,locktime:e.locktime,ins:e.vin.map((t=>t.coinbase?{coinbase:t.coinbase,sequence:t.sequence}:{txId:t.txid,vout:t.vout,script:t.scriptSig.hex,sequence:t.sequence})),outs:e.vout.map((t=>{let e;return t.scriptPubKey.addresses?[e]=t.scriptPubKey.addresses:e=t.scriptPubKey.address?t.scriptPubKey.address:void 0,{address:e,script:t.scriptPubKey.hex,value:Math.round(1e8*t.value)}}))};var e}static async sendRawTransaction(t){const{result:e,error:s}=await Vt.sendRawTransaction(t);if(s)throw Z.error(s),new Error("Error sending transaction");return e}static getUtxos=async t=>(void 0===(await Vt.getaddressinfo(t)).result.timestamp&&(Z.info(`Importing address: ${t}`),await Vt.importaddress(t,!1)),(await Vt.listunspent(0,999999,[t])).result);static walletSetup=async()=>{if("regtest"===S){if(Z.info(`Node is starting for chain ${T} and network ${S}, \n\n. Starting Wallet setup.`),"LTC"===T){const{result:t}=await Vt.getBlockchainInfo();const e=t.blocks;if(e{try{await Vt.createwallet(U,!1,!1,"",!1,!1)}catch(t){if(t.message.includes("already exists"))return void Z.info(`Wallet ${U} already exists`);Z.error(`Wallet creation failed with error '${t.message}'`)}};static checkBlockchainProgress=async t=>{const e=await h((async()=>{const e=await Vt.getBlockchainInfo();const s=(100*parseFloat(e.result.verificationprogress)).toFixed(4);const{blocks:a}=e.result;if(Z.info(`Zmq. Bitcoind { percentage:${s}%, blocks:${a} }`),parseFloat(e.result.verificationprogress)<=t)throw new Error("Node not ready yet");return e}),{startingDelay:6e4,timeMultiple:1,numOfAttempts:8760});const s=(100*parseFloat(e.result.verificationprogress)).toFixed(4);const a=e.result.blocks;Z.info(`BCN reaches sync end...at { bitcoind.progress:${s}%, bitcoindSyncedHeight:${a} }`)}}class qt{static get=async t=>jt.getTransaction(t);static getRaw=async t=>jt.getBulkTransactions(t);static getRawJSON=async t=>jt.getRawTransactionsJSON(t);static sendRaw=async t=>jt.sendRawTransaction(t);static getUtxos=async t=>jt.getUtxos(t);static parseTransactions=async(t,e,s,a)=>{let n=t;"LTC"===e&&(n=t.filter((t=>"08"!==t.hex.slice(10,12))));const r=[];for(const t of n)try{let{hex:e}=t;e||(e=(await jt.getRawTransaction(t.txid,1)).hex);const s=E.txFromHex({hex:e});s&&r.push(s)}catch(e){Z.error(`[wid ${s} pid: ${process.pid}: failed to parse transaction in block ${a}\n error message: ${e.message}\n transaction: ${JSON.stringify(t)}`)}return r};static walletSetup=async()=>jt.walletSetup()}class Jt{static getTxBatch=async(t,e,s,a=T)=>{const n=[];const r=[];let o;let i;let c=!1;for(;9*n.length<1e4&&3*r.length<1e4;){try{const{result:e}=await Vt.getBlockHash(t);o=e;const{result:s}=await Vt.getBlock(e,2);i=s}catch(s){Z.info(`[wid ${e} pid: ${process.pid}]: waiting to get block hash or block for height: ${t} ${s.message}`),c=!0;break}try{const s=await qt.parseTransactions(i.tx,a,e,t);n.push(...Ht.getOutputs(s,o)),r.push(...kt.getInputs(s,o))}catch(t){Z.error(`[wid ${e} pid: ${process.pid}]: failed to get inputs or outputs for hash: ${o} ${t.message} ${t.stack}`)}t+=s}return{outputs:n,inputs:r,blockToSync:t,atTip:c}};static syncTxs=async(t,e,s)=>{let a=t;for(Z.info(`[wid ${e} pid: ${process.pid}]: starting to sync txs from block: ${a} - numWorkers: ${s}`);;)try{const t=await this.getTxBatch(a,e,s);(t.outputs.length>0||t.inputs.length>0)&&(await Promise.all([Ht.insert(t.outputs),kt.insert(t.inputs)]),await Ct.update({blockToSync:t.blockToSync,workerId:e}),a=t.blockToSync,Z.info(`[wid ${e} pid: ${process.pid}: backfilling up to ${t.blockToSync-1} - #outputs ${t.outputs.length} #inputs ${t.inputs.length} `)),t.atTip&&await ut(2e3)}catch(t){Z.error(`[wid ${e} pid: ${process.pid}]: failed to process block ${a} ${t.message} ${t.stack}`)}};static findOrphans=async t=>{let e;try{const{result:s}=await Vt.getBlock(t,2);e=s}catch(t){return await ut(2e3),[]}if(1===e?.height)return[];const s=await $t.selectByHeight(e.height-1);if(!s)return[];if(e?.previousblockhash===s?.hash)return[];const a=await Jt.findOrphans(e.previousblockhash);const{hash:n,height:r}=s;return[...a,{hash:n,height:r,processed:!1}]};static updateStatus=async(t,e)=>{await pt.update(t);const s=await rt.min();const a=Math.min(s,t);Z.info(`[wid 0 pid: ${process.pid}: reorg detected, resuming at block min(${s},${t}) = ${a}`),await Ct.register(e,a)};static registerOrphans=async(t,e,s)=>{Z.info(`[wid 0 pid: ${process.pid}: detected ${t.length} orphaned blocks resuming at height ${e}`),await ft.insertAll(t),await this.updateStatus(e,s)};static getBlockBatch=async(t,e,s,a)=>{const n=[];for(let r=t;n.lengthparseInt(C||"",10)){const e=await this.findOrphans(a);e.length&&(lt(s),await this.registerOrphans(e,t-e.length,s.length),Z.info(`[wid 0 pid: ${process.pid}: exiting ...`),process.exit(0))}}catch(t){Z.error(`[wid 0 pid: ${process.pid}: ${t.message} error at checking block reorgs ${t.stack}`)}t+=1,e=a}return{blockHashes:n,blockToSync:t,previousDbHash:e}};static syncBlocks=async(t,e,s)=>{let a=t;let n=(await $t.selectByHeight(t-1))?.hash||"";Z.info(`[wid ${e} pid: ${process.pid}]: starting to sync block: ${t} prev hash: ${n}`);const r=Math.floor(3333.3333333333335);for(;;)try{const t=await this.getBlockBatch(a,n,s,r);t.blockHashes.length&&(await pt.update(t.blockToSync),a=t.blockToSync,n=t.previousDbHash,await $t.insertBatch(t.blockHashes),Z.info(`[wip ${e} pid: ${process.pid}: synchronizing up to block num ${t.blockToSync}`)),t.blockHashes.length{await h((()=>st.connect()),{startingDelay:500})})(),0===Zt){Z.info(`[wid ${Zt} pid: ${process.pid}]: connected to the database successfully`),Z.info(`[wid ${Zt} pid: ${process.pid}]: parameters { url: ${H}, chain:${T} network:${S} numWorkers: ${Xt}}`),await Ct.setup(Xt);for(let t=1;t<=Xt;t+=1){Z.info(`[wid ${Zt} pid: ${process.pid}]: spawning worker ${t}`);const e=c("node",["dist/bcn.sync.es.mjs"],{env:{...process.env,BCN_WORKER_ID:`${t}`,BCN_NUM_WORKERS:`${Xt}`},stdio:"inherit"});e.pid||(Z.error(`[wid ${Zt} pid: ${process.pid}]: failed to spawn worker ${t}`),lt(zt),p(1)),zt.push(e.pid)}Z.info(`Spawned workers: ${zt.map((t=>t)).join(", ")}`);const t=await pt.select();await Jt.syncBlocks(t,0,zt)}else{await Ct.waitUntilSetup(Zt,Xt);const t=await Ct.selectByWorkerId(Zt);await Jt.syncTxs(t.blockToSync,t.workerId,Xt)}}catch(t){Z.error(`[wid ${Zt} pid: ${process.pid}]: synchronizing failed with error '${t.message} ${t.stack}'`),lt(zt),p(1)} +import*as t from"@bitcoin-computer/secp256k1";import{networks as e,bufferUtils as s,script as a,crypto as n,address as r,initEccLib as o}from"@bitcoin-computer/nakamotojs";import{availableParallelism as c}from"node:os";import{spawn as i}from"node:child_process";import{exit as p}from"node:process";import l from"dotenv";import u from"winston";import d from"winston-daily-rotate-file";import{backOff as h}from"exponential-backoff";import m from"pg-promise";import y from"pg-monitor";import w from"fs";import{Transaction as g,Computer as E}from"@bitcoin-computer/lib";import $ from"bitcoind-rpc";import v from"util";l.config();const T=process.env.BCN_CHAIN;const S=process.env.BCN_NETWORK;const{BCN_PORT:O}=process.env;process.env,process.env;const{BCN_LOG_LEVEL:f}=process.env;const{BCN_LOG_MAX_FILES:I}=process.env;const{BCN_LOG_MAX_SIZE:k}=process.env;const{BCN_LOG_ZIP:R}=process.env;const{BCN_SHOW_DB_LOGS:b}=process.env;process.env,process.env,process.env,process.env,process.env;const{BCN_NUM_WORKERS:B}=process.env;const{BCN_WORKER_ID:x}=process.env;process.env,process.env;const M=process.env.BCN_QUERY_LIMIT||"1000";const N=process.env.BCN_URL||`http://127.0.0.1:${O}`;const H=process.env.BCN_ENV||"dev";const L=process.env.BCN_ZMQ_ACTIVATION_HEIGHT||"1";const{BITCOIN_RPC_USER:C}=process.env;const{BITCOIN_RPC_PASSWORD:A}=process.env;const{BITCOIN_RPC_HOST:P}=process.env;const{BITCOIN_RPC_PORT:U}=process.env;const{BITCOIN_RPC_PROTOCOL:D}=process.env;const{BITCOIN_DEFAULT_WALLET:_}=process.env;const{POSTGRES_USER:W}=process.env;const{POSTGRES_PASSWORD:F}=process.env;const{POSTGRES_DB:G}=process.env;const{POSTGRES_HOST:Y}=process.env;const{POSTGRES_PORT:K}=process.env;const{POSTGRES_MAX_CONNECTIONS:V}=process.env;const{POSTGRES_IDLE_TIMEOUT_MILLIS:j}=process.env;u.addColors({error:"red",warn:"yellow",info:"green",http:"magenta",debug:"blue"});const q=u.format.combine(u.format.colorize(),u.format.timestamp({format:"YYYY-MM-DD HH:mm:ss:ms"}),u.format.json(),u.format.printf((t=>`${t.timestamp} [${t.level.slice(5).slice(0,-5)}] ${t.message}`)));const J=u.format.combine(u.format.colorize(),u.format.timestamp({format:"MM-DD-YYYY HH:mm:ss"}),u.format.printf((t=>`${t.timestamp} ${t.level} ${t.message}`)));const z={zippedArchive:"true"===R,maxSize:k,maxFiles:I,dirname:"logs"};const X=[];const Z={0:"error",1:"warn",2:"info",3:"http",4:"debug"}[f];"dev"===H&&X.push(new u.transports.Console({format:J,level:Z})),X.push(new d({filename:"logs/application-%DATE%.log",datePattern:"YYYY-MM-DD",level:Z,...z})),X.push(new d({filename:"logs/error-%DATE%.log",datePattern:"YYYY-MM-DD",level:"error",...z}));const Q=u.createLogger({levels:{error:0,warn:1,info:2,http:3,debug:4},format:q,transports:X,exceptionHandlers:[new u.transports.File({filename:"logs/exceptions.log"})],rejectionHandlers:[new u.transports.File({filename:"logs/rejections.log"})]});l.config();const{version:tt}=JSON.parse(w.readFileSync("package.json","utf8"));tt||process.env.BCN_SERVER_VERSION;const et=parseInt(process.env.MWEB_HEIGHT||"",10)||432;const st={error:(t,e)=>{if(e.cn){const{host:s,port:a,database:n,user:r,password:o}=e.cn;Q.debug(`Waiting for db to start { message:${t.message} host:${s}, port:${a}, database:${n}, user:${r}, password: ${o}`)}},noWarnings:!0};"true"===b&&(y.isAttached()?y.detach():(y.attach(st),y.setTheme("matrix")));const at=m(st)({host:Y,port:parseInt(K,10),database:G,user:W,password:F,max:parseInt(V,10),allowExitOnIdle:!0,idleTimeoutMillis:parseInt(j,10)});const{PreparedStatement:nt}=m;class rt{static async selectByWorkerId(t){const e=new nt({name:`TxStatus.select.${Math.random()}`,text:'SELECT "blockToSync", "workerId" FROM "TxStatus" WHERE "workerId" = $1',values:[t]});return at.oneOrNone(e)}static async update({blockToSync:t,workerId:e}){const s=new nt({name:`TxStatus.update.${Math.random()}`,text:'UPDATE "TxStatus" SET "blockToSync" = $1 WHERE "workerId" = $2',values:[t,e]});await at.any(s)}static async count(){const t=new nt({name:`TxStatus.count.${Math.random()}`,text:'SELECT COUNT(*) FROM "TxStatus"'});const e=await at.oneOrNone(t);return parseInt(e?.count,10)||0}static async min(){const t=new nt({name:`TxStatus.min.${Math.random()}`,text:'SELECT MIN("blockToSync") FROM "TxStatus"'});const e=await at.oneOrNone(t);return parseInt(e?.min,10)||0}static async deleteAll(){const t=new nt({name:`TxStatus.delete.${Math.random()}`,text:'DELETE FROM "TxStatus"'});await at.any(t)}static async insertBatch(t){const e=[];for(let s=1;s<=t.length;s+=2)e.push(`($${s}, $${s+1})`);const s=e.join(",");const a=new nt({name:`TxStatus.reorg.${Math.random()}`,text:`INSERT INTO "TxStatus"("workerId", "blockToSync") VALUES ${s}`,values:t});await at.any(a)}}class ot{static async selectByWorkerId(t){return rt.selectByWorkerId(t)}static async update(t){await rt.update(t)}static async count(){return rt.count()}static async insertBatch(t){await rt.insertBatch(t)}static async min(){return rt.min()}static async deleteAll(){await rt.deleteAll()}}const{PreparedStatement:ct}=m;class it{static async select(){const t=new ct({name:`BlockStatus.select.${Math.random()}`,text:'SELECT "blockToSync" FROM "BlockStatus"'});const e=await at.oneOrNone(t);return parseInt(e?.blockToSync,10)||null}static async update(t){const e=new ct({name:`BlockStatus.update.${Math.random()}`,text:'UPDATE "BlockStatus" SET "blockToSync" = $1',values:[t]});await at.any(e)}static async insert(t){const e=new ct({name:`BlockStatus.insert.${Math.random()}`,text:'INSERT INTO "BlockStatus"("blockToSync") VALUES ($1)',values:[t]});await at.any(e)}static async count(){const t=new ct({name:`BlockStatus.count.${Math.random()}`,text:'SELECT COUNT(*) FROM "BlockStatus"'});const e=await at.oneOrNone(t);return parseInt(e?.count,10)||0}static async delete(){const t=new ct({name:`BlockStatus.delete.${Math.random()}`,text:'DELETE FROM "BlockStatus"'});await at.any(t)}}class pt{static async select(){return it.select()}static async update(t){await it.update(t)}static async insert(t){await it.insert(t)}static async count(){return it.count()}static async delete(){await it.delete()}}class lt{static update=async t=>pt.update(t);static select=async()=>pt.select();static insert=async t=>pt.insert(t);static count=async()=>pt.count();static delete=async()=>pt.delete()}function ut(t){t.forEach((t=>{Q.info(`[wid 0 pid: ${process.pid}]: killing worker ${t}`);try{Q.info(`[wid 0 pid: ${process.pid}]: killing worker (SIGTERM) ${t}: ${process.kill(t,"SIGTERM")}`),process.kill(t,"SIGTERM")}catch(e){Q.info(`[wid 0 pid: ${process.pid}]: killing worker ${t}: ${process.kill(t)}`),process.kill(t,"SIGKILL")}}))}const dt=t=>new Promise((e=>{setTimeout(e,t)}));const ht=(t,e)=>{const s=[];for(let a=0;a{const e=[];for(let s=1;s<=t;s+=3){const t=`($${s},$${s+1},$${s+2})`;e.push(t)}return e.join(",")};const yt=t=>{const e=[];for(let s=1;s<=t;s+=9){const t=`($${s},$${s+1},$${s+2},$${s+3},$${s+4},$${s+5},$${s+6},$${s+7},$${s+8})`;e.push(t)}return e.join(",")};const wt=t=>{try{return t()}catch{return null}};const{PreparedStatement:gt}=m;class Et{static async selectByHeight(t){const e=new gt({name:`Block.select.${Math.random()}`,text:'SELECT "hash", "height", "previousHash" FROM "Block" WHERE "height" = $1',values:[t]});return at.oneOrNone(e)}static async insert(t){const e=new gt({name:`Block.insert.${Math.random()}`,text:'INSERT INTO "Block" ("hash", "height", "previousHash") VALUES ($1, $2, $3)',values:[t.hash,t.height,t.previousHash]});await at.none(e)}static async insertBatch(t){const e=t.flatMap((({hash:t,height:e,previousHash:s})=>[t,e,s]));const s=new gt({name:`Block.insertBatch.${Math.random()}`,text:`INSERT INTO "Block" ("hash", "height", "previousHash") VALUES ${mt(e.length)}`,values:e});await at.none(s)}static async deleteAll(){const t=new gt({name:`Block.delete.${Math.random()}`,text:'DELETE FROM "Block"',values:[]});await at.none(t)}static async deleteByHash(t){const e=t.map((t=>t)).join("', '");const s=new gt({name:`Block.deleteByHash.${Math.random()}`,text:`DELETE FROM "Block" WHERE "hash" IN ('${e}')`});await at.none(s)}}class $t{static selectByHeight=async t=>Et.selectByHeight(t);static insert=async t=>Et.insert(t);static insertBatch=async t=>Et.insertBatch(t);static deleteAll=async()=>Et.deleteAll();static deleteByHash=async t=>Et.deleteByHash(t);static waitForDbBlockHash=async(t,e)=>(await h((async()=>{let s;try{s=await Et.selectByHeight(t)}catch(s){throw Q.debug(`[wid ${e} pid: ${process.pid}]: waiting for DB to get block ${t} ...`),s}return s}),{startingDelay:1e4,timeMultiple:1,numOfAttempts:720})).hash}class vt{static selectByHeight=async t=>$t.selectByHeight(t);static insert=async t=>$t.insert(t);static insertBatch=async t=>$t.insertBatch(t);static deleteAll=async()=>$t.deleteAll();static deleteByHash=async t=>$t.deleteByHash(t);static waitForDbBlockHash=async(t,e)=>$t.waitForDbBlockHash(t,e)}const{PreparedStatement:Tt}=m;class St{static async selectAll(){const t=new Tt({name:`Orphan.select.${Math.random()}`,text:'SELECT * FROM "Orphan"'});return at.any(t)}static async insertAll(t){const e=t.map((t=>`('${t.hash}',${t.height}, ${t.processed})`)).join(",");const s=new Tt({name:`Orphan.insert.${Math.random()}`,text:`INSERT INTO "Orphan" (hash, height, processed) VALUES ${e}`});await at.none(s)}static async process(t){const e=t.map((t=>t.height)).join(",");const s=new Tt({name:`Orphan.process.${Math.random()}`,text:`UPDATE "Orphan" SET processed = true WHERE height IN (${e})`});await at.none(s)}}class Ot{static selectAll=async()=>St.selectAll();static insertAll=async t=>St.insertAll(t);static process=async t=>St.process(t)}class ft{static selectAll=async()=>Ot.selectAll();static insertAll=async t=>Ot.insertAll(t);static process=async t=>Ot.process(t)}const{PreparedStatement:It}=m;class kt{static async select(t){const e=new It({name:`Input.select.${Math.random()}`,text:'SELECT "outputSpent", "spendingInput", "blockHash" FROM "Input" WHERE "outputSpent" = $1',values:[t]});return at.any(e)}static async insert(t){await Promise.all(ht(t,3333).map((t=>{const e=t.flatMap((({outputSpent:t,spendingInput:e,blockHash:s})=>[t,e,s]));return at.none(new It({name:`Input.insert.${Math.random()}`,text:`INSERT INTO "Input"("outputSpent", "spendingInput", "blockHash") VALUES ${mt(e.length)} \n ON CONFLICT ("spendingInput") \n DO UPDATE SET "blockHash" = COALESCE("Input"."blockHash", EXCLUDED."blockHash")`,values:e}))})))}static async eraseBlockHash(t){await Promise.all(ht(t,1e4).map((t=>{const e=t.join("','");return at.none(new It({name:`Input.eraseBlockHash.${Math.random()}`,text:`UPDATE "Input" SET "blockHash" = NULL WHERE "blockHash" IN ('${e}')`}))})))}static async count(t){const e=t.map((t=>t.outputSpent));const s=new It({name:`Input.belong.${Math.random()}`,text:'SELECT count(*) FROM "Input" WHERE "outputSpent" LIKE ANY ($1)',values:[[e]]});const a=await at.oneOrNone(s);return parseInt(a?.count,10)||0}}class Rt{static async select(t){return kt.select(t)}static async insert(t){return kt.insert(t)}static async eraseBlockHash(t){return kt.eraseBlockHash(t)}}class bt{static insert=async t=>{await Rt.insert(t)};static getInputs=(t,e=null)=>t.flatMap((({ins:t,txId:e})=>t.map(((t,s)=>({input:t,index:s,txId:e}))))).filter((({input:t})=>!g.isCoinbaseHash(t.hash))).map((({input:t,index:a,txId:n})=>{return{outputSpent:`${r=t.hash,s.reverseBuffer(Buffer.from(r)).toString("hex")}:${t.index}`,spendingInput:`${n}:${a}`,blockHash:e};var r}));static select=async t=>Rt.select(t);static eraseBlockHash=async t=>{await Rt.eraseBlockHash(t)}}function Bt(t){if(!function(t){return/^[0-9A-Fa-f]{64}:\d+$/.test(t)}(t))throw new Error("Invalid rev")}const{PreparedStatement:xt}=m;class Mt{static async listSentOutputs(t){const e=new xt({name:`Output.listSentTxs.${Math.random()}`,text:'SELECT "Input"."spendingInput" AS "output", "Output"."satoshis" AS "amount"\n FROM "Output" INNER JOIN "Input" ON "Output".rev = "Input"."outputSpent" \n WHERE "Output"."address" = $1',values:[t]});return(await at.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listReceivedOutputs(t){const e=new xt({name:`Output.listReceivedTxs.${Math.random()}`,text:'SELECT "Output"."rev" as "output", "Output"."satoshis" as "amount" FROM "Output" WHERE "address" = $1',values:[t]});return(await at.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listTxs(t){const e=new xt({name:`Output.listTxs.${Math.random()}`,text:'WITH\n -- List all txs sent from a given address\n SENT AS (\n SELECT split_part("Input"."spendingInput",\':\',1) as "txId", SUM("Output".satoshis) as "satoshis"\n FROM "Output" INNER JOIN "Input" ON "Output".rev = "Input"."outputSpent"\n WHERE "Output".address = $1\n GROUP BY split_part("Input"."spendingInput",\':\',1)\n ),\n -- List all tx received from a given address\n RECEIVED AS (\n SELECT SPLIT_PART("Output"."rev",\':\',1) as "txId", SUM("Output"."satoshis") as "satoshis" \n FROM "Output" \n WHERE "address" = $1\n GROUP BY "txId"\n )\n\n SELECT\n RECEIVED."txId", \n coalesce(SENT."satoshis", 0) as "inputsSatoshis", \n coalesce(RECEIVED."satoshis", 0) as "outputsSatoshis", \n coalesce(RECEIVED."satoshis",0) - coalesce(SENT."satoshis",0) as "satoshis"\n FROM\n SENT RIGHT JOIN RECEIVED ON SENT."txId" = RECEIVED."txId";',values:[t]});const s=(await at.any(e)).map((t=>({...t,inputsSatoshis:parseInt(t.inputsSatoshis,10)||0,outputsSatoshis:parseInt(t.outputsSatoshis,10)||0,satoshis:parseInt(t.satoshis,10)||0})));return{sentTxs:s.filter((t=>t.satoshis<0)).map((t=>({...t,satoshis:Math.abs(t.satoshis)}))),receivedTxs:s.filter((t=>t.satoshis>=0))}}static async select(t){const e=new xt({name:`Output.select.${Math.random()}`,text:'SELECT "address", "satoshis", "asm", "rev", "hash", "mod", "isTbcOutput", "previous", "blockHash" FROM "Output" WHERE "address" = $1',values:[t]});return at.any(e)}static async insert(t){await Promise.all(ht(t,1111).map((t=>{const e=t.flatMap((({rev:t,address:e,satoshis:s,asm:a,isTbcOutput:n,mod:r,previous:o,hash:c,blockHash:i})=>[t,e,s,a,n,r,o,c,i]));return at.none(new xt({name:`Output.insert.${Math.random()}`,text:`INSERT INTO "Output"("rev", "address", "satoshis", "asm", "isTbcOutput",\n "mod", "previous", "hash", "blockHash") VALUES ${yt(e.length)} ON CONFLICT ("rev") \n DO UPDATE SET "blockHash" = COALESCE("Output"."blockHash", EXCLUDED."blockHash")`,values:e}))})))}static async eraseBlockHash(t){await Promise.all(ht(t,1e4).map((t=>{const e=t.join("','");return at.none(new xt({name:`Output.eraseBlockHash.${Math.random()}`,text:`UPDATE "Output" SET "blockHash" = NULL WHERE "blockHash" IN ('${e}')`}))})))}static async getIdByRev(t){const e=new xt({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON r."previous" = o."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=(await at.any(e)).filter((t=>null===t.previous));return s[0]?.rev}static async getIdsByRevs(t){return Promise.all(t.map((t=>this.getIdByRev(t))))}static async getLatestRev(t){const e=new xt({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON o."previous" = r."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=await at.any(e);const a=Object.fromEntries(s.map((t=>[t.previous,t.rev])));let n=t;for(;a[n];)n=a[n];return n}static async getLatestRevs(t){return Promise.all(t.map(this.getLatestRev))}static async getIdsByMod(t){const e=new xt({name:`Output.getIdsByMod.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1',values:[t]});return(await at.any(e)).map((t=>t.rev))}static sqlSuffix(t,e,s){let a="";return s&&(a+=` order by "timestamp" ${s}`),a+=` limit ${t||M}`,e&&(a+=` offset ${e}`),a}static async getUnspentRevsByMod(t,e,s,a){const n=await this.getIdsByMod(t);const r=await this.getLatestRevs(n);const o=new xt({name:`Output.getUnspentRevsByMod.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(e,s,a)}`,values:[r]});return(await at.any(o)).map((t=>t.rev))}static async getUnspentRevsByPublicKey(t,e,s,a){const n=new xt({name:`Output.getUnspentRevsByPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE asm LIKE '%' || $1 || '%' AND "isTbcOutput" = true \n AND NOT EXISTS (SELECT 1 FROM "Input" ip WHERE "ip"."outputSpent" = "Output"."rev") \n ${this.sqlSuffix(e,s,a)}`,values:[t]});return(await at.any(n)).map((t=>t.rev))}static async getUnspentRevsByModAndPublicKey(t,e,s,a,n){const r=await this.getUnspentRevsByPublicKey(e,s,a,n);const o=await this.getIdsByRevs(r);const c=new xt({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1 AND "rev" = ANY($2)',values:[t,o]});const i=(await at.any(c)).map((t=>t.rev));const p=await this.getLatestRevs(i);const l=new xt({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(s,a,n)}`,values:[p]});return(await at.any(l)).map((t=>t.rev))}static async getUnspentTbcOutputs(t,e,s){const a=new xt({name:`Output.getUnspentTbcOutputs.${Math.random()}`,text:`SELECT "rev", "address", "satoshis", "asm", "timestamp"\n FROM "Output" WHERE "isTbcOutput" = true AND NOT EXISTS\n (SELECT 1 FROM "Input" ip WHERE "ip"."outputSpent" = "Output"."rev") ${this.sqlSuffix(t,e,s)}`});return(await at.any(a)).map((t=>t.rev))}static async query(t){const{publicKey:e,limit:s,offset:a,ids:n,mod:r,order:o}=t;const c=parseInt(M||"",10);if(s&&parseInt(s||"",10)>c||n&&n.length>c)throw new Error(`Can't fetch more than ${M} revs.`);if(o&&"ASC"!==o&&"DESC"!==o)throw new Error("Invalid order. Should be ASC or DESC.");return n?(n.map(Bt),this.getLatestRevs(n)):r&&!e?this.getUnspentRevsByMod(r,s,a,o):!r&&e?this.getUnspentRevsByPublicKey(e,s,a,o):r&&e?this.getUnspentRevsByModAndPublicKey(r,e,s,a,o):this.getUnspentTbcOutputs(s,a,o)}static async selectNext(t){const e=new xt({name:`Output.selectNext.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "previous" = $1',values:[t]});return at.any(e)}static async selectPrev(t){const e=new xt({name:`Output.selectPrev.${Math.random()}`,text:'SELECT "previous" FROM "Output" WHERE "rev" = $1',values:[t]});return at.any(e)}}class Nt{static async select(t){return Mt.select(t)}static async insert(t){return Mt.insert(t)}static async eraseBlockHash(t){return Mt.eraseBlockHash(t)}static async listSentOutputs(t){return Mt.listSentOutputs(t)}static async listReceivedOutputs(t){return Mt.listReceivedOutputs(t)}static async listTxs(t){return Mt.listTxs(t)}static async getLatestRev(t){return Mt.getLatestRev(t)}static async getLatestRevs(t){return Mt.getLatestRevs(t)}static async getIdByRev(t){return Mt.getIdByRev(t)}static async query(t){return Mt.query(t)}static async selectNext(t){return Mt.selectNext(t)}static async selectPrev(t){return Mt.selectPrev(t)}}const Ht=function(t=T,s=S){switch(t){case"BTC":switch(s){case"mainnet":return e.bitcoin;case"testnet":return e.testnet;case"regtest":return e.regtest;default:throw new Error(`Invalid network ${s}`)}case"LTC":switch(s){case"mainnet":return e.litecoin;case"testnet":return e.litecointestnet;case"regtest":return e.litecoinregtest;default:throw new Error(`Invalid network ${s}`)}case"PEPE":switch(s){case"mainnet":return e.pepecoin;case"testnet":return e.pepecointestnet;case"regtest":return e.pepecoinregtest;default:throw new Error(`Invalid network ${s}`)}case"DOGE":switch(s){case"mainnet":return e.dogecoin;case"testnet":return e.dogecointestnet;case"regtest":return e.dogecoinregtest;default:throw new Error(`Invalid network ${s}`)}default:throw new Error(`Invalid chain ${t}`)}}(T,S);class Lt{static insert(t){return Nt.insert(t)}static getOutputs=(t,e=null)=>t.flatMap((({outs:t,txId:s,zip:o,onChainMetaData:c})=>{const{exp:i="",mod:p=""}=c;return t.map((({script:t,value:c},l)=>{const u=lr.fromOutputScript(t,Ht))),satoshis:Math.round(c),asm:a.toASM(t),isTbcOutput:u,mod:u?p:"",previous:u?o[l][0]:null,hash:u?n.sha256(Buffer.from(i)).toString("hex"):null,blockHash:e}}))}));static select=async t=>Nt.select(t);static eraseBlockHash=async t=>{await Nt.eraseBlockHash(t)};static listSentOutputs=async t=>Nt.listSentOutputs(t);static listReceivedOutputs=async t=>Nt.listReceivedOutputs(t);static listTxs=async t=>Nt.listTxs(t);static getLatestRev=async t=>Nt.getLatestRev(t);static getLatestRevs=async t=>Nt.getLatestRevs(t);static getIdByRev=async t=>Nt.getIdByRev(t);static query=async t=>Nt.query(t)}class Ct{static update=async t=>ot.update(t);static insertBatch=async t=>ot.insertBatch(t);static deleteAll=async()=>ot.deleteAll();static count=async()=>ot.count();static waitUntilSetup=async(t,e)=>{Q.debug(`WorkerId ${t} waiting for master worker to reorg up to ${e} workers...`),await h((async()=>{const s=await ot.count();if(s===e)return!0;throw Q.info(`WorkerId ${t} waiting until setup done. Actual ${s}, numWorkers ${e}`),new Error("Not all workers have reorged")}),{startingDelay:500})};static selectByWorkerId=async t=>ot.selectByWorkerId(t);static setup=async t=>{0===await pt.count()&&(await lt.insert(1),Q.info(`[wid 0 pid: ${process.pid}: registering block sync status on block 1`)),await ot.count()===t?Q.debug(`[wid 1 pid: ${process.pid}: all workers have already registered`):await Ct.register(t,await ot.min());const e=await ft.selectAll();if(e.length>0){Q.info(`[wid 0 pid: ${process.pid}: found ${e.length} orphans`);const s=e.map((t=>t.hash));await vt.deleteByHash(s),await bt.eraseBlockHash(s),await Lt.eraseBlockHash(s);const a=await ot.min();const n=Math.min(e[0].height-1,a);Q.info(`[wid 0 pid: ${process.pid}: last block synced from workers ${a}`),Q.info(`[wid 0 pid: ${process.pid}: last valid block at ${e[0].height-1}`),Q.info(`[wid 0 pid: ${process.pid}: resuming at ${n}`),await Ct.register(t,n),await ft.process(e)}};static register=async(t,e)=>{const s=[];let a=Math.max(1,e);for(let e=1;e<=t;e+=1,a+=1)s.push(e,a);Q.info(`[wid 0 pid: ${process.pid}: reorging sync status for ${t} workers...${s}`),await ot.deleteAll(),await ot.insertBatch(s)};static min=async()=>ot.min()}const At=new $({protocol:D,user:C,pass:A,host:P,port:parseInt(U,10)});const Pt=v.promisify($.prototype.createwallet.bind(At));const Ut=v.promisify($.prototype.generateToAddress.bind(At));const Dt=v.promisify($.prototype.getaddressinfo.bind(At));const _t=v.promisify($.prototype.getBlock.bind(At));const Wt=v.promisify($.prototype.getBlockchainInfo.bind(At));const Ft=v.promisify($.prototype.getBlockHash.bind(At));const Gt=v.promisify($.prototype.getRawTransaction.bind(At));const Yt=v.promisify($.prototype.getRawTransaction.bind(At));const Kt=v.promisify($.prototype.getTransaction.bind(At));const Vt=v.promisify($.prototype.getNewAddress.bind(At));const jt={createwallet:Pt,generateToAddress:Ut,getaddressinfo:Dt,getBlock:_t,getBlockchainInfo:Wt,getBlockHash:Ft,getRawTransaction:Gt,getTransaction:Kt,importaddress:v.promisify($.prototype.importaddress.bind(At)),invalidateBlock:v.promisify($.prototype.invalidateBlock.bind(At)),listunspent:v.promisify($.prototype.listunspent.bind(At)),sendRawTransaction:v.promisify($.prototype.sendRawTransaction.bind(At)),getNewAddress:Vt,sendToAddress:v.promisify($.prototype.sendToAddress.bind(At)),getRawTransactionJSON:Yt};class qt{static async getTransaction(t){const{result:e}=await jt.getTransaction(t);return e}static async getBulkTransactions(t){return(await Promise.all(t.map((t=>jt.getRawTransaction(t,0))))).map((t=>t.result))}static async getRawTransaction(t,e){const{result:s}=await jt.getRawTransaction(t,e);return s}static async getRawTransactionsJSON(t){return{txId:(e=(await jt.getRawTransactionJSON(t,1)).result).txid,txHex:e.hex,vsize:e.vsize,version:e.version,locktime:e.locktime,ins:e.vin.map((t=>t.coinbase?{coinbase:t.coinbase,sequence:t.sequence}:{txId:t.txid,vout:t.vout,script:t.scriptSig.hex,sequence:t.sequence})),outs:e.vout.map((t=>{let e;return t.scriptPubKey.addresses?[e]=t.scriptPubKey.addresses:e=t.scriptPubKey.address?t.scriptPubKey.address:void 0,{address:e,script:t.scriptPubKey.hex,value:Math.round(1e8*t.value)}}))};var e}static async sendRawTransaction(t){const{result:e,error:s}=await jt.sendRawTransaction(t);if(s)throw Q.error(s),new Error("Error sending transaction");return e}static getUtxos=async t=>(void 0===(await jt.getaddressinfo(t)).result.timestamp&&(Q.info(`Importing address: ${t}`),await jt.importaddress(t,!1)),(await jt.listunspent(0,999999,[t])).result);static walletSetup=async()=>{if("regtest"===S){if(Q.info(`Node is starting for chain ${T} and network ${S}, \n\n. Starting Wallet setup.`),"LTC"===T){const{result:t}=await jt.getBlockchainInfo();const e=t.blocks;if(e{try{await jt.createwallet(_,!1,!1,"",!1,!1)}catch(t){if(t.message.includes("already exists"))return void Q.info(`Wallet ${_} already exists`);Q.warn(`Wallet creation failed with error '${t.message}'`)}};static checkBlockchainProgress=async t=>{const e=await h((async()=>{const e=await jt.getBlockchainInfo();const s=(100*parseFloat(e.result.verificationprogress)).toFixed(4);const{blocks:a}=e.result;if(Q.info(`Zmq. Bitcoind { percentage:${s}%, blocks:${a} }`),parseFloat(e.result.verificationprogress)<=t)throw new Error("Node not ready yet");return e}),{startingDelay:6e4,timeMultiple:1,numOfAttempts:8760});const s=(100*parseFloat(e.result.verificationprogress)).toFixed(4);const a=e.result.blocks;Q.info(`BCN reaches sync end...at { bitcoind.progress:${s}%, bitcoindSyncedHeight:${a} }`)}}class Jt{static get=async t=>qt.getTransaction(t);static getRaw=async t=>qt.getBulkTransactions(t);static getRawJSON=async t=>qt.getRawTransactionsJSON(t);static sendRaw=async t=>qt.sendRawTransaction(t);static getUtxos=async t=>qt.getUtxos(t);static parseTransactions=async(t,e,s,a)=>{let n=t;"LTC"===e&&(n=t.filter((t=>"08"!==t.hex.slice(10,12))));const r=[];for(const t of n)try{let{hex:e}=t;e||(e=(await qt.getRawTransaction(t.txid,1)).hex);const s=E.txFromHex({hex:e});s&&r.push(s)}catch(e){Q.error(`[wid ${s} pid: ${process.pid}: failed to parse transaction in block ${a}\n error message: ${e.message}\n transaction: ${JSON.stringify(t)}`)}return r};static walletSetup=async()=>qt.walletSetup()}class zt{static getTxBatch=async(t,e,s,a=T)=>{const n=[];const r=[];let o;let c;let i=!1;for(;9*n.length<1e4&&3*r.length<1e4;){try{const{result:e}=await jt.getBlockHash(t);o=e;const{result:s}=await jt.getBlock(e,2);c=s}catch(s){Q.debug(`[wid ${e} pid: ${process.pid}]: waiting to get block hash or block for height: ${t} ${s.message}`),i=!0;break}try{const s=await Jt.parseTransactions(c.tx,a,e,t);n.push(...Lt.getOutputs(s,o)),r.push(...bt.getInputs(s,o))}catch(t){Q.error(`[wid ${e} pid: ${process.pid}]: failed to get inputs or outputs for hash: ${o} ${t.message} ${t.stack}`)}t+=s}return{outputs:n,inputs:r,blockToSync:t,atTip:i}};static syncTxs=async(t,e,s)=>{let a=t;for(Q.debug(`[wid ${e} pid: ${process.pid}]: starting to sync txs from block: ${a} - numWorkers: ${s}`);;)try{const t=await this.getTxBatch(a,e,s);(t.outputs.length>0||t.inputs.length>0)&&(await Promise.all([Lt.insert(t.outputs),bt.insert(t.inputs)]),await Ct.update({blockToSync:t.blockToSync,workerId:e}),a=t.blockToSync,Q.debug(`[wid ${e} pid: ${process.pid}: backfilling up to ${t.blockToSync-1} - #outputs ${t.outputs.length} #inputs ${t.inputs.length} `)),t.atTip&&await dt(2e3)}catch(t){Q.error(`[wid ${e} pid: ${process.pid}]: failed to process block ${a} ${t.message} ${t.stack}`)}};static findOrphans=async t=>{let e;try{const{result:s}=await jt.getBlock(t,2);e=s}catch(t){return await dt(2e3),[]}if(1===e?.height)return[];const s=await vt.selectByHeight(e.height-1);if(!s)return[];if(e?.previousblockhash===s?.hash)return[];const a=await zt.findOrphans(e.previousblockhash);const{hash:n,height:r}=s;return[...a,{hash:n,height:r,processed:!1}]};static updateStatus=async(t,e)=>{await lt.update(t);const s=await ot.min();const a=Math.min(s,t);Q.warn(`[wid 0 pid: ${process.pid}: reorg detected, resuming at block min(${s},${t}) = ${a}`),await Ct.register(e,a)};static registerOrphans=async(t,e,s)=>{Q.warn(`[wid 0 pid: ${process.pid}: detected ${t.length} orphaned blocks resuming at height ${e}`),await ft.insertAll(t),await this.updateStatus(e,s)};static getBlockBatch=async(t,e,s,a)=>{const n=[];for(let r=t;n.lengthparseInt(L||"",10)){const e=await this.findOrphans(a);e.length&&(ut(s),await this.registerOrphans(e,t-e.length,s.length),Q.warning(`[wid 0 pid: ${process.pid}: exiting ...`),process.exit(0))}}catch(t){Q.error(`[wid 0 pid: ${process.pid}: ${t.message} error at checking block reorgs ${t.stack}`)}t+=1,e=a}return{blockHashes:n,blockToSync:t,previousDbHash:e}};static syncBlocks=async(t,e,s)=>{let a=t;let n=(await vt.selectByHeight(t-1))?.hash||"";Q.debug(`[wid ${e} pid: ${process.pid}]: starting to sync block: ${t} prev hash: ${n}`);const r=Math.floor(3333.3333333333335);for(;;)try{const t=await this.getBlockBatch(a,n,s,r);t.blockHashes.length&&(await lt.update(t.blockToSync),a=t.blockToSync,n=t.previousDbHash,await vt.insertBatch(t.blockHashes),Q.debug(`[wip ${e} pid: ${process.pid}: synchronizing up to block num ${t.blockToSync}`)),t.blockHashes.length{await h((()=>at.connect()),{startingDelay:500})})(),0===Qt){Q.info(`[wid ${Qt} pid: ${process.pid}]: connected to the database successfully`),Q.info(`[wid ${Qt} pid: ${process.pid}]: parameters { url: ${N}, chain:${T} network:${S} numWorkers: ${Zt}}`),await Ct.setup(Zt);for(let t=1;t<=Zt;t+=1){Q.debug(`[wid ${Qt} pid: ${process.pid}]: spawning worker ${t}`);const e=i("node",["dist/bcn.sync.es.mjs"],{env:{...process.env,BCN_WORKER_ID:`${t}`,BCN_NUM_WORKERS:`${Zt}`},stdio:"inherit"});e.pid||(Q.error(`[wid ${Qt} pid: ${process.pid}]: failed to spawn worker ${t}`),ut(Xt),p(1)),Xt.push(e.pid)}Q.debug(`Spawned workers: ${Xt.map((t=>t)).join(", ")}`);const t=await lt.select();await zt.syncBlocks(t,0,Xt)}else{Q.info(`[wid ${Qt} pid: ${process.pid}]: worker ${Qt} starting`),await Ct.waitUntilSetup(Qt,Zt);const t=await Ct.selectByWorkerId(Qt);await zt.syncTxs(t.blockToSync,t.workerId,Zt)}}catch(t){Q.error(`[wid ${Qt} pid: ${process.pid}]: synchronizing failed with error '${t.message} ${t.stack}'`),ut(Xt),p(1)} From e9740c2f97ce2d9f9ee83b5f177242b4ae11b8ad Mon Sep 17 00:00:00 2001 From: ltardivo Date: Tue, 14 Jan 2025 20:22:57 -0300 Subject: [PATCH 2/2] Adds parameter changes to docs --- packages/docs/node.md | 18 +++++++++++------- packages/node/README.md | 18 +++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/docs/node.md b/packages/docs/node.md index c3df26f13..c384c49af 100644 --- a/packages/docs/node.md +++ b/packages/docs/node.md @@ -164,7 +164,7 @@ You can configure several options by editing the `.env` file. ```bash -# Chain: BTC, LTC or PEPE +# Chain: BTC, LTC, DOGE or PEPE BCN_CHAIN='LTC' # Network: mainnet, testnet, or regtest BCN_NETWORK='regtest' @@ -195,11 +195,13 @@ BITCOIN_RPC_PROTOCOL='http' # Default wallet name BITCOIN_DEFAULT_WALLET='defaultwallet' +# Bitcoin Computer Node (BCN) Settings # Port for Bitcoin Computer Node BCN_PORT='1031' -# Setup the environment to prod or dev -BCN_ENV=dev +# Enable to launch with fixed number of parallel workers +# BCN_NUM_WORKERS='6' + BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -211,14 +213,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -229,8 +234,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings diff --git a/packages/node/README.md b/packages/node/README.md index 362ebd8b4..320465104 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -184,7 +184,7 @@ You can configure several options by editing the `.env` file. ```bash -# Chain: BTC, LTC or PEPE +# Chain: BTC, LTC, DOGE or PEPE BCN_CHAIN='LTC' # Network: mainnet, testnet, or regtest BCN_NETWORK='regtest' @@ -215,11 +215,13 @@ BITCOIN_RPC_PROTOCOL='http' # Default wallet name BITCOIN_DEFAULT_WALLET='defaultwallet' +# Bitcoin Computer Node (BCN) Settings # Port for Bitcoin Computer Node BCN_PORT='1031' -# Setup the environment to prod or dev -BCN_ENV=dev +# Enable to launch with fixed number of parallel workers +# BCN_NUM_WORKERS='6' + BCN_ZMQ_URL='tcp://node:28332' BCN_ZMQ_PORT='28332' # Height of the block at which the zmq connection should start @@ -231,14 +233,17 @@ BCN_URL='http://127.0.0.1:1031' # Allowed RPC Methods BCN_ALLOWED_RPC_METHODS='^get|^gen|^send|^lis' +# Setup the environment to 'prod' (no console logs) or 'dev' +BCN_ENV='dev' + # Winston Logger Settings -# Debug mode +# Log levels # 0: Error logs only # 1: Error and warning logs # 2: Error, warning and info logs # 3: Error, warning, info and http logs # 4: Error, warning, info, http and debug logs -BCN_DEBUG_MODE='4' +BCN_LOG_LEVEL='2' # Maximum number of logs to keep. If not set, no logs will be removed. This can be # a number of files or number of days. If using days, add 'd' as the suffix. BCN_LOG_MAX_FILES='14d' @@ -249,8 +254,7 @@ BCN_LOG_MAX_SIZE='20m' # A boolean to define whether or not to gzip archived log files. BCN_LOG_ZIP='false' -# Show logs attached to the Console transport. -BCN_SHOW_CONSOLE_LOGS='true' +# Show logs at db service BCN_SHOW_DB_LOGS='false' # Rate Limiting Settings