diff --git a/packages/node/chain-setup/btc-mainnet/bitcoin.conf b/packages/node/chain-setup/btc-mainnet/bitcoin.conf deleted file mode 100644 index f3090782b..000000000 --- a/packages/node/chain-setup/btc-mainnet/bitcoin.conf +++ /dev/null @@ -1,22 +0,0 @@ -dbcache=4000 -txindex=1 - -# [rpc] -# Accept command line and JSON-RPC commands. -server=1 -# Username for JSON-RPC connections -rpcauth=bcn-admin:c71460f0f08e4eeec90e033c04f7bb82$c36e8561d46abbf3bf13da6b88612c19d758d46d02c45cd2716f06a13ec407af -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0 - -printtoconsole=1 - -# [wallet] -# Do not load the wallet and disable wallet RPC calls. -disablewallet=1 - -zmqpubhashtx=tcp://0.0.0.0:28332 -zmqpubrawtx=tcp://0.0.0.0:28332 - -# Rpc work queue increase -rpcworkqueue=512 diff --git a/packages/node/chain-setup/btc-mainnet/docker-compose-local-btc-mainnet.yml b/packages/node/chain-setup/btc-mainnet/docker-compose-local-btc-mainnet.yml index cc5033871..d9be92796 100644 --- a/packages/node/chain-setup/btc-mainnet/docker-compose-local-btc-mainnet.yml +++ b/packages/node/chain-setup/btc-mainnet/docker-compose-local-btc-mainnet.yml @@ -1,4 +1,3 @@ -version: "3" services: db: env_file: .env diff --git a/packages/node/chain-setup/btc-regtest/docker-compose-local-btc-regtest.yml b/packages/node/chain-setup/btc-regtest/docker-compose-local-btc-regtest.yml index c82ed8a69..1ee2bcf7c 100644 --- a/packages/node/chain-setup/btc-regtest/docker-compose-local-btc-regtest.yml +++ b/packages/node/chain-setup/btc-regtest/docker-compose-local-btc-regtest.yml @@ -1,4 +1,3 @@ -version: "3" services: db: env_file: .env diff --git a/packages/node/chain-setup/btc-testnet/docker-compose-local-btc-testnet.yml b/packages/node/chain-setup/btc-testnet/docker-compose-local-btc-testnet.yml index 7bb9ef977..0cb8e4c99 100644 --- a/packages/node/chain-setup/btc-testnet/docker-compose-local-btc-testnet.yml +++ b/packages/node/chain-setup/btc-testnet/docker-compose-local-btc-testnet.yml @@ -1,4 +1,3 @@ -version: "3" services: db: env_file: .env diff --git a/packages/node/chain-setup/ltc-mainnet/docker-compose-local-ltc-mainnet.yml b/packages/node/chain-setup/ltc-mainnet/docker-compose-local-ltc-mainnet.yml index 64e89c329..9c66a5b19 100644 --- a/packages/node/chain-setup/ltc-mainnet/docker-compose-local-ltc-mainnet.yml +++ b/packages/node/chain-setup/ltc-mainnet/docker-compose-local-ltc-mainnet.yml @@ -1,4 +1,3 @@ -version: "3" services: db: env_file: .env @@ -71,7 +70,7 @@ services: - WORKER_ID=${WORKER_ID} - NUM_WORKERS=${NUM_WORKERS} - SYNC_NON_STANDARD=${SYNC_NON_STANDARD} - - BC_START_HEIGHT=2413791 + - BC_START_HEIGHT=${BC_START_HEIGHT} depends_on: - db - node diff --git a/packages/node/chain-setup/ltc-regtest/docker-compose-local-ltc-regtest.yml b/packages/node/chain-setup/ltc-regtest/docker-compose-local-ltc-regtest.yml index ad34c73c7..1dca12a69 100644 --- a/packages/node/chain-setup/ltc-regtest/docker-compose-local-ltc-regtest.yml +++ b/packages/node/chain-setup/ltc-regtest/docker-compose-local-ltc-regtest.yml @@ -1,4 +1,3 @@ -version: "3" services: db: env_file: .env diff --git a/packages/node/chain-setup/ltc-testnet/docker-compose-local-ltc-testnet.yml b/packages/node/chain-setup/ltc-testnet/docker-compose-local-ltc-testnet.yml index 74340cb25..01af20329 100644 --- a/packages/node/chain-setup/ltc-testnet/docker-compose-local-ltc-testnet.yml +++ b/packages/node/chain-setup/ltc-testnet/docker-compose-local-ltc-testnet.yml @@ -1,4 +1,3 @@ -version: "3" services: db: env_file: .env @@ -74,6 +73,8 @@ services: - NUM_WORKERS=${NUM_WORKERS} - SYNC_NON_STANDARD=${SYNC_NON_STANDARD} - BC_START_HEIGHT=${BC_START_HEIGHT} + volumes: + - ./logs:/dist/packages/node/logs depends_on: - db - node diff --git a/packages/node/db/db_schema.sql b/packages/node/db/db_schema.sql index f38764432..ded481878 100644 --- a/packages/node/db/db_schema.sql +++ b/packages/node/db/db_schema.sql @@ -73,8 +73,9 @@ CREATE TABLE IF NOT EXISTS CREATE TABLE IF NOT EXISTS "SyncStatus" ( - "syncedHeight" INTEGER NOT NULL, - "workerId" INTEGER NOT NULL PRIMARY KEY + "blockToSync" INTEGER NOT NULL, + "workerId" INTEGER NOT NULL PRIMARY KEY, + "nonStandard" BOOLEAN NOT NULL ); CREATE VIEW "Utxos" AS diff --git a/packages/node/dist/bcn.es.mjs b/packages/node/dist/bcn.es.mjs index b2eed07c5..18e02f542 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 o from"dotenv";import i from"fs";import{networks as c,bufferUtils as u,crypto as d,address as l,payments as p,Psbt as m,Transaction as h}from"@bitcoin-computer/nakamotojs";import y from"winston";import g from"winston-daily-rotate-file";import w from"pg-promise";import f from"pg-monitor";import{backOff as E}from"exponential-backoff";import{ECPairFactory as T}from"ecpair";import*as S from"@bitcoin-computer/tiny-secp256k1";import $ from"bitcoind-rpc";import v from"util";import{Computer as I}from"@bitcoin-computer/lib";import b from"elliptic";import O from"hash.js";import R,{dirname as x}from"path";import{fileURLToPath as N}from"url";o.config();const M=JSON.parse(i.readFileSync("package.json","utf8"));function C(t,e){switch(t){case"BTC":return"mainnet"===e?c.bitcoin:c.testnet;case"LTC":return"mainnet"===e?c.litecoin:c.litecoinregtest;default:throw new Error("We currently only support BTC and LTC, support for other currencies will be added soon.")}}const{PORT:A,ZMQ_URL:P,CHAIN:j,NETWORK:H,BCN_ENV:L,BCN_URL:B,DEBUG_MODE:_,POSTGRES_USER:k,POSTGRES_PASSWORD:D,POSTGRES_DB:F,POSTGRES_HOST:K,POSTGRES_PORT:U,RPC_PROTOCOL:W,RPC_USER:Y,RPC_PASSWORD:G,RPC_HOST:V,RPC_PORT:J,SERVER_VERSION:q,DEFAULT_WALLET:z,POSTGRES_MAX_PARAM_NUM:Z,DB_CONNECTION_RETRY_TIME:Q,SIGNATURE_FRESHNESS_MINUTES:X,ALLOWED_RPC_METHODS:tt,MAX_BLOCKCHAIN_HEIGHT:et,MWEB_HEIGHT:st,BC_START_HEIGHT:rt,WORKER_ID:at,NUM_WORKERS:nt,SYNC_NON_STANDARD:ot,ZMQ_WAIT_PERCENTAGE:it,QUERY_LIMIT:ct,LOG_MAX_FILE_SIZE:ut,LOG_MAX_FILE_NUM:dt,LOG_ZIP:lt}=process.env;const pt=parseInt(A,10)||"1031";const mt=P||"tcp://node:28332";const ht=j||"LTC";const yt=H||"regtest";const gt=L||"dev";const wt=B||`http://127.0.0.1:${pt}`;const ft=parseInt(_,10)||1;const Et=k||"bcn";const Tt=D||"bcn";const St=F||"bcn";const $t=K||"127.0.0.1";const vt=parseInt(U,10)||"5432";const It=W||"http";const bt=Y||"bcn-admin";const Ot=G||"kH4nU5Okm6-uyC0_mA5ztVNacJqZbYd_KGLl6mx722A=";const Rt=V||"node";const xt=parseInt(J,10)||19332;const Nt=M.version||q;const Mt=z||"defaultwallet";const Ct=parseInt(Z,10)||1e4;const At=parseInt(Q,10)||500;const Pt=parseInt(X,10)||3;const jt=tt?tt.split(",").map((t=>new RegExp(t))):[];const Ht=parseInt(et||"",10)||2538171;const Lt=parseInt(st||"",10)||432;const Bt=parseInt(rt||"",10)||25e5;const _t=parseInt(it||"",10)||.7;const kt=parseInt(ct||"",10)||1e3;const Dt=ut||"20m";const Ft=dt||"14d";const Kt=!!lt;y.addColors({error:"red",warn:"yellow",info:"green",http:"magenta",debug:"white"});const Ut=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 Wt={zippedArchive:Kt,maxSize:Dt,maxFiles:Ft,dirname:"logs"};const Yt=[];"dev"===gt&&Yt.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}`)))})),ft>=0&&Yt.push(new g({filename:"error-%DATE%.log",datePattern:"YYYY-MM-DD",level:"error",...Wt})),ft>=1&&Yt.push(new g({filename:"warn-%DATE%.log",datePattern:"YYYY-MM-DD",level:"warn",...Wt})),ft>=2&&Yt.push(new g({filename:"info-%DATE%.log",datePattern:"YYYY-MM-DD",level:"info",...Wt})),ft>=3&&Yt.push(new g({filename:"http-%DATE%.log",datePattern:"YYYY-MM-DD",level:"http",...Wt})),ft>=4&&Yt.push(new g({filename:"debug-%DATE%.log",datePattern:"YYYY-MM-DD",level:"debug",...Wt}));const Gt=y.createLogger({levels:{error:0,warn:1,info:2,http:3,debug:4},format:Ut,transports:Yt,exceptionHandlers:[new y.transports.File({filename:"logs/exceptions.log"})],rejectionHandlers:[new y.transports.File({filename:"logs/rejections.log"})]});const Vt=()=>"dev"===gt;const Jt={error:(t,e)=>{if(e.cn){const{host:s,port:r,database:a,user:n,password:o}=e.cn;Gt.debug(`Waiting for db to start { message:${t.message} host:${s}, port:${r}, database:${a}, user:${n}, password: ${o}`)}},noWarnings:!0};Vt()&&ft>0&&(f.isAttached()?f.detach():(f.attach(Jt),f.setTheme("matrix")));const qt=w(Jt)({host:$t,port:vt,database:St,user:Et,password:Tt,allowExitOnIdle:!0,idleTimeoutMillis:100});const{PreparedStatement:zt}=w;class Zt{static async select(t){const e=new zt({name:`OffChain.select.${Math.random()}`,text:'SELECT "data" FROM "OffChain" WHERE "id" = $1',values:[t]});return qt.oneOrNone(e)}static async insert({id:t,data:e}){const s=new zt({name:`OffChain.insert.${Math.random()}`,text:'INSERT INTO "OffChain" ("id", "data") VALUES ($1, $2) ON CONFLICT DO NOTHING',values:[t,e]});return qt.none(s)}static async delete(t){const e=new zt({name:`OffChain.delete.${Math.random()}`,text:'WITH deleted AS (DELETE FROM "OffChain" WHERE "id" = $1 RETURNING *) SELECT count(*) FROM deleted;',values:[t]});return(await qt.any(e))[0].count>0}}class Qt{static async select(t){const e=await Zt.select(t);return e?.data||null}static async insert(t){return Zt.insert(t)}static async delete(t){return Zt.delete(t)}}const Xt=s.Router();Xt.get("/:id",(async({params:{id:t},url:e},s)=>{try{const e=await Qt.select(t);e?s.status(200).json(e):s.status(403).json({error:"No entry found."})}catch(t){Gt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),Xt.post("/",(async(t,e)=>{const{body:{data:s},url:r}=t;try{const r=d.sha256(Buffer.from(s)).toString("hex");await Qt.insert({id:r,data:s});const a=`${t.protocol}://${t.get("host")}/store/${r}`;e.status(201).json({_url:a})}catch(t){Gt.error(`POST ${r} failed with error '${t.message}'`),e.status(500).json({error:t.message})}})),Xt.delete("/:id",(async(t,e)=>{e.status(500).json({error:"Deletions are not supported yet."})}));const{PreparedStatement:te}=w;class ee{static async getBalance(t){const e=new te({name:`Utxos.getBalance.${Math.random()}`,text:'SELECT sum("satoshis") as "satoshis" FROM "Utxos" WHERE "address" = $1',values:[t]});const s=await qt.oneOrNone(e);return parseInt(s?.satoshis,10)||0}static async select(t){const e=new te({name:`Utxos.select.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "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 qt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)||0})))}static async selectByScriptHex(t){const e=new te({name:`Utxos.select.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "rev", split_part(rev, \':\', 1) AS "txId", cast(split_part(rev, \':\', 2) as INTEGER) AS "vout" FROM "Utxos" WHERE "scriptPubKey" = $1',values:[t]});return(await qt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)||0})))}static async selectByPk(t){const e=new te({name:`Utxos.selectByPk.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "rev", split_part(rev, \':\', 1) AS "txId", cast(split_part(rev, \':\', 2) as INTEGER) AS "vout", "publicKeys" FROM "Utxos" WHERE $1 = ANY ("publicKeys")',values:[t]});return(await qt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)})))}}class se{static async getBalance(t){return ee.getBalance(t)}static async select(t){return ee.select(t)}static async selectByScriptHex(t){return ee.selectByScriptHex(t)}static async selectByPk(t){return ee.selectByPk(t)}}class re{static getBalance=async t=>se.getBalance(t);static select=async t=>se.select(t);static selectByScriptHex=async t=>se.selectByScriptHex(t);static selectByPk=async t=>se.selectByPk(t)}function ae(t){return/^[0-9A-Fa-f]{64}:\d+$/.test(t)}function ne(t){if(!ae(t))throw new Error("Invalid rev")}const{PreparedStatement:oe}=w;class ie{static async query(t){const{publicKey:e,hash:s,limit:r,offset:a,order:n,ids:o,mod:i}=t;if(r&&parseInt(r||"",10)>kt||o&&o.length>kt)throw new Error(`Can't fetch more than ${kt} revs.`);if(n&&"ASC"!==n&&"DESC"!==n)throw new Error("Invalid order");let c;c=o?.length?'SELECT "rev", "id", array_position($1, "id") as ord\n FROM "NonStandard" \n WHERE true ':'SELECT "rev"\n FROM "NonStandard"\n WHERE true ';const u=[];s&&(u.push(s),c+=` AND "hash" = $${u.length}`),i&&(u.push(i),c+=` AND "mod" = $${u.length}`),o&&(o.map(ne),u.push(o),c+=` AND "id" = ANY ($${u.length})`),e&&(u.push(e),c+=` AND $${u.length} = ANY ("publicKeys")`),n?(c+=` order by "lastUpdated" ${n}`,o?.length&&(c+=", ord")):o?.length&&(c+=" order by ord"),u.push(r||kt),c+=` limit $${u.length}`,a&&(u.push(a),c+=` offset $${u.length}`);const d=new oe({name:`NonStandard.query.${Math.random()}`,text:c,values:u});return(await qt.any(d)).map((t=>t.rev))}static async insert({id:t,rev:e,publicKeys:s,hash:r,mod:a}){const n=new oe({name:`NonStandard.insert.${Math.random()}`,text:'INSERT INTO "NonStandard"("id", "rev", "publicKeys", "hash", "mod") VALUES ($1, $2, $3, $4, $5) ON CONFLICT DO NOTHING',values:[t,e,s,r,a]});await qt.none(n)}static async update({id:t,rev:e,publicKeys:s}){const r=new oe({name:`NonStandard.update.${Math.random()}`,text:'UPDATE "NonStandard" SET "rev"=$2, "publicKeys"=$3 WHERE "id" = $1',values:[t,e,s]});return qt.none(r)}static async delete({rev:t}){const e=new oe({name:`NonStandard.delete.${Math.random()}`,text:'DELETE FROM "NonStandard" WHERE "rev" = $1',values:[t]});await qt.none(e)}static async getRevsByIds(t){if(t&&t.length>kt)throw new Error(`Can't fetch more than ${kt} revs.`);const e=new oe({name:`NonStandard.getRevsByIds.${Math.random()}`,text:'SELECT "rev" FROM "NonStandard" WHERE "id" LIKE ANY($1)',values:[[t]]});return qt.any(e)}static async select(t){const e=new oe({name:`NonStandard.select.${Math.random()}`,text:'SELECT "id", "hash", "mod" FROM "NonStandard" WHERE "rev" = $1',values:[t]});return qt.oneOrNone(e)}}class ce{static async select(t){return ie.select(t)}static async query(t){return ie.query(t)}static async getRevsByIds(t){return ie.getRevsByIds(t)}static async insert(t){return ie.insert(t)}static async update(t){return ie.update(t)}static async delete(t){return ie.delete({rev:t})}}const{PreparedStatement:ue}=w;class de{static async getId(t){const e=new ue({name:`RevToId.select.${Math.random()}`,text:'SELECT "id" FROM "RevToId" WHERE "rev" = $1',values:[t]});const s=await qt.oneOrNone(e);return s?.id}static async insert(t){const e=new ue({name:`RevToId.insert.${Math.random()}`,text:'INSERT INTO "RevToId"("rev", "id") VALUES ($1, $2) ON CONFLICT DO NOTHING',values:[t.rev,t.id]});await qt.none(e)}}class le{static async getId(t){return de.getId(t)}static async insert(t){return de.insert(t)}}class pe{static add=async t=>{const{zip:e,outData:s}=t;for(let t=0;tce.query(t);static getRevsByIds=async t=>(await ce.getRevsByIds(t)).map((t=>t.rev))}const me=new $({protocol:It,user:bt,pass:Ot,host:Rt,port:xt});const he=v.promisify($.prototype.createwallet.bind(me));const ye=v.promisify($.prototype.generateToAddress.bind(me));const ge=v.promisify($.prototype.getaddressinfo.bind(me));const we=v.promisify($.prototype.getBlock.bind(me));const fe=v.promisify($.prototype.getBlockchainInfo.bind(me));const Ee=v.promisify($.prototype.getBlockHash.bind(me));const Te=v.promisify($.prototype.getRawTransaction.bind(me));const Se=v.promisify($.prototype.getRawTransaction.bind(me));const $e=v.promisify($.prototype.getTransaction.bind(me));const ve=v.promisify($.prototype.getNewAddress.bind(me));const Ie={createwallet:he,generateToAddress:ye,getaddressinfo:ge,getBlock:we,getBlockchainInfo:fe,getBlockHash:Ee,getRawTransaction:Te,getTransaction:$e,importaddress:v.promisify($.prototype.importaddress.bind(me)),listunspent:v.promisify($.prototype.listunspent.bind(me)),sendRawTransaction:v.promisify($.prototype.sendRawTransaction.bind(me)),getNewAddress:ve,sendToAddress:v.promisify($.prototype.sendToAddress.bind(me)),getRawTransactionJSON:Se};class be{static async getTransaction(t){const{result:e}=await Ie.getTransaction(t);return e}static async getBulkTransactions(t){return(await Promise.all(t.map((t=>Ie.getRawTransaction(t))))).map((t=>t.result))}static async getRawTransactionsJSON(t){return{txId:(e=(await Ie.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 Ie.sendRawTransaction(t);if(s)throw Gt.error(s),new Error("Error sending transaction");return e}static getUtxos=async t=>(void 0===(await Ie.getaddressinfo(t)).result.timestamp&&(Gt.info(`Importing address: ${t}`),await Ie.importaddress(t,!1)),(await Ie.listunspent(0,999999,[t])).result)}class Oe{static get=async t=>be.getTransaction(t);static getRaw=async t=>be.getBulkTransactions(t);static getRawJSON=async t=>be.getRawTransactionsJSON(t);static sendRaw=async t=>be.sendRawTransaction(t);static getUtxos=async t=>be.getUtxos(t)}const Re=new $({protocol:It,user:bt,pass:Ot,host:Rt,port:xt});const xe={};const Ne=JSON.parse(JSON.stringify($.callspec));Object.keys(Ne).forEach((t=>{Ne[t.toLowerCase()]=Ne[t]}));const Me={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($.prototype).forEach((t=>{if(t&&"function"==typeof $.prototype[t]){const e=t.toLowerCase();xe[t]=v.promisify($.prototype[t].bind(Re)),xe[e]=v.promisify($.prototype[e].bind(Re))}}))}catch(t){Gt.error(`Error occurred while binding RPC methods: ${t.message}`)}const{PreparedStatement:Ce}=w;class Ae{static async listSentOutputs(t){const e=new Ce({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 qt.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listReceivedOutputs(t){const e=new Ce({name:`Output.listReceivedTxs.${Math.random()}`,text:'SELECT "Output"."rev" as "output", "Output"."satoshis" as "amount" FROM "Output" WHERE "address" = $1',values:[t]});return(await qt.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listTxs(t){const e=new Ce({name:`Output.listTxs.${Math.random()}`,text:' -- List all txs sent from a given address\n WITH 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 \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 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 qt.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 Ce({name:`Output.select.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "rev" FROM "Output" WHERE "address" = $1',values:[t]});return qt.any(e)}static async insert(t){const e=t.flatMap((t=>[t.rev,t.address,t.satoshis,t.scriptPubKey,t.publicKeys]));for(;e.length;){const t=e.splice(0,Ct);const s=[];for(let e=1;e<=t.length;e+=5)s.push(`($${e}, $${e+1}, $${e+2}, $${e+3}, $${e+4})`);const r=s.join(",");const a=new Ce({name:`Output.insert.${Math.random()}`,text:`INSERT INTO "Output"("rev", "address", "satoshis", "scriptPubKey", "publicKeys") VALUES ${r} ON CONFLICT DO NOTHING`,values:t});await qt.none(a)}}}class Pe{static async select(t){return Ae.select(t)}static async insert(t){return Ae.insert(t)}static async listSentOutputs(t){return Ae.listSentOutputs(t)}static async listReceivedOutputs(t){return Ae.listReceivedOutputs(t)}static async listTxs(t){return Ae.listTxs(t)}}class je{static insert=async t=>{const e=t.flatMap((t=>t.tx.outs.map(((e,s)=>{const{script:r}=e;let a;let n;try{a=l.fromOutputScript(r,C(ht,yt))}catch(t){a=null}try{n=p.p2ms({output:r,network:C(ht,yt)}).pubkeys.map((t=>t.toString("hex"))),n.some((t=>t.length>66))&&(n=null)}catch(t){n=null}const o=r.toString("hex");const i=Math.round(e.value);return{address:a,rev:`${t.txId}:${s}`,scriptPubKey:o,satoshis:i,publicKeys:n}}))));return Pe.insert(e)};static listSentOutputs=async t=>Pe.listSentOutputs(t);static listReceivedOutputs=async t=>Pe.listReceivedOutputs(t);static listTxs=async t=>Pe.listTxs(t)}const He=t=>new Promise((e=>setTimeout(e,t)));const Le=T(S);const Be=c.regtest;const{PreparedStatement:_e}=w;class ke{static async select(t){const e=new _e({name:`Input.select.${Math.random()}`,text:'SELECT "outputSpent" FROM "Input" WHERE "outputSpent" = $1',values:[t]});return qt.any(e)}static async insert(t){const e=t.flatMap((t=>[t.outputSpent,t.spendingInput]));for(;e.length;){const t=e.splice(0,Ct);const s=[];for(let e=1;e<=t.length;e+=2)s.push(`($${e}, $${e+1})`);const r=s.join(",");const a=new _e({name:`Input.insert.${Math.random()}`,text:`INSERT INTO "Input"("outputSpent", "spendingInput") VALUES ${r} ON CONFLICT DO NOTHING`,values:t});await qt.none(a)}}static async count(t){const e=t.map((t=>t.outputSpent));const s=new _e({name:`Input.belong.${Math.random()}`,text:'SELECT count(*) FROM "Input" WHERE "outputSpent" LIKE ANY ($1)',values:[[e]]});const r=await qt.oneOrNone(s);return parseInt(r?.count,10)||0}}class De{static async select(t){return ke.select(t)}static async insert(t){return ke.insert(t)}}class Fe{static getNonCoinbaseRevs=t=>t.filter((t=>!h.isCoinbaseHash(t.input.hash))).map((({input:t,txId:e},s)=>{return{outputSpent:`${r=t.hash,u.reverseBuffer(Buffer.from(r)).toString("hex")}:${t.index}`,spendingInput:`${e}:${s}`};var r}));static insert=async t=>{De.insert(this.getNonCoinbaseRevs(t))}}let Ke;try{Ke=new I({chain:ht,network:yt,url:wt})}catch(t){Gt.error(`Error creating computer, ${t.message}`),process.exit(1)}class Ue{static syncTx=async t=>{await je.insert([t]),await Fe.insert(t.tx.ins.map((e=>({input:e,txId:t.txId})))),t.isBcTx(ht,yt)&&await pe.add(t)};static rawTxSubscriber=async t=>{const e=t.toString("hex");if(Gt.info(`ZMQ message { rawTx:${e} }`),"08"!==e.slice(10,12)){let t;try{t=await Ke.txFromHex({hex:e})}catch(t){Gt.error(`RawTxSubscriber failed with error '${t.message} ${t.stack}'`)}try{await this.syncTx(t)}catch(t){Gt.error(`Error parsing transaction ${t.message} ${t.stack}`)}}};static checkSyncStatus=async()=>{const t=await E((async()=>{const t=await Ie.getBlockchainInfo();const e=(100*parseFloat(t.result.verificationprogress)).toFixed(4);const{blocks:s}=t.result;if(Gt.info(`Zmq. Bitcoind { percentage:${e}%, blocks:${s} }`),parseFloat(t.result.verificationprogress)<=_t)throw new Error("Node not ready yet");return t}),{startingDelay:6e4,timeMultiple:1,numOfAttempts:8760});const e=(100*parseFloat(t.result.verificationprogress)).toFixed(4);const s=t.result.blocks;Gt.info(`BCN reaches sync end...at { bitcoind.progress:${e}%, bitcoindSyncedHeight:${s} }`)};static createWallet=async()=>{try{await Ie.createwallet(Mt,!1,!1,"",!1,!1)}catch(t){Gt.error(`Wallet creation failed with error '${t.message}'`)}};static sub=async t=>{try{await this.createWallet(),"regtest"!==yt&&await this.checkSyncStatus(),await(async()=>{if("regtest"===yt){if(Gt.info(`Node is starting for chain ${ht} and network ${yt}, \n\n. Starting Wallet setup.`),"LTC"===ht){const{result:t}=await Ie.getBlockchainInfo();const e=t.blocks;if(e{try{const t=Vt()?"bcn.test.config.json":"bcn.config.json";const e=x(N(import.meta.url));this.configFile=i.readFileSync(R.join(e,"..","..",t)),this.loaded=!0}catch(t){if(t.message.includes("ENOENT: no such file or directory"))return void(this.loaded=!0);throw Gt.error(`Access-list failed with error '${t.message}'`),t}};middleware=({url:t},e,s)=>{if(void 0!==e.locals.authToken)if(this.loaded||(Gt.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){Gt.error(`Authorization failed at ${t} with error: '${s.message}'`),e.status(403).json({error:s.message})}else s();else s()}};let Ze;try{Ze=r.createServer(qe)}catch(t){throw Gt.error(`Starting server failed with error '${t.message}'`),t}if(Gt.info(`Server listening on port ${pt}`),qe.use(e()),"dev"!==gt){const t=n({windowMs:9e5,max:300,standardHeaders:!0,legacyHeaders:!1});qe.use(t)}qe.use(t.json({limit:"100mb"})),qe.use(t.urlencoded({limit:"100mb",extended:!0})),qe.get("/",((t,e)=>e.status(200).send(`\n

Bitcoin Computer Node

\n Status: Healthy
\n Version: ${Nt}
\n Chain: ${ht}
\n Network: ${yt}\n `))),ze.loaded&&(qe.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 Gt.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>1e3*Pt*60)return void e.status(401).json({error:"Signature is too old."});const c=O.sha256().update(wt+i).digest("hex");if(!Je.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 Ge.select(o);if(u){if(u.clientTimestamp>=i)return void e.status(401).json({error:"Please use a fresh authentication token."});await Ge.update({publicKey:o,clientTimestamp:i})}else await Ge.insert({publicKey:o,clientTimestamp:i});e.locals.authToken=a,s()}catch(t){Gt.error(`Auth failed with error '${t.message}'`),e.status(401).json({error:t.message})}})),qe.use(ze.middleware));const Qe=(()=>{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 re.select(e))}catch(t){Gt.error(`GET ${e} failed with error '${t.message}'`),s.status(404).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 je.listSentOutputs(e))}catch(t){Gt.error(`GET ${e} failed with error '${t.message}'`),s.status(404).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 je.listReceivedOutputs(e))}catch(t){Gt.error(`GET ${e} failed with error '${t.message}'`),s.status(404).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 je.listTxs(e))}catch(t){Gt.error(`GET ${e} failed with error '${t.message}'`),s.status(404).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"),hash:s.get("hash"),limit:s.get("limit"),order:s.get("order"),offset:s.get("offset"),ids:JSON.parse(s.get("ids"))};const a=await pe.query(r);e.status(200).json(a)}catch(s){Gt.error(`GET ${t.url} failed with error '${s.messages}'`),e.status(404).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 re.getBalance(e))}catch(t){Gt.error(`GET ${e} failed with error '${t.message}'`),s.status(404).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(500).json({error:"Missing input txIds."});const e=await Oe.getRaw(t);e?s.status(200).json(e):s.status(404).json({error:"Not found"})}catch(t){Gt.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(500).json({error:"Missing input hex."});const e=await Oe.sendRaw(t);e?s.status(200).json(e):s.status(404).json({error:"Error Occured"})}catch(r){Gt.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 xe.getnewaddress();if("string"!=typeof t)throw new Error("Please provide appropriate count");return await xe.generatetoaddress(parseInt(t,10)||1,e),s.status(200).json({success:!0})}catch(t){return Gt.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 xe.getbestblockhash();e=t}const{result:r}=await xe.getblockheader(e,!0);return s.status(200).json({height:r.height})}catch(t){return Gt.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=(await xe.sendtoaddress(t,parseInt(e,10)/1e8,"","")).result;const a=process.env.TEST_ADDRESS.split(";")[0];await xe.generateToAddress(1,a);const n=(await xe.getrawtransaction(s,1)).result.vout.findIndex((t=>1e8*t.value===parseInt(e,10)));return r.status(200).json({txId:s,vout:n,height:-1,satoshis:e})}catch(t){return Gt.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=Le.makeRandom({network:Be});const a=p.p2pkh({pubkey:s.publicKey,network:Be});const{address:n}=a;const o=(await xe.sendtoaddress(n,2*parseInt(e,10)/1e8,"","")).result;let i;let c=10;for(;!i;)if(i=(await re.select(n)).filter((t=>t.txId===o))[0],!i){if(c-=1,c<=0)throw new Error("No outputs");await He(10)}const u=(await xe.getrawtransaction(i.txId,1)).result;const d=new m({network:Be});d.addInput({hash:i.txId,index:i.vout,nonWitnessUtxo:Buffer.from(u.hex,"hex")}),d.addOutput({script:Buffer.from(t,"hex"),value:parseInt(e,10)}),d.signInput(0,s),d.finalizeAllInputs();const l=d.extractTransaction();let h;for(await xe.sendrawtransaction(l.toHex()),c=5;!h;)if(h=(await re.selectByScriptHex(t)).filter((t=>t.txId===l.getId()))[0],!h){if(c-=1,c<=0)throw new Error("No outputs");await He(10)}return r.status(200).json({txId:l.getId(),vout:h.vout,height:-1,satoshis:h.satoshis})}catch(t){return Gt.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(500).json({error:"Missing input txId."});const e=await Oe.getRawJSON(t);e?s.status(200).json(e):s.status(404).json({error:"Not found"})}catch(t){Gt.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(404).json({error:"Missing input object ids."});const e=await pe.getRevsByIds(t);s.status(200).json(e)}catch(t){Gt.error(`POST ${e} failed with error '${t.message}'`),s.status(404).json({error:t.message})}})),t.post("/revToId",(async({body:{rev:t},url:e},s)=>{try{if(!ae(t))return void s.status(400).json({error:"Invalid rev id"});const e=await le.getId(t);e&&s.status(200).json(e),s.status(404).json()}catch(t){Gt.error(`POST ${e} failed with error '${t.message}'`),s.status(404).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(!jt.some((e=>e.test(t.method))))throw new Error("Method is not allowed");const e=function(t,e){if(void 0===Ne[t]||null===Ne[t])throw new Error("This RPC method does not exist, or not supported");const s=e.trim().split(" ");const r=Ne[t].trim().split(" ");if(0===e.trim().length&&0!==Ne[t].trim().length)throw new Error(`Too few params provided. Expected ${r.length} Provided 0`);if(0!==e.trim().length&&0===Ne[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)=>Me[r[e]](t)))}(t.method,t.params);const r=e.length?await xe[t.method](...e):await xe[t.method]();s.status(200).json({result:r})}catch(t){Gt.error(`POST ${e} failed with error '${t.message}'`),s.status(404).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})();if("mainnet"===yt)throw new Error("Mainnet is currently disabled in your jurisdiction");qe.use(`/v1/${ht}/${yt}`,Qe),qe.use("/v1/store",Xt),Ze.listen(pt,(()=>{Gt.info(`Rev ${Nt} Started web server on port ${pt} BC_START_HEIGHT ${Bt}`)})).on("error",(t=>{Gt.error(t.message),process.exit(1)}));const Xe=new a.Subscriber;Xe.connect(mt),Xe.subscribe("rawtx"),Gt.info(`ZMQ Subscriber connected to ${mt}`),(async()=>{await(async()=>{await E((()=>qt.connect()),{startingDelay:At})})(),await Ue.sub(Xe)})(); +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 o from"dotenv";import i from"fs";import{networks as c,bufferUtils as u,crypto as d,address as l,payments as p,Psbt as m,Transaction as h}from"@bitcoin-computer/nakamotojs";import y from"winston";import g from"winston-daily-rotate-file";import w from"pg-promise";import f from"pg-monitor";import{backOff as E}from"exponential-backoff";import{ECPairFactory as T}from"ecpair";import*as S from"@bitcoin-computer/tiny-secp256k1";import $ from"bitcoind-rpc";import v from"util";import{Computer as I}from"@bitcoin-computer/lib";import O from"elliptic";import b from"hash.js";import R,{dirname as x}from"path";import{fileURLToPath as N}from"url";o.config();const M=JSON.parse(i.readFileSync("package.json","utf8"));function C(t,e){switch(t){case"BTC":return"mainnet"===e?c.bitcoin:c.testnet;case"LTC":return"mainnet"===e?c.litecoin:c.litecoinregtest;default:throw new Error("We currently only support BTC and LTC, support for other currencies will be added soon.")}}const{PORT:A,ZMQ_URL:P,CHAIN:j,NETWORK:H,BCN_ENV:L,BCN_URL:B,DEBUG_MODE:_,POSTGRES_USER:k,POSTGRES_PASSWORD:D,POSTGRES_DB:F,POSTGRES_HOST:K,POSTGRES_PORT:U,RPC_PROTOCOL:W,RPC_USER:Y,RPC_PASSWORD:G,RPC_HOST:V,RPC_PORT:J,SERVER_VERSION:q,DEFAULT_WALLET:z,POSTGRES_MAX_PARAM_NUM:Z,DB_CONNECTION_RETRY_TIME:Q,SIGNATURE_FRESHNESS_MINUTES:X,ALLOWED_RPC_METHODS:tt,MAX_BLOCKCHAIN_HEIGHT:et,MWEB_HEIGHT:st,BC_START_HEIGHT:rt,WORKER_ID:at,NUM_WORKERS:nt,SYNC_NON_STANDARD:ot,ZMQ_WAIT_PERCENTAGE:it,QUERY_LIMIT:ct,LOG_MAX_FILE_SIZE:ut,LOG_MAX_FILE_NUM:dt,LOG_ZIP:lt}=process.env;const pt=parseInt(A,10)||"1031";const mt=P||"tcp://node:28332";const ht=j||"LTC";const yt=H||"regtest";const gt=L||"dev";const wt=B||`http://127.0.0.1:${pt}`;const ft=parseInt(_,10)||1;const Et=k||"bcn";const Tt=D||"bcn";const St=F||"bcn";const $t=K||"127.0.0.1";const vt=parseInt(U,10)||"5432";const It=W||"http";const Ot=Y||"bcn-admin";const bt=G||"kH4nU5Okm6-uyC0_mA5ztVNacJqZbYd_KGLl6mx722A=";const Rt=V||"node";const xt=parseInt(J,10)||19332;const Nt=M.version||q;const Mt=z||"defaultwallet";const Ct=parseInt(Z,10)||1e4;const At=parseInt(Q,10)||500;const Pt=parseInt(X,10)||3;const jt=tt?tt.split(",").map((t=>new RegExp(t))):[];const Ht=parseInt(et||"",10)||2538171;const Lt=parseInt(st||"",10)||432;const Bt=parseInt(rt||"",10)||25e5;const _t=parseInt(it||"",10)||.7;const kt=parseInt(ct||"",10)||1e3;const Dt=ut||"20m";const Ft=dt||"14d";const Kt=!!lt;y.addColors({error:"red",warn:"yellow",info:"green",http:"magenta",debug:"white"});const Ut=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 Wt={zippedArchive:Kt,maxSize:Dt,maxFiles:Ft,dirname:"logs"};const Yt=[];"dev"===gt&&Yt.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}`)))})),ft>=0&&Yt.push(new g({filename:"error-%DATE%.log",datePattern:"YYYY-MM-DD",level:"error",...Wt})),ft>=1&&Yt.push(new g({filename:"warn-%DATE%.log",datePattern:"YYYY-MM-DD",level:"warn",...Wt})),ft>=2&&Yt.push(new g({filename:"info-%DATE%.log",datePattern:"YYYY-MM-DD",level:"info",...Wt})),ft>=3&&Yt.push(new g({filename:"http-%DATE%.log",datePattern:"YYYY-MM-DD",level:"http",...Wt})),ft>=4&&Yt.push(new g({filename:"debug-%DATE%.log",datePattern:"YYYY-MM-DD",level:"debug",...Wt}));const Gt=y.createLogger({levels:{error:0,warn:1,info:2,http:3,debug:4},format:Ut,transports:Yt,exceptionHandlers:[new y.transports.File({filename:"logs/exceptions.log"})],rejectionHandlers:[new y.transports.File({filename:"logs/rejections.log"})]});const Vt=()=>"dev"===gt;const Jt={error:(t,e)=>{if(e.cn){const{host:s,port:r,database:a,user:n,password:o}=e.cn;Gt.debug(`Waiting for db to start { message:${t.message} host:${s}, port:${r}, database:${a}, user:${n}, password: ${o}`)}},noWarnings:!0};Vt()&&ft>0&&(f.isAttached()?f.detach():(f.attach(Jt),f.setTheme("matrix")));const qt=w(Jt)({host:$t,port:vt,database:St,user:Et,password:Tt,allowExitOnIdle:!0,idleTimeoutMillis:100});const{PreparedStatement:zt}=w;class Zt{static async select(t){const e=new zt({name:`OffChain.select.${Math.random()}`,text:'SELECT "data" FROM "OffChain" WHERE "id" = $1',values:[t]});return qt.oneOrNone(e)}static async insert({id:t,data:e}){const s=new zt({name:`OffChain.insert.${Math.random()}`,text:'INSERT INTO "OffChain" ("id", "data") VALUES ($1, $2) ON CONFLICT DO NOTHING',values:[t,e]});return qt.none(s)}static async delete(t){const e=new zt({name:`OffChain.delete.${Math.random()}`,text:'WITH deleted AS (DELETE FROM "OffChain" WHERE "id" = $1 RETURNING *) SELECT count(*) FROM deleted;',values:[t]});return(await qt.any(e))[0].count>0}}class Qt{static async select(t){const e=await Zt.select(t);return e?.data||null}static async insert(t){return Zt.insert(t)}static async delete(t){return Zt.delete(t)}}const Xt=s.Router();Xt.get("/:id",(async({params:{id:t},url:e},s)=>{try{const e=await Qt.select(t);e?s.status(200).json(e):s.status(403).json({error:"No entry found."})}catch(t){Gt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),Xt.post("/",(async(t,e)=>{const{body:{data:s},url:r}=t;try{const r=d.sha256(Buffer.from(s)).toString("hex");await Qt.insert({id:r,data:s});const a=`${t.protocol}://${t.get("host")}/store/${r}`;e.status(201).json({_url:a})}catch(t){Gt.error(`POST ${r} failed with error '${t.message}'`),e.status(500).json({error:t.message})}})),Xt.delete("/:id",(async(t,e)=>{e.status(500).json({error:"Deletions are not supported yet."})}));const{PreparedStatement:te}=w;class ee{static async getBalance(t){const e=new te({name:`Utxos.getBalance.${Math.random()}`,text:'SELECT sum("satoshis") as "satoshis" FROM "Utxos" WHERE "address" = $1',values:[t]});const s=await qt.oneOrNone(e);return parseInt(s?.satoshis,10)||0}static async select(t){const e=new te({name:`Utxos.select.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "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 qt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)||0})))}static async selectByScriptHex(t){const e=new te({name:`Utxos.select.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "rev", split_part(rev, \':\', 1) AS "txId", cast(split_part(rev, \':\', 2) as INTEGER) AS "vout" FROM "Utxos" WHERE "scriptPubKey" = $1',values:[t]});return(await qt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)||0})))}static async selectByPk(t){const e=new te({name:`Utxos.selectByPk.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "rev", split_part(rev, \':\', 1) AS "txId", cast(split_part(rev, \':\', 2) as INTEGER) AS "vout", "publicKeys" FROM "Utxos" WHERE $1 = ANY ("publicKeys")',values:[t]});return(await qt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)})))}}class se{static async getBalance(t){return ee.getBalance(t)}static async select(t){return ee.select(t)}static async selectByScriptHex(t){return ee.selectByScriptHex(t)}static async selectByPk(t){return ee.selectByPk(t)}}class re{static getBalance=async t=>se.getBalance(t);static select=async t=>se.select(t);static selectByScriptHex=async t=>se.selectByScriptHex(t);static selectByPk=async t=>se.selectByPk(t)}function ae(t){return/^[0-9A-Fa-f]{64}:\d+$/.test(t)}function ne(t){if(!ae(t))throw new Error("Invalid rev")}const{PreparedStatement:oe}=w;class ie{static async query(t){const{publicKey:e,hash:s,limit:r,offset:a,order:n,ids:o,mod:i}=t;if(r&&parseInt(r||"",10)>kt||o&&o.length>kt)throw new Error(`Can't fetch more than ${kt} revs.`);if(n&&"ASC"!==n&&"DESC"!==n)throw new Error("Invalid order");let c;c=o?.length?'SELECT "rev", "id", array_position($1, "id") as ord\n FROM "NonStandard" \n WHERE true ':'SELECT "rev"\n FROM "NonStandard"\n WHERE true ';const u=[];s&&(u.push(s),c+=` AND "hash" = $${u.length}`),i&&(u.push(i),c+=` AND "mod" = $${u.length}`),o&&(o.map(ne),u.push(o),c+=` AND "id" = ANY ($${u.length})`),e&&(u.push(e),c+=` AND $${u.length} = ANY ("publicKeys")`),n?(c+=` order by "lastUpdated" ${n}`,o?.length&&(c+=", ord")):o?.length&&(c+=" order by ord"),u.push(r||kt),c+=` limit $${u.length}`,a&&(u.push(a),c+=` offset $${u.length}`);const d=new oe({name:`NonStandard.query.${Math.random()}`,text:c,values:u});return(await qt.any(d)).map((t=>t.rev))}static async insert({id:t,rev:e,publicKeys:s,hash:r,mod:a}){const n=new oe({name:`NonStandard.insert.${Math.random()}`,text:'INSERT INTO "NonStandard"("id", "rev", "publicKeys", "hash", "mod") VALUES ($1, $2, $3, $4, $5) ON CONFLICT DO NOTHING',values:[t,e,s,r,a]});await qt.none(n)}static async update({id:t,rev:e,publicKeys:s}){const r=new oe({name:`NonStandard.update.${Math.random()}`,text:'UPDATE "NonStandard" SET "rev"=$2, "publicKeys"=$3 WHERE "id" = $1',values:[t,e,s]});return qt.none(r)}static async delete({rev:t}){const e=new oe({name:`NonStandard.delete.${Math.random()}`,text:'DELETE FROM "NonStandard" WHERE "rev" = $1',values:[t]});await qt.none(e)}static async getRevsByIds(t){if(t&&t.length>kt)throw new Error(`Can't fetch more than ${kt} revs.`);const e=new oe({name:`NonStandard.getRevsByIds.${Math.random()}`,text:'SELECT "rev" FROM "NonStandard" WHERE "id" LIKE ANY($1)',values:[[t]]});return qt.any(e)}static async select(t){const e=new oe({name:`NonStandard.select.${Math.random()}`,text:'SELECT "id", "hash", "mod" FROM "NonStandard" WHERE "rev" = $1',values:[t]});return qt.oneOrNone(e)}}class ce{static async select(t){return ie.select(t)}static async query(t){return ie.query(t)}static async getRevsByIds(t){return ie.getRevsByIds(t)}static async insert(t){return ie.insert(t)}static async update(t){return ie.update(t)}static async delete(t){return ie.delete({rev:t})}}const{PreparedStatement:ue}=w;class de{static async getId(t){const e=new ue({name:`RevToId.select.${Math.random()}`,text:'SELECT "id" FROM "RevToId" WHERE "rev" = $1',values:[t]});const s=await qt.oneOrNone(e);return s?.id}static async insert(t){const e=new ue({name:`RevToId.insert.${Math.random()}`,text:'INSERT INTO "RevToId"("rev", "id") VALUES ($1, $2) ON CONFLICT DO NOTHING',values:[t.rev,t.id]});await qt.none(e)}}class le{static async getId(t){return de.getId(t)}static async insert(t){return de.insert(t)}}class pe{static add=async t=>{const{zip:e,outData:s}=t;for(let t=0;tce.query(t);static getRevsByIds=async t=>(await ce.getRevsByIds(t)).map((t=>t.rev))}const me=new $({protocol:It,user:Ot,pass:bt,host:Rt,port:xt});const he=v.promisify($.prototype.createwallet.bind(me));const ye=v.promisify($.prototype.generateToAddress.bind(me));const ge=v.promisify($.prototype.getaddressinfo.bind(me));const we=v.promisify($.prototype.getBlock.bind(me));const fe=v.promisify($.prototype.getBlockchainInfo.bind(me));const Ee=v.promisify($.prototype.getBlockHash.bind(me));const Te=v.promisify($.prototype.getRawTransaction.bind(me));const Se=v.promisify($.prototype.getRawTransaction.bind(me));const $e=v.promisify($.prototype.getTransaction.bind(me));const ve=v.promisify($.prototype.getNewAddress.bind(me));const Ie={createwallet:he,generateToAddress:ye,getaddressinfo:ge,getBlock:we,getBlockchainInfo:fe,getBlockHash:Ee,getRawTransaction:Te,getTransaction:$e,importaddress:v.promisify($.prototype.importaddress.bind(me)),listunspent:v.promisify($.prototype.listunspent.bind(me)),sendRawTransaction:v.promisify($.prototype.sendRawTransaction.bind(me)),getNewAddress:ve,sendToAddress:v.promisify($.prototype.sendToAddress.bind(me)),getRawTransactionJSON:Se};class Oe{static async getTransaction(t){const{result:e}=await Ie.getTransaction(t);return e}static async getBulkTransactions(t){return(await Promise.all(t.map((t=>Ie.getRawTransaction(t))))).map((t=>t.result))}static async getRawTransactionsJSON(t){return{txId:(e=(await Ie.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 Ie.sendRawTransaction(t);if(s)throw Gt.error(s),new Error("Error sending transaction");return e}static getUtxos=async t=>(void 0===(await Ie.getaddressinfo(t)).result.timestamp&&(Gt.info(`Importing address: ${t}`),await Ie.importaddress(t,!1)),(await Ie.listunspent(0,999999,[t])).result)}class be{static get=async t=>Oe.getTransaction(t);static getRaw=async t=>Oe.getBulkTransactions(t);static getRawJSON=async t=>Oe.getRawTransactionsJSON(t);static sendRaw=async t=>Oe.sendRawTransaction(t);static getUtxos=async t=>Oe.getUtxos(t)}const Re=new $({protocol:It,user:Ot,pass:bt,host:Rt,port:xt});const xe={};const Ne=JSON.parse(JSON.stringify($.callspec));Object.keys(Ne).forEach((t=>{Ne[t.toLowerCase()]=Ne[t]}));const Me={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($.prototype).forEach((t=>{if(t&&"function"==typeof $.prototype[t]){const e=t.toLowerCase();xe[t]=v.promisify($.prototype[t].bind(Re)),xe[e]=v.promisify($.prototype[e].bind(Re))}}))}catch(t){Gt.error(`Error occurred while binding RPC methods: ${t.message}`)}const{PreparedStatement:Ce}=w;class Ae{static async listSentOutputs(t){const e=new Ce({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 qt.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listReceivedOutputs(t){const e=new Ce({name:`Output.listReceivedTxs.${Math.random()}`,text:'SELECT "Output"."rev" as "output", "Output"."satoshis" as "amount" FROM "Output" WHERE "address" = $1',values:[t]});return(await qt.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listTxs(t){const e=new Ce({name:`Output.listTxs.${Math.random()}`,text:' -- List all txs sent from a given address\n WITH 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 \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 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 qt.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 Ce({name:`Output.select.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "rev" FROM "Output" WHERE "address" = $1',values:[t]});return qt.any(e)}static async insert(t){const e=t.flatMap((t=>[t.rev,t.address,t.satoshis,t.scriptPubKey,t.publicKeys]));for(;e.length;){const t=e.splice(0,Ct);const s=[];for(let e=1;e<=t.length;e+=5)s.push(`($${e}, $${e+1}, $${e+2}, $${e+3}, $${e+4})`);const r=s.join(",");const a=new Ce({name:`Output.insert.${Math.random()}`,text:`INSERT INTO "Output"("rev", "address", "satoshis", "scriptPubKey", "publicKeys") VALUES ${r} ON CONFLICT DO NOTHING`,values:t});await qt.none(a)}}}class Pe{static async select(t){return Ae.select(t)}static async insert(t){return Ae.insert(t)}static async listSentOutputs(t){return Ae.listSentOutputs(t)}static async listReceivedOutputs(t){return Ae.listReceivedOutputs(t)}static async listTxs(t){return Ae.listTxs(t)}}class je{static insert=async t=>{const e=t.flatMap((t=>t.tx.outs.map(((e,s)=>{const{script:r}=e;let a;let n;try{a=l.fromOutputScript(r,C(ht,yt))}catch(t){a=null}try{n=p.p2ms({output:r,network:C(ht,yt)}).pubkeys.map((t=>t.toString("hex"))),n.some((t=>t.length>66))&&(n=null)}catch(t){n=null}const o=r.toString("hex");const i=Math.round(e.value);return{address:a,rev:`${t.txId}:${s}`,scriptPubKey:o,satoshis:i,publicKeys:n}}))));return Pe.insert(e)};static listSentOutputs=async t=>Pe.listSentOutputs(t);static listReceivedOutputs=async t=>Pe.listReceivedOutputs(t);static listTxs=async t=>Pe.listTxs(t)}const He=t=>new Promise((e=>setTimeout(e,t)));const Le=T(S);const Be=c.regtest;const{PreparedStatement:_e}=w;class ke{static async select(t){const e=new _e({name:`Input.select.${Math.random()}`,text:'SELECT "outputSpent" FROM "Input" WHERE "outputSpent" = $1',values:[t]});return qt.any(e)}static async insert(t){const e=t.flatMap((t=>[t.outputSpent,t.spendingInput]));for(;e.length;){const t=e.splice(0,Ct);const s=[];for(let e=1;e<=t.length;e+=2)s.push(`($${e}, $${e+1})`);const r=s.join(",");const a=new _e({name:`Input.insert.${Math.random()}`,text:`INSERT INTO "Input"("outputSpent", "spendingInput") VALUES ${r} ON CONFLICT DO NOTHING`,values:t});await qt.none(a)}}static async count(t){const e=t.map((t=>t.outputSpent));const s=new _e({name:`Input.belong.${Math.random()}`,text:'SELECT count(*) FROM "Input" WHERE "outputSpent" LIKE ANY ($1)',values:[[e]]});const r=await qt.oneOrNone(s);return parseInt(r?.count,10)||0}}class De{static async select(t){return ke.select(t)}static async insert(t){return ke.insert(t)}}class Fe{static getNonCoinbaseRevs=t=>t.filter((t=>!h.isCoinbaseHash(t.input.hash))).map((({input:t,txId:e},s)=>{return{outputSpent:`${r=t.hash,u.reverseBuffer(Buffer.from(r)).toString("hex")}:${t.index}`,spendingInput:`${e}:${s}`};var r}));static insert=async t=>{De.insert(this.getNonCoinbaseRevs(t))}}let Ke;try{Ke=new I({chain:ht,network:yt,url:wt})}catch(t){Gt.error(`Error creating computer, ${t.message}`),process.exit(1)}class Ue{static syncTx=async t=>{await je.insert([t]),await Fe.insert(t.tx.ins.map((e=>({input:e,txId:t.txId})))),t.isBcTx(ht,yt)&&await pe.add(t)};static rawTxSubscriber=async t=>{const e=t.toString("hex");if(Gt.info(`ZMQ message { rawTx:${e} }`),"08"!==e.slice(10,12)){let t;try{t=await Ke.txFromHex({hex:e})}catch(t){Gt.error(`RawTxSubscriber failed with error '${t.message} ${t.stack}'`)}try{await this.syncTx(t)}catch(t){Gt.error(`Error parsing transaction ${t.message} ${t.stack}`)}}};static checkSyncStatus=async()=>{const t=await E((async()=>{const t=await Ie.getBlockchainInfo();const e=(100*parseFloat(t.result.verificationprogress)).toFixed(4);const{blocks:s}=t.result;if(Gt.info(`Zmq. Bitcoind { percentage:${e}%, blocks:${s} }`),parseFloat(t.result.verificationprogress)<=_t)throw new Error("Node not ready yet");return t}),{startingDelay:6e4,timeMultiple:1,numOfAttempts:8760});const e=(100*parseFloat(t.result.verificationprogress)).toFixed(4);const s=t.result.blocks;Gt.info(`BCN reaches sync end...at { bitcoind.progress:${e}%, bitcoindSyncedHeight:${s} }`)};static createWallet=async()=>{try{await Ie.createwallet(Mt,!1,!1,"",!1,!1)}catch(t){Gt.error(`Wallet creation failed with error '${t.message}'`)}};static sub=async t=>{try{await this.createWallet(),"regtest"!==yt&&await this.checkSyncStatus(),await(async()=>{if("regtest"===yt){if(Gt.info(`Node is starting for chain ${ht} and network ${yt}, \n\n. Starting Wallet setup.`),"LTC"===ht){const{result:t}=await Ie.getBlockchainInfo();const e=t.blocks;if(e{try{const t=Vt()?"bcn.test.config.json":"bcn.config.json";const e=x(N(import.meta.url));this.configFile=i.readFileSync(R.join(e,"..","..",t)),this.loaded=!0}catch(t){if(t.message.includes("ENOENT: no such file or directory"))return void(this.loaded=!0);throw Gt.error(`Access-list failed with error '${t.message}'`),t}};middleware=({url:t},e,s)=>{if(void 0!==e.locals.authToken)if(this.loaded||(Gt.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){Gt.error(`Authorization failed at ${t} with error: '${s.message}'`),e.status(403).json({error:s.message})}else s();else s()}};let Ze;try{Ze=r.createServer(qe)}catch(t){throw Gt.error(`Starting server failed with error '${t.message}'`),t}if(Gt.info(`Server listening on port ${pt}`),qe.use(e()),"dev"!==gt){const t=n({windowMs:9e5,max:300,standardHeaders:!0,legacyHeaders:!1});qe.use(t)}qe.use(t.json({limit:"100mb"})),qe.use(t.urlencoded({limit:"100mb",extended:!0})),qe.get("/",((t,e)=>e.status(200).send(`\n

Bitcoin Computer Node

\n Status: Healthy
\n Version: ${Nt}
\n Chain: ${ht}
\n Network: ${yt}\n `))),ze.loaded&&(qe.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 Gt.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>1e3*Pt*60)return void e.status(401).json({error:"Signature is too old."});const c=b.sha256().update(wt+i).digest("hex");if(!Je.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 Ge.select(o);if(u){if(u.clientTimestamp>=i)return void e.status(401).json({error:"Please use a fresh authentication token."});await Ge.update({publicKey:o,clientTimestamp:i})}else await Ge.insert({publicKey:o,clientTimestamp:i});e.locals.authToken=a,s()}catch(t){Gt.error(`Auth failed with error '${t.message}'`),e.status(401).json({error:t.message})}})),qe.use(ze.middleware));const Qe=(()=>{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 re.select(e))}catch(t){Gt.error(`GET ${e} failed with error '${t.message}'`),s.status(404).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 je.listSentOutputs(e))}catch(t){Gt.error(`GET ${e} failed with error '${t.message}'`),s.status(404).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 je.listReceivedOutputs(e))}catch(t){Gt.error(`GET ${e} failed with error '${t.message}'`),s.status(404).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 je.listTxs(e))}catch(t){Gt.error(`GET ${e} failed with error '${t.message}'`),s.status(404).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"),hash:s.get("hash"),limit:s.get("limit"),order:s.get("order"),offset:s.get("offset"),ids:JSON.parse(s.get("ids"))};const a=await pe.query(r);e.status(200).json(a)}catch(s){Gt.error(`GET ${t.url} failed with error '${s.messages}'`),e.status(404).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 re.getBalance(e))}catch(t){Gt.error(`GET ${e} failed with error '${t.message}'`),s.status(404).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(500).json({error:"Missing input txIds."});const e=await be.getRaw(t);e?s.status(200).json(e):s.status(404).json({error:"Not found"})}catch(t){Gt.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(500).json({error:"Missing input hex."});const e=await be.sendRaw(t);e?s.status(200).json(e):s.status(404).json({error:"Error Occured"})}catch(r){Gt.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 xe.getnewaddress();if("string"!=typeof t)throw new Error("Please provide appropriate count");return await xe.generatetoaddress(parseInt(t,10)||1,e),s.status(200).json({success:!0})}catch(t){return Gt.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 xe.getbestblockhash();e=t}const{result:r}=await xe.getblockheader(e,!0);return s.status(200).json({height:r.height})}catch(t){return Gt.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=(await xe.sendtoaddress(t,parseInt(e,10)/1e8,"","")).result;const a=process.env.TEST_ADDRESS.split(";")[0];await xe.generateToAddress(1,a);const n=(await xe.getrawtransaction(s,1)).result.vout.findIndex((t=>1e8*t.value===parseInt(e,10)));return r.status(200).json({txId:s,vout:n,height:-1,satoshis:e})}catch(t){return Gt.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=Le.makeRandom({network:Be});const a=p.p2pkh({pubkey:s.publicKey,network:Be});const{address:n}=a;const o=(await xe.sendtoaddress(n,2*parseInt(e,10)/1e8,"","")).result;let i;let c=10;for(;!i;)if(i=(await re.select(n)).filter((t=>t.txId===o))[0],!i){if(c-=1,c<=0)throw new Error("No outputs");await He(10)}const u=(await xe.getrawtransaction(i.txId,1)).result;const d=new m({network:Be});d.addInput({hash:i.txId,index:i.vout,nonWitnessUtxo:Buffer.from(u.hex,"hex")}),d.addOutput({script:Buffer.from(t,"hex"),value:parseInt(e,10)}),d.signInput(0,s),d.finalizeAllInputs();const l=d.extractTransaction();let h;for(await xe.sendrawtransaction(l.toHex()),c=5;!h;)if(h=(await re.selectByScriptHex(t)).filter((t=>t.txId===l.getId()))[0],!h){if(c-=1,c<=0)throw new Error("No outputs");await He(10)}return r.status(200).json({txId:l.getId(),vout:h.vout,height:-1,satoshis:h.satoshis})}catch(t){return Gt.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(500).json({error:"Missing input txId."});const e=await be.getRawJSON(t);e?s.status(200).json(e):s.status(404).json({error:"Not found"})}catch(t){Gt.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(404).json({error:"Missing input object ids."});const e=await pe.getRevsByIds(t);s.status(200).json(e)}catch(t){Gt.error(`POST ${e} failed with error '${t.message}'`),s.status(404).json({error:t.message})}})),t.post("/revToId",(async({body:{rev:t},url:e},s)=>{try{if(!ae(t))return void s.status(400).json({error:"Invalid rev id"});const e=await le.getId(t);e&&s.status(200).json(e),s.status(404).json()}catch(t){Gt.error(`POST ${e} failed with error '${t.message}'`),s.status(404).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(!jt.some((e=>e.test(t.method))))throw new Error("Method is not allowed");const e=function(t,e){if(void 0===Ne[t]||null===Ne[t])throw new Error("This RPC method does not exist, or not supported");const s=e.trim().split(" ");const r=Ne[t].trim().split(" ");if(0===e.trim().length&&0!==Ne[t].trim().length)throw new Error(`Too few params provided. Expected ${r.length} Provided 0`);if(0!==e.trim().length&&0===Ne[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)=>Me[r[e]](t)))}(t.method,t.params);const r=e.length?await xe[t.method](...e):await xe[t.method]();s.status(200).json({result:r})}catch(t){Gt.error(`POST ${e} failed with error '${t.message}'`),s.status(404).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})();qe.use(`/v1/${ht}/${yt}`,Qe),qe.use("/v1/store",Xt),Ze.listen(pt,(()=>{Gt.info(`Rev ${Nt} Started web server on port ${pt} BC_START_HEIGHT ${Bt}`)})).on("error",(t=>{Gt.error(t.message),process.exit(1)}));const Xe=new a.Subscriber;Xe.connect(mt),Xe.subscribe("rawtx"),Gt.info(`ZMQ Subscriber connected to ${mt}`),(async()=>{await(async()=>{await E((()=>qt.connect()),{startingDelay:At})})(),await Ue.sub(Xe)})(); diff --git a/packages/node/dist/bcn.sync.es.mjs b/packages/node/dist/bcn.sync.es.mjs index 50122d3ed..16eea16d2 100644 --- a/packages/node/dist/bcn.sync.es.mjs +++ b/packages/node/dist/bcn.sync.es.mjs @@ -1 +1 @@ -import{backOff as t}from"exponential-backoff";import{Computer as e}from"@bitcoin-computer/lib";import s from"dotenv";import n from"fs";import{networks as a,bufferUtils as r,crypto as o,Transaction as i,address as c,payments as d}from"@bitcoin-computer/nakamotojs";import p from"winston";import u from"winston-daily-rotate-file";import l from"bitcoind-rpc";import m from"util";import h from"pg-promise";import y from"pg-monitor";s.config();const E=JSON.parse(n.readFileSync("package.json","utf8"));function S(t,e){switch(t){case"BTC":return"mainnet"===e?a.bitcoin:a.testnet;case"LTC":return"mainnet"===e?a.litecoin:a.litecoinregtest;default:throw new Error("We currently only support BTC and LTC, support for other currencies will be added soon.")}}const{PORT:f,ZMQ_URL:w,CHAIN:g,NETWORK:$,BCN_ENV:I,BCN_URL:T,DEBUG_MODE:O,POSTGRES_USER:R,POSTGRES_PASSWORD:N,POSTGRES_DB:v,POSTGRES_HOST:x,POSTGRES_PORT:M,RPC_PROTOCOL:b,RPC_USER:C,RPC_PASSWORD:H,RPC_HOST:A,RPC_PORT:D,SERVER_VERSION:L,DEFAULT_WALLET:_,POSTGRES_MAX_PARAM_NUM:k,DB_CONNECTION_RETRY_TIME:P,SIGNATURE_FRESHNESS_MINUTES:Y,ALLOWED_RPC_METHODS:B,MAX_BLOCKCHAIN_HEIGHT:F,MWEB_HEIGHT:W,BC_START_HEIGHT:U,WORKER_ID:G,NUM_WORKERS:K,SYNC_NON_STANDARD:V,ZMQ_WAIT_PERCENTAGE:j,QUERY_LIMIT:z,LOG_MAX_FILE_SIZE:q,LOG_MAX_FILE_NUM:J,LOG_ZIP:Z}=process.env;const X=parseInt(f,10)||"1031";const Q=g||"LTC";const tt=$||"regtest";const et=I||"dev";const st=T||`http://127.0.0.1:${X}`;const nt=parseInt(O,10)||1;const at=R||"bcn";const rt=N||"bcn";const ot=v||"bcn";const it=x||"127.0.0.1";const ct=parseInt(M,10)||"5432";const dt=b||"http";const pt=C||"bcn-admin";const ut=H||"kH4nU5Okm6-uyC0_mA5ztVNacJqZbYd_KGLl6mx722A=";const lt=A||"node";const mt=parseInt(D,10)||19332;E.version;const ht=parseInt(k,10)||1e4;const yt=parseInt(P,10)||500;!B||B.split(",").map((t=>new RegExp(t)));const Et=parseInt(U||"",10)||25e5;const St=parseInt(G,10)||1;const ft=parseInt(K||"",10)||1;const wt="true"===V||!1;const gt=parseInt(z||"",10)||1e3;const $t=q||"20m";const It=J||"14d";const Tt=!!Z;p.addColors({error:"red",warn:"yellow",info:"green",http:"magenta",debug:"white"});const Ot=p.format.combine(p.format.colorize(),p.format.timestamp({format:"YYYY-MM-DD HH:mm:ss:ms"}),p.format.json(),p.format.printf((t=>`${t.timestamp} [${t.level.slice(5).slice(0,-5)}] ${t.message}`)));const Rt={zippedArchive:Tt,maxSize:$t,maxFiles:It,dirname:"logs"};const Nt=[];"dev"===et&&Nt.push(new p.transports.Console({format:p.format.combine(p.format.colorize(),p.format.timestamp({format:"MM-DD-YYYY HH:mm:ss"}),p.format.printf((t=>`${t.timestamp} ${t.level} ${t.message}`)))})),nt>=0&&Nt.push(new u({filename:"error-%DATE%.log",datePattern:"YYYY-MM-DD",level:"error",...Rt})),nt>=1&&Nt.push(new u({filename:"warn-%DATE%.log",datePattern:"YYYY-MM-DD",level:"warn",...Rt})),nt>=2&&Nt.push(new u({filename:"info-%DATE%.log",datePattern:"YYYY-MM-DD",level:"info",...Rt})),nt>=3&&Nt.push(new u({filename:"http-%DATE%.log",datePattern:"YYYY-MM-DD",level:"http",...Rt})),nt>=4&&Nt.push(new u({filename:"debug-%DATE%.log",datePattern:"YYYY-MM-DD",level:"debug",...Rt}));const vt=p.createLogger({levels:{error:0,warn:1,info:2,http:3,debug:4},format:Ot,transports:Nt,exceptionHandlers:[new p.transports.File({filename:"logs/exceptions.log"})],rejectionHandlers:[new p.transports.File({filename:"logs/rejections.log"})]});const xt=new l({protocol:dt,user:pt,pass:ut,host:lt,port:mt});const Mt=m.promisify(l.prototype.createwallet.bind(xt));const bt=m.promisify(l.prototype.generateToAddress.bind(xt));const Ct=m.promisify(l.prototype.getaddressinfo.bind(xt));const Ht=m.promisify(l.prototype.getBlock.bind(xt));const At=m.promisify(l.prototype.getBlockchainInfo.bind(xt));const Dt=m.promisify(l.prototype.getBlockHash.bind(xt));const Lt=m.promisify(l.prototype.getRawTransaction.bind(xt));const _t=m.promisify(l.prototype.getRawTransaction.bind(xt));const kt=m.promisify(l.prototype.getTransaction.bind(xt));const Pt=m.promisify(l.prototype.getNewAddress.bind(xt));const Yt={createwallet:Mt,generateToAddress:bt,getaddressinfo:Ct,getBlock:Ht,getBlockchainInfo:At,getBlockHash:Dt,getRawTransaction:Lt,getTransaction:kt,importaddress:m.promisify(l.prototype.importaddress.bind(xt)),listunspent:m.promisify(l.prototype.listunspent.bind(xt)),sendRawTransaction:m.promisify(l.prototype.sendRawTransaction.bind(xt)),getNewAddress:Pt,sendToAddress:m.promisify(l.prototype.sendToAddress.bind(xt)),getRawTransactionJSON:_t};const Bt={error:(t,e)=>{if(e.cn){const{host:s,port:n,database:a,user:r,password:o}=e.cn;vt.debug(`Waiting for db to start { message:${t.message} host:${s}, port:${n}, database:${a}, user:${r}, password: ${o}`)}},noWarnings:!0};"dev"===et&&nt>0&&(y.isAttached()?y.detach():(y.attach(Bt),y.setTheme("matrix")));const Ft=h(Bt)({host:it,port:ct,database:ot,user:at,password:rt,allowExitOnIdle:!0,idleTimeoutMillis:100});const{PreparedStatement:Wt}=h;class Ut{static async select(t){const e=new Wt({name:`SyncStatus.select.${Math.random()}`,text:'SELECT "syncedHeight" FROM "SyncStatus" WHERE "workerId" = $1',values:[t]});return Ft.one(e)}static async update({syncedHeight:t,workerId:e}){const s=new Wt({name:`SyncStatus.update.${Math.random()}`,text:'UPDATE "SyncStatus" SET "syncedHeight" = $1 WHERE "workerId" = $2',values:[t,e]});await Ft.any(s)}static async insert({syncedHeight:t,workerId:e}){const s=new Wt({name:`SyncStatus.insert.${Math.random()}`,text:'INSERT INTO "SyncStatus"("syncedHeight","workerId") VALUES ($1, $2) ON CONFLICT DO NOTHING',values:[t,e]});await Ft.any(s)}}class Gt{static async select(t){return Ut.select(t)}static async update(t){await Ut.update(t)}static async insert(t){await Ut.insert(t)}}class Kt{static updateSync=async t=>Gt.update(t);static selectSync=async t=>Gt.select(t);static insertSync=async t=>Gt.insert(t)}function Vt(t){if(!function(t){return/^[0-9A-Fa-f]{64}:\d+$/.test(t)}(t))throw new Error("Invalid rev")}const{PreparedStatement:jt}=h;class zt{static async query(t){const{publicKey:e,hash:s,limit:n,offset:a,order:r,ids:o,mod:i}=t;if(n&&parseInt(n||"",10)>gt||o&&o.length>gt)throw new Error(`Can't fetch more than ${gt} revs.`);if(r&&"ASC"!==r&&"DESC"!==r)throw new Error("Invalid order");let c;c=o?.length?'SELECT "rev", "id", array_position($1, "id") as ord\n FROM "NonStandard" \n WHERE true ':'SELECT "rev"\n FROM "NonStandard"\n WHERE true ';const d=[];s&&(d.push(s),c+=` AND "hash" = $${d.length}`),i&&(d.push(i),c+=` AND "mod" = $${d.length}`),o&&(o.map(Vt),d.push(o),c+=` AND "id" = ANY ($${d.length})`),e&&(d.push(e),c+=` AND $${d.length} = ANY ("publicKeys")`),r?(c+=` order by "lastUpdated" ${r}`,o?.length&&(c+=", ord")):o?.length&&(c+=" order by ord"),d.push(n||gt),c+=` limit $${d.length}`,a&&(d.push(a),c+=` offset $${d.length}`);const p=new jt({name:`NonStandard.query.${Math.random()}`,text:c,values:d});return(await Ft.any(p)).map((t=>t.rev))}static async insert({id:t,rev:e,publicKeys:s,hash:n,mod:a}){const r=new jt({name:`NonStandard.insert.${Math.random()}`,text:'INSERT INTO "NonStandard"("id", "rev", "publicKeys", "hash", "mod") VALUES ($1, $2, $3, $4, $5) ON CONFLICT DO NOTHING',values:[t,e,s,n,a]});await Ft.none(r)}static async update({id:t,rev:e,publicKeys:s}){const n=new jt({name:`NonStandard.update.${Math.random()}`,text:'UPDATE "NonStandard" SET "rev"=$2, "publicKeys"=$3 WHERE "id" = $1',values:[t,e,s]});return Ft.none(n)}static async delete({rev:t}){const e=new jt({name:`NonStandard.delete.${Math.random()}`,text:'DELETE FROM "NonStandard" WHERE "rev" = $1',values:[t]});await Ft.none(e)}static async getRevsByIds(t){if(t&&t.length>gt)throw new Error(`Can't fetch more than ${gt} revs.`);const e=new jt({name:`NonStandard.getRevsByIds.${Math.random()}`,text:'SELECT "rev" FROM "NonStandard" WHERE "id" LIKE ANY($1)',values:[[t]]});return Ft.any(e)}static async select(t){const e=new jt({name:`NonStandard.select.${Math.random()}`,text:'SELECT "id", "hash", "mod" FROM "NonStandard" WHERE "rev" = $1',values:[t]});return Ft.oneOrNone(e)}}class qt{static async select(t){return zt.select(t)}static async query(t){return zt.query(t)}static async getRevsByIds(t){return zt.getRevsByIds(t)}static async insert(t){return zt.insert(t)}static async update(t){return zt.update(t)}static async delete(t){return zt.delete({rev:t})}}const{PreparedStatement:Jt}=h;class Zt{static async getId(t){const e=new Jt({name:`RevToId.select.${Math.random()}`,text:'SELECT "id" FROM "RevToId" WHERE "rev" = $1',values:[t]});const s=await Ft.oneOrNone(e);return s?.id}static async insert(t){const e=new Jt({name:`RevToId.insert.${Math.random()}`,text:'INSERT INTO "RevToId"("rev", "id") VALUES ($1, $2) ON CONFLICT DO NOTHING',values:[t.rev,t.id]});await Ft.none(e)}}class Xt{static async getId(t){return Zt.getId(t)}static async insert(t){return Zt.insert(t)}}class Qt{static add=async t=>{const{zip:e,outData:s}=t;for(let t=0;tqt.query(t);static getRevsByIds=async t=>(await qt.getRevsByIds(t)).map((t=>t.rev))}const{PreparedStatement:te}=h;class ee{static async select(t){const e=new te({name:`Input.select.${Math.random()}`,text:'SELECT "outputSpent" FROM "Input" WHERE "outputSpent" = $1',values:[t]});return Ft.any(e)}static async insert(t){const e=t.flatMap((t=>[t.outputSpent,t.spendingInput]));for(;e.length;){const t=e.splice(0,ht);const s=[];for(let e=1;e<=t.length;e+=2)s.push(`($${e}, $${e+1})`);const n=s.join(",");const a=new te({name:`Input.insert.${Math.random()}`,text:`INSERT INTO "Input"("outputSpent", "spendingInput") VALUES ${n} ON CONFLICT DO NOTHING`,values:t});await Ft.none(a)}}static async count(t){const e=t.map((t=>t.outputSpent));const s=new te({name:`Input.belong.${Math.random()}`,text:'SELECT count(*) FROM "Input" WHERE "outputSpent" LIKE ANY ($1)',values:[[e]]});const n=await Ft.oneOrNone(s);return parseInt(n?.count,10)||0}}class se{static async select(t){return ee.select(t)}static async insert(t){return ee.insert(t)}}class ne{static getNonCoinbaseRevs=t=>t.filter((t=>!i.isCoinbaseHash(t.input.hash))).map((({input:t,txId:e},s)=>{return{outputSpent:`${n=t.hash,r.reverseBuffer(Buffer.from(n)).toString("hex")}:${t.index}`,spendingInput:`${e}:${s}`};var n}));static insert=async t=>{se.insert(this.getNonCoinbaseRevs(t))}}const{PreparedStatement:ae}=h;class re{static async listSentOutputs(t){const e=new ae({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 Ft.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listReceivedOutputs(t){const e=new ae({name:`Output.listReceivedTxs.${Math.random()}`,text:'SELECT "Output"."rev" as "output", "Output"."satoshis" as "amount" FROM "Output" WHERE "address" = $1',values:[t]});return(await Ft.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listTxs(t){const e=new ae({name:`Output.listTxs.${Math.random()}`,text:' -- List all txs sent from a given address\n WITH 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 \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 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 Ft.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 ae({name:`Output.select.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "rev" FROM "Output" WHERE "address" = $1',values:[t]});return Ft.any(e)}static async insert(t){const e=t.flatMap((t=>[t.rev,t.address,t.satoshis,t.scriptPubKey,t.publicKeys]));for(;e.length;){const t=e.splice(0,ht);const s=[];for(let e=1;e<=t.length;e+=5)s.push(`($${e}, $${e+1}, $${e+2}, $${e+3}, $${e+4})`);const n=s.join(",");const a=new ae({name:`Output.insert.${Math.random()}`,text:`INSERT INTO "Output"("rev", "address", "satoshis", "scriptPubKey", "publicKeys") VALUES ${n} ON CONFLICT DO NOTHING`,values:t});await Ft.none(a)}}}class oe{static async select(t){return re.select(t)}static async insert(t){return re.insert(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)}}class ie{static insert=async t=>{const e=t.flatMap((t=>t.tx.outs.map(((e,s)=>{const{script:n}=e;let a;let r;try{a=c.fromOutputScript(n,S(Q,tt))}catch(t){a=null}try{r=d.p2ms({output:n,network:S(Q,tt)}).pubkeys.map((t=>t.toString("hex"))),r.some((t=>t.length>66))&&(r=null)}catch(t){r=null}const o=n.toString("hex");const i=Math.round(e.value);return{address:a,rev:`${t.txId}:${s}`,scriptPubKey:o,satoshis:i,publicKeys:r}}))));return oe.insert(e)};static listSentOutputs=async t=>oe.listSentOutputs(t);static listReceivedOutputs=async t=>oe.listReceivedOutputs(t);static listTxs=async t=>oe.listTxs(t)}const ce=new e({chain:Q,network:tt,url:st});class de{static waitForBlockHash=async e=>(await t((async()=>{let t;try{t=await Yt.getBlockHash(e)}catch(t){throw vt.info(`Sync workerId ${St}: waiting for block ${e} ...`),t}return t}),{startingDelay:3e4,timeMultiple:1,numOfAttempts:720})).result;static syncBlock=async(t,e="LTC")=>{const s=await de.waitForBlockHash(t);const{result:n}=await Yt.getBlock(s,2);const{tx:a}=n;let r=a;"LTC"===e&&(r=a.filter((t=>"08"!==t.hex.slice(10,12))));const o=`Backfilling progress ${t} Backfilling ${r.length} txs `;"LTC"===e&&o.concat(`(${a.length-r.length} mweb tx's filtered)...`),vt.info(o);const i=await Promise.allSettled(r.map((t=>ce.txFromHex({hex:t.hex}))));const c=i.filter((t=>"fulfilled"===t.status)).map((t=>t.value));const d=i.filter((t=>"rejected"===t.status)).map((t=>t.reason));var p,u;d.length&&vt.error(`Failed to parse ${d.length} transactions of block num ${t}: ${d.map((t=>t)).join(", ")}\n Failed txs: ${p=r.map((t=>t.id)),u=c.map((t=>t.tx.getId())),p.filter((t=>-1===u.indexOf(t)))}`),await this.syncTxs(c,t)};static sync=async(t,e,s,n,a,r)=>{let o=e;if(n&&1!==s){const e=`Non standard worker ${t} is not supposed to sync non standard blocks with increment ${s}. Please check the configuration.`;throw vt.error(e),new Error(e)}if(!n){if((o-t)%a!=0){const e=`Worker ${t} is not supposed to sync block ${o}. Please check the configuration.`;throw vt.error(e),new Error(e)}if(s!==a){const e=`Worker ${t} is not supposed to sync with increment ${s}. Please check the configuration.`;throw vt.error(e),new Error(e)}}for(vt.info(`WorkerId ${t} starting sync on blockToSync: ${o} - \n increment: ${s} - sycNonStandard: ${n} - numWorkers: ${a} }`);n||o{try{await ie.insert(t),await ne.insert(t.flatMap((t=>t.tx.ins.map((e=>({input:e,txId:t.txId})))))),e>=Et&&t.map((async t=>{try{t.isBcTx(Q,tt)&&await Qt.add(t)}catch(e){vt.error(`Failed to add non-standard tx ${t.tx.getId()} ${e.message}`)}}))}catch(t){vt.error(`Processing block ${e} failed with error '${t.message}'`)}};static register=async t=>{try{await Kt.insertSync({syncedHeight:-1,workerId:t}),vt.info(`Register workerId: '${t}'`)}catch(t){vt.error(`Register action failed with error '${t.message}'`)}}}!function(){try{const e=`Synchronizing { nonStandard:${wt} url: ${st}, chain:${Q} network:${tt} numWorkers: ${ft} workerId: ${St} activationHeight: ${Et} }`;vt.info(e),"regtest"!==tt&&(async()=>{if(await(async()=>{await t((()=>Ft.connect()),{startingDelay:yt})})(),await de.register(St),wt)await de.sync(St,Et,1,wt,ft,Et);else{const t=await Kt.selectSync(St);const e=t.syncedHeight>0?t.syncedHeight+ft:St;await de.sync(St,e,ft,!1,ft,Et)}})()}catch(t){vt.error(`Synchronizing failed with error '${t.message}'`)}}(); +import{backOff as t}from"exponential-backoff";import{Computer as e}from"@bitcoin-computer/lib";import n from"dotenv";import s from"fs";import{networks as a,bufferUtils as r,crypto as o,Transaction as i,address as c,payments as d}from"@bitcoin-computer/nakamotojs";import u from"winston";import l from"winston-daily-rotate-file";import p from"bitcoind-rpc";import m from"util";import h from"pg-promise";import y from"pg-monitor";n.config();const S=JSON.parse(s.readFileSync("package.json","utf8"));function E(t,e){switch(t){case"BTC":return"mainnet"===e?a.bitcoin:a.testnet;case"LTC":return"mainnet"===e?a.litecoin:a.litecoinregtest;default:throw new Error("We currently only support BTC and LTC, support for other currencies will be added soon.")}}const{PORT:w,ZMQ_URL:$,CHAIN:f,NETWORK:I,BCN_ENV:T,BCN_URL:g,DEBUG_MODE:O,POSTGRES_USER:R,POSTGRES_PASSWORD:N,POSTGRES_DB:v,POSTGRES_HOST:M,POSTGRES_PORT:x,RPC_PROTOCOL:b,RPC_USER:C,RPC_PASSWORD:k,RPC_HOST:A,RPC_PORT:L,SERVER_VERSION:D,DEFAULT_WALLET:_,POSTGRES_MAX_PARAM_NUM:H,DB_CONNECTION_RETRY_TIME:P,SIGNATURE_FRESHNESS_MINUTES:B,ALLOWED_RPC_METHODS:Y,MAX_BLOCKCHAIN_HEIGHT:F,MWEB_HEIGHT:W,BC_START_HEIGHT:U,WORKER_ID:G,NUM_WORKERS:K,SYNC_NON_STANDARD:V,ZMQ_WAIT_PERCENTAGE:j,QUERY_LIMIT:z,LOG_MAX_FILE_SIZE:q,LOG_MAX_FILE_NUM:J,LOG_ZIP:Z}=process.env;const X=parseInt(w,10)||"1031";const Q=f||"LTC";const tt=I||"regtest";const et=T||"dev";const nt=g||`http://127.0.0.1:${X}`;const st=parseInt(O,10)||1;const at=R||"bcn";const rt=N||"bcn";const ot=v||"bcn";const it=M||"127.0.0.1";const ct=parseInt(x,10)||"5432";const dt=b||"http";const ut=C||"bcn-admin";const lt=k||"kH4nU5Okm6-uyC0_mA5ztVNacJqZbYd_KGLl6mx722A=";const pt=A||"node";const mt=parseInt(L,10)||19332;S.version;const ht=parseInt(H,10)||1e4;const yt=parseInt(P,10)||500;!Y||Y.split(",").map((t=>new RegExp(t)));const St=parseInt(U||"",10)||25e5;const Et=parseInt(G,10)||1;const wt=parseInt(K||"",10)||1;const $t="true"===V||!1;const ft=parseInt(z||"",10)||1e3;const It=q||"20m";const Tt=J||"14d";const gt=!!Z;u.addColors({error:"red",warn:"yellow",info:"green",http:"magenta",debug:"white"});const Ot=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 Rt={zippedArchive:gt,maxSize:It,maxFiles:Tt,dirname:"logs"};const Nt=[];"dev"===et&&Nt.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}`)))})),st>=0&&Nt.push(new l({filename:"error-%DATE%.log",datePattern:"YYYY-MM-DD",level:"error",...Rt})),st>=1&&Nt.push(new l({filename:"warn-%DATE%.log",datePattern:"YYYY-MM-DD",level:"warn",...Rt})),st>=2&&Nt.push(new l({filename:"info-%DATE%.log",datePattern:"YYYY-MM-DD",level:"info",...Rt})),st>=3&&Nt.push(new l({filename:"http-%DATE%.log",datePattern:"YYYY-MM-DD",level:"http",...Rt})),st>=4&&Nt.push(new l({filename:"debug-%DATE%.log",datePattern:"YYYY-MM-DD",level:"debug",...Rt}));const vt=u.createLogger({levels:{error:0,warn:1,info:2,http:3,debug:4},format:Ot,transports:Nt,exceptionHandlers:[new u.transports.File({filename:"logs/exceptions.log"})],rejectionHandlers:[new u.transports.File({filename:"logs/rejections.log"})]});const Mt=new p({protocol:dt,user:ut,pass:lt,host:pt,port:mt});const xt=m.promisify(p.prototype.createwallet.bind(Mt));const bt=m.promisify(p.prototype.generateToAddress.bind(Mt));const Ct=m.promisify(p.prototype.getaddressinfo.bind(Mt));const kt=m.promisify(p.prototype.getBlock.bind(Mt));const At=m.promisify(p.prototype.getBlockchainInfo.bind(Mt));const Lt=m.promisify(p.prototype.getBlockHash.bind(Mt));const Dt=m.promisify(p.prototype.getRawTransaction.bind(Mt));const _t=m.promisify(p.prototype.getRawTransaction.bind(Mt));const Ht=m.promisify(p.prototype.getTransaction.bind(Mt));const Pt=m.promisify(p.prototype.getNewAddress.bind(Mt));const Bt={createwallet:xt,generateToAddress:bt,getaddressinfo:Ct,getBlock:kt,getBlockchainInfo:At,getBlockHash:Lt,getRawTransaction:Dt,getTransaction:Ht,importaddress:m.promisify(p.prototype.importaddress.bind(Mt)),listunspent:m.promisify(p.prototype.listunspent.bind(Mt)),sendRawTransaction:m.promisify(p.prototype.sendRawTransaction.bind(Mt)),getNewAddress:Pt,sendToAddress:m.promisify(p.prototype.sendToAddress.bind(Mt)),getRawTransactionJSON:_t};const Yt={error:(t,e)=>{if(e.cn){const{host:n,port:s,database:a,user:r,password:o}=e.cn;vt.debug(`Waiting for db to start { message:${t.message} host:${n}, port:${s}, database:${a}, user:${r}, password: ${o}`)}},noWarnings:!0};"dev"===et&&st>0&&(y.isAttached()?y.detach():(y.attach(Yt),y.setTheme("matrix")));const Ft=h(Yt)({host:it,port:ct,database:ot,user:at,password:rt,allowExitOnIdle:!0,idleTimeoutMillis:100});const{PreparedStatement:Wt}=h;class Ut{static async select(t){const e=new Wt({name:`SyncStatus.select.${Math.random()}`,text:'SELECT "blockToSync", "nonStandard", "workerId" FROM "SyncStatus" WHERE "workerId" = $1',values:[t]});return Ft.oneOrNone(e)}static async update({blockToSync:t,workerId:e}){const n=new Wt({name:`SyncStatus.update.${Math.random()}`,text:'UPDATE "SyncStatus" SET "blockToSync" = $1 WHERE "workerId" = $2',values:[t,e]});await Ft.any(n)}static async count(){const t=new Wt({name:`SyncStatus.count.${Math.random()}`,text:'SELECT COUNT(*) FROM "SyncStatus"'});const e=await Ft.oneOrNone(t);return parseInt(e?.count,10)||0}static async min(t){const e=new Wt({name:`SyncStatus.min.${Math.random()}`,text:'SELECT MIN("blockToSync") FROM "SyncStatus" where "nonStandard" = $1',values:[t]});const n=await Ft.oneOrNone(e);return parseInt(n?.min,10)||0}static async delete(){const t=new Wt({name:`SyncStatus.delete.${Math.random()}`,text:'DELETE FROM "SyncStatus"'});await Ft.any(t)}static async insertBatch(t){const e=[];for(let n=1;n<=t.length;n+=3)e.push(`($${n}, $${n+1}, $${n+2})`);const n=e.join(",");const s=new Wt({name:`SyncStatus.reorg.${Math.random()}`,text:`INSERT INTO "SyncStatus"("workerId", "blockToSync", "nonStandard") VALUES ${n}`,values:t});await Ft.any(s)}}class Gt{static async select(t){return Ut.select(t)}static async update(t){await Ut.update(t)}static async count(){return Ut.count()}static async insertBatch(t){await Ut.insertBatch(t)}static async minStandard(){return Ut.min(!1)}static async minNonStandard(){return Ut.min(!0)}static async delete(){await Ut.delete()}}class Kt{static update=async t=>Gt.update(t);static select=async t=>Gt.select(t);static setup=async t=>{if(await Gt.count()===t)return;const e=[];const n=Math.max(St,await Gt.minNonStandard());let s=Math.max(1,await Gt.minStandard());for(let n=1;n{t((async()=>{if(await Gt.count()===e)return!0;throw new Error("Not all workers have reorged")}),{startingDelay:500})}}function Vt(t){if(!function(t){return/^[0-9A-Fa-f]{64}:\d+$/.test(t)}(t))throw new Error("Invalid rev")}const{PreparedStatement:jt}=h;class zt{static async query(t){const{publicKey:e,hash:n,limit:s,offset:a,order:r,ids:o,mod:i}=t;if(s&&parseInt(s||"",10)>ft||o&&o.length>ft)throw new Error(`Can't fetch more than ${ft} revs.`);if(r&&"ASC"!==r&&"DESC"!==r)throw new Error("Invalid order");let c;c=o?.length?'SELECT "rev", "id", array_position($1, "id") as ord\n FROM "NonStandard" \n WHERE true ':'SELECT "rev"\n FROM "NonStandard"\n WHERE true ';const d=[];n&&(d.push(n),c+=` AND "hash" = $${d.length}`),i&&(d.push(i),c+=` AND "mod" = $${d.length}`),o&&(o.map(Vt),d.push(o),c+=` AND "id" = ANY ($${d.length})`),e&&(d.push(e),c+=` AND $${d.length} = ANY ("publicKeys")`),r?(c+=` order by "lastUpdated" ${r}`,o?.length&&(c+=", ord")):o?.length&&(c+=" order by ord"),d.push(s||ft),c+=` limit $${d.length}`,a&&(d.push(a),c+=` offset $${d.length}`);const u=new jt({name:`NonStandard.query.${Math.random()}`,text:c,values:d});return(await Ft.any(u)).map((t=>t.rev))}static async insert({id:t,rev:e,publicKeys:n,hash:s,mod:a}){const r=new jt({name:`NonStandard.insert.${Math.random()}`,text:'INSERT INTO "NonStandard"("id", "rev", "publicKeys", "hash", "mod") VALUES ($1, $2, $3, $4, $5) ON CONFLICT DO NOTHING',values:[t,e,n,s,a]});await Ft.none(r)}static async update({id:t,rev:e,publicKeys:n}){const s=new jt({name:`NonStandard.update.${Math.random()}`,text:'UPDATE "NonStandard" SET "rev"=$2, "publicKeys"=$3 WHERE "id" = $1',values:[t,e,n]});return Ft.none(s)}static async delete({rev:t}){const e=new jt({name:`NonStandard.delete.${Math.random()}`,text:'DELETE FROM "NonStandard" WHERE "rev" = $1',values:[t]});await Ft.none(e)}static async getRevsByIds(t){if(t&&t.length>ft)throw new Error(`Can't fetch more than ${ft} revs.`);const e=new jt({name:`NonStandard.getRevsByIds.${Math.random()}`,text:'SELECT "rev" FROM "NonStandard" WHERE "id" LIKE ANY($1)',values:[[t]]});return Ft.any(e)}static async select(t){const e=new jt({name:`NonStandard.select.${Math.random()}`,text:'SELECT "id", "hash", "mod" FROM "NonStandard" WHERE "rev" = $1',values:[t]});return Ft.oneOrNone(e)}}class qt{static async select(t){return zt.select(t)}static async query(t){return zt.query(t)}static async getRevsByIds(t){return zt.getRevsByIds(t)}static async insert(t){return zt.insert(t)}static async update(t){return zt.update(t)}static async delete(t){return zt.delete({rev:t})}}const{PreparedStatement:Jt}=h;class Zt{static async getId(t){const e=new Jt({name:`RevToId.select.${Math.random()}`,text:'SELECT "id" FROM "RevToId" WHERE "rev" = $1',values:[t]});const n=await Ft.oneOrNone(e);return n?.id}static async insert(t){const e=new Jt({name:`RevToId.insert.${Math.random()}`,text:'INSERT INTO "RevToId"("rev", "id") VALUES ($1, $2) ON CONFLICT DO NOTHING',values:[t.rev,t.id]});await Ft.none(e)}}class Xt{static async getId(t){return Zt.getId(t)}static async insert(t){return Zt.insert(t)}}class Qt{static add=async t=>{const{zip:e,outData:n}=t;for(let t=0;tqt.query(t);static getRevsByIds=async t=>(await qt.getRevsByIds(t)).map((t=>t.rev))}const{PreparedStatement:te}=h;class ee{static async select(t){const e=new te({name:`Input.select.${Math.random()}`,text:'SELECT "outputSpent" FROM "Input" WHERE "outputSpent" = $1',values:[t]});return Ft.any(e)}static async insert(t){const e=t.flatMap((t=>[t.outputSpent,t.spendingInput]));for(;e.length;){const t=e.splice(0,ht);const n=[];for(let e=1;e<=t.length;e+=2)n.push(`($${e}, $${e+1})`);const s=n.join(",");const a=new te({name:`Input.insert.${Math.random()}`,text:`INSERT INTO "Input"("outputSpent", "spendingInput") VALUES ${s} ON CONFLICT DO NOTHING`,values:t});await Ft.none(a)}}static async count(t){const e=t.map((t=>t.outputSpent));const n=new te({name:`Input.belong.${Math.random()}`,text:'SELECT count(*) FROM "Input" WHERE "outputSpent" LIKE ANY ($1)',values:[[e]]});const s=await Ft.oneOrNone(n);return parseInt(s?.count,10)||0}}class ne{static async select(t){return ee.select(t)}static async insert(t){return ee.insert(t)}}class se{static getNonCoinbaseRevs=t=>t.filter((t=>!i.isCoinbaseHash(t.input.hash))).map((({input:t,txId:e},n)=>{return{outputSpent:`${s=t.hash,r.reverseBuffer(Buffer.from(s)).toString("hex")}:${t.index}`,spendingInput:`${e}:${n}`};var s}));static insert=async t=>{ne.insert(this.getNonCoinbaseRevs(t))}}const{PreparedStatement:ae}=h;class re{static async listSentOutputs(t){const e=new ae({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 Ft.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listReceivedOutputs(t){const e=new ae({name:`Output.listReceivedTxs.${Math.random()}`,text:'SELECT "Output"."rev" as "output", "Output"."satoshis" as "amount" FROM "Output" WHERE "address" = $1',values:[t]});return(await Ft.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listTxs(t){const e=new ae({name:`Output.listTxs.${Math.random()}`,text:' -- List all txs sent from a given address\n WITH 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 \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 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 n=(await Ft.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:n.filter((t=>t.satoshis<0)).map((t=>({...t,satoshis:Math.abs(t.satoshis)}))),receivedTxs:n.filter((t=>t.satoshis>=0))}}static async select(t){const e=new ae({name:`Output.select.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "rev" FROM "Output" WHERE "address" = $1',values:[t]});return Ft.any(e)}static async insert(t){const e=t.flatMap((t=>[t.rev,t.address,t.satoshis,t.scriptPubKey,t.publicKeys]));for(;e.length;){const t=e.splice(0,ht);const n=[];for(let e=1;e<=t.length;e+=5)n.push(`($${e}, $${e+1}, $${e+2}, $${e+3}, $${e+4})`);const s=n.join(",");const a=new ae({name:`Output.insert.${Math.random()}`,text:`INSERT INTO "Output"("rev", "address", "satoshis", "scriptPubKey", "publicKeys") VALUES ${s} ON CONFLICT DO NOTHING`,values:t});await Ft.none(a)}}}class oe{static async select(t){return re.select(t)}static async insert(t){return re.insert(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)}}class ie{static insert=async t=>{const e=t.flatMap((t=>t.tx.outs.map(((e,n)=>{const{script:s}=e;let a;let r;try{a=c.fromOutputScript(s,E(Q,tt))}catch(t){a=null}try{r=d.p2ms({output:s,network:E(Q,tt)}).pubkeys.map((t=>t.toString("hex"))),r.some((t=>t.length>66))&&(r=null)}catch(t){r=null}const o=s.toString("hex");const i=Math.round(e.value);return{address:a,rev:`${t.txId}:${n}`,scriptPubKey:o,satoshis:i,publicKeys:r}}))));return oe.insert(e)};static listSentOutputs=async t=>oe.listSentOutputs(t);static listReceivedOutputs=async t=>oe.listReceivedOutputs(t);static listTxs=async t=>oe.listTxs(t)}const ce=new e({chain:Q,network:tt,url:nt});class de{static waitForBlockHash=async e=>(await t((async()=>{let t;try{t=await Bt.getBlockHash(e)}catch(t){throw vt.info(`Sync workerId ${Et}: waiting for block ${e} ...`),t}return t}),{startingDelay:3e4,timeMultiple:1,numOfAttempts:720})).result;static syncBlock=async(t,e="LTC")=>{const n=await de.waitForBlockHash(t);const{result:s}=await Bt.getBlock(n,2);const{tx:a}=s;let r=a;"LTC"===e&&(r=a.filter((t=>"08"!==t.hex.slice(10,12))));const o=`Backfilling progress ${t} Backfilling ${r.length} txs `;"LTC"===e&&o.concat(`(${a.length-r.length} mweb tx's filtered)...`),vt.info(o);const i=await Promise.allSettled(r.map((t=>ce.txFromHex({hex:t.hex}))));const c=i.filter((t=>"fulfilled"===t.status)).map((t=>t.value));const d=i.filter((t=>"rejected"===t.status)).map((t=>t.reason));var u,l;d.length&&vt.error(`Failed to parse ${d.length} transactions of block num ${t}: ${d.map((t=>t)).join(", ")}\n Failed txs: ${u=r.map((t=>t.id)),l=c.map((t=>t.tx.getId())),u.filter((t=>-1===l.indexOf(t)))}`),await this.syncTxs(c,t)};static sync=async(t,e,n,s,a)=>{for(;s||e{try{await ie.insert(t),await se.insert(t.flatMap((t=>t.tx.ins.map((e=>({input:e,txId:t.txId})))))),e>=St&&t.map((async t=>{try{t.isBcTx(Q,tt)&&await Qt.add(t)}catch(e){vt.error(`Failed to add non-standard tx ${t.tx.getId()} ${e.message}`)}}))}catch(t){vt.error(`Processing block ${e} failed with error '${t.message}'`)}}}!function(){try{const e=`Synchronizing { nonStandard:${$t} url: ${nt}, chain:${Q} network:${tt} numWorkers: ${wt} workerId: ${Et} activationHeight: ${St} }`;vt.info(e),"regtest"!==tt&&(async()=>{const e=wt-1;if(await(async()=>{await t((()=>Ft.connect()),{startingDelay:yt})})(),vt.info(`Worker ${Et} connected to the database successfully. Setting up syncStatus table...`),wt<2)throw new Error(`At least two workers are needed to sync the blockchain. Please check the configuration. ${wt} workers are configured.`);1===Et?await Kt.setup(wt):await Kt.waitUntilSetup(wt);const n=await Kt.select(Et);const s=n.nonStandard?1:e;vt.info(`WorkerId ${n.workerId} starting sync on blockToSync: ${n.blockToSync} - \n increment: ${s} - sycNonStandard: ${n.nonStandard} - numWorkers: ${wt} - num standard workers: ${e}`),await de.sync(n.workerId,n.blockToSync,s,n.nonStandard,St)})()}catch(t){vt.error(`Synchronizing failed with error '${t.message}'`)}}(); diff --git a/packages/node/docker-compose.yml b/packages/node/docker-compose.yml index 671cb106f..f0ad4cbab 100644 --- a/packages/node/docker-compose.yml +++ b/packages/node/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3" services: db: image: postgres diff --git a/packages/node/scripts/reset.sh b/packages/node/scripts/reset.sh index e75c242f1..ba7065500 100755 --- a/packages/node/scripts/reset.sh +++ b/packages/node/scripts/reset.sh @@ -18,8 +18,6 @@ TRUNCATE TABLE "Output"; TRUNCATE TABLE "User"; TRUNCATE TABLE "OffChain"; TRUNCATE TABLE "SyncStatus"; - -INSERT INTO "SyncStatus" ("syncedHeight", "workerId") VALUES (-1, 1); EOF # stop all docker containers and networks diff --git a/packages/node/scripts/up.py b/packages/node/scripts/up.py index 2a040c792..b45aca257 100755 --- a/packages/node/scripts/up.py +++ b/packages/node/scripts/up.py @@ -9,27 +9,25 @@ def runSync(args, commandLine): if(args.cpus is not None): - # numWorkers = cpus - db, node + nonSt - numWorkers = args.cpus - 3 if args.cpus - 3 > 0 else 1 + # numWorkers = #cpus - dbService + nodeSercice + nonStandardWorker + numStandardWorkers = args.cpus - 3 if args.cpus - 3 > 0 else 1 else: - numWorkers = multiprocessing.cpu_count() - 3 if multiprocessing.cpu_count() - 3 > 0 else 1 + numStandardWorkers = multiprocessing.cpu_count() - 3 if multiprocessing.cpu_count() - 3 > 0 else 1 standardTxCommand = '' - print('Launching '+str(numWorkers+1)+' workers.') - for worker in range(numWorkers): + numWorkers = numStandardWorkers + 1 + print('Launching '+str(numStandardWorkers)+' standard workers and 1 non-standard worker. ') + for worker in range(numStandardWorkers): standardTxCommand+=syncCommandLine('false', str(worker+1), str(numWorkers), commandLine)+ ' && ' - nonStandardTxCommand = syncCommandLine('true', str(numWorkers+1), '1', commandLine) + nonStandardTxCommand = syncCommandLine('true', str(numWorkers), str(numWorkers), commandLine) parallelCommand = standardTxCommand + nonStandardTxCommand p = subprocess.run( ['sh', '-c', parallelCommand], ) def syncCommandLine(nonStandard, workerId, numWorkers, commandLine): - if (nonStandard == 'true'): - return 'export SYNC_NON_STANDARD='+nonStandard+' WORKER_ID='+str(workerId)+' ; '+ commandLine+' run -d sync' - else: - return 'export SYNC_NON_STANDARD='+nonStandard+' WORKER_ID='+str(workerId)+' NUM_WORKERS='+str(numWorkers)+'; '+ commandLine+' run -d sync' + return 'export SYNC_NON_STANDARD='+nonStandard+' WORKER_ID='+str(workerId)+' NUM_WORKERS='+str(numWorkers)+'; '+ commandLine+' run -d sync' def main(): @@ -53,7 +51,6 @@ def main(): parser.set_defaults(service='') parser.add_argument('-cpus', dest="cpus",type=int) - parser.add_argument('-optimize', action="store_true") args = parser.parse_args() @@ -74,19 +71,12 @@ def main(): # testnet or mainnet url = subprocess.check_output("grep BCN_URL .env | cut -d '=' -f2", shell=True).decode("utf-8").strip() bcnUrl = url if url != '' else 'https://rltc.node.bitcoincomputer.io' - if(args.optimize): - # Optimize for speed: skip launching bcn service (no port binding) - subprocess.run( - ['sh', '-c', commandLine+' run -d -e BCN_URL='+bcnUrl+' bcn']) - # Launch sync in automatic parallel mode. If any service is specified, don't launch sync services - if(args.service.strip() == ''): - runSync(args, commandLine) - else: - subprocess.run( - ['sh', '-c', commandLine+' run -d -e BCN_URL='+bcnUrl+' -p {0}:{0} bcn'.format(bcnPort)]) - # If any service is specified, don't launch sync services - if(args.service.strip() == ''): - runSync(args, commandLine) + + subprocess.run( + ['sh', '-c', commandLine+' run -d -e BCN_URL='+bcnUrl+' -p {0}:{0} bcn'.format(bcnPort)]) + # If any service is specified, don't launch sync services + if(args.service.strip() == ''): + runSync(args, commandLine) if __name__ == '__main__': main() \ No newline at end of file