From e461ff23f55476ca874e93505a22cb49371ac239 Mon Sep 17 00:00:00 2001 From: ltardivo Date: Tue, 23 Jul 2024 15:00:00 -0500 Subject: [PATCH] Fixes query with no paramters --- packages/node/dist/bcn.es.mjs | 2 +- packages/node/dist/bcn.sync.es.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node/dist/bcn.es.mjs b/packages/node/dist/bcn.es.mjs index 70f9b401c..08a6d6cf4 100644 --- a/packages/node/dist/bcn.es.mjs +++ b/packages/node/dist/bcn.es.mjs @@ -1 +1 @@ -import t from"body-parser";import e from"cors";import s from"express";import r from"http";import*as a from"zeromq";import n from"express-rate-limit";import*as o from"@bitcoin-computer/secp256k1";import{crypto as i,networks as c,bufferUtils as u,address as p,payments as l,Psbt as d,Transaction as m,initEccLib as h}from"@bitcoin-computer/nakamotojs";import y from"dotenv";import g from"winston";import w from"winston-daily-rotate-file";import f from"pg-promise";import v from"pg-monitor";import{backOff as E}from"exponential-backoff";import T from"fs";import{ECPairFactory as O}from"ecpair";import S from"bitcoind-rpc";import $ from"util";import{Computer as R}from"@bitcoin-computer/lib";import I from"elliptic";import b from"hash.js";import x,{dirname as M}from"path";import{fileURLToPath as N}from"url";y.config();const P=process.env.CHAIN;const A=process.env.NETWORK;const{PORT:L}=process.env;const{POSTGRES_USER:C}=process.env;const{POSTGRES_PASSWORD:B}=process.env;const{POSTGRES_DB:j}=process.env;const{POSTGRES_HOST:H}=process.env;const{POSTGRES_PORT:D}=process.env;const{RPC_USER:F}=process.env;const{RPC_PASSWORD:U}=process.env;process.env;const{RPC_HOST:k}=process.env;const{RPC_PORT:_}=process.env;const{RPC_PROTOCOL:K}=process.env;const{ZMQ_URL:W}=process.env;const{DEFAULT_WALLET:Y}=process.env;const{ALLOWED_RPC_METHODS:G}=process.env;const{DEBUG_MODE:q}=process.env;const{LOG_MAX_FILES:J}=process.env;const{LOG_MAX_SIZE:V}=process.env;const{LOG_ZIP:z}=process.env;const{SHOW_CONSOLE_LOGS:Z}=process.env;const{SHOW_DB_LOGS:Q}=process.env;const{RATE_LIMIT_ENABLED:X}=process.env;const{RATE_LIMIT_WINDOW:tt}=process.env;const{RATE_LIMIT_MAX:et}=process.env;const{RATE_LIMIT_STANDARD_HEADERS:st}=process.env;const{RATE_LIMIT_LEGACY_HEADERS:rt}=process.env;process.env,process.env;const{OFFCHAIN_PROTOCOL:at}=process.env;const nt=process.env.QUERY_LIMIT||"1000";const ot=process.env.BCN_URL||`http://127.0.0.1:${L}`;const it=process.env.BCN_ENV||"dev";g.addColors({error:"red",warn:"yellow",info:"green",http:"magenta",debug:"white"});const ct=g.format.combine(g.format.colorize(),g.format.timestamp({format:"YYYY-MM-DD HH:mm:ss:ms"}),g.format.json(),g.format.printf((t=>`${t.timestamp} [${t.level.slice(5).slice(0,-5)}] ${t.message}`)));const ut={zippedArchive:"true"===z,maxSize:V,maxFiles:J,dirname:"logs"};const pt=[];"true"===Z&&pt.push(new g.transports.Console({format:g.format.combine(g.format.colorize(),g.format.timestamp({format:"MM-DD-YYYY HH:mm:ss"}),g.format.printf((t=>`${t.timestamp} ${t.level} ${t.message}`)))}));const lt=parseInt(q,10);lt>=0&&pt.push(new w({filename:"error-%DATE%.log",datePattern:"YYYY-MM-DD",level:"error",...ut})),lt>=1&&pt.push(new w({filename:"warn-%DATE%.log",datePattern:"YYYY-MM-DD",level:"warn",...ut})),lt>=2&&pt.push(new w({filename:"info-%DATE%.log",datePattern:"YYYY-MM-DD",level:"info",...ut})),lt>=3&&pt.push(new w({filename:"http-%DATE%.log",datePattern:"YYYY-MM-DD",level:"http",...ut})),lt>=4&&pt.push(new w({filename:"debug-%DATE%.log",datePattern:"YYYY-MM-DD",level:"debug",...ut}));const dt=g.createLogger({levels:{error:0,warn:1,info:2,http:3,debug:4},format:ct,transports:pt,exceptionHandlers:[new g.transports.File({filename:"logs/exceptions.log"})],rejectionHandlers:[new g.transports.File({filename:"logs/rejections.log"})]});y.config();const{version:mt}=JSON.parse(T.readFileSync("package.json","utf8"));const ht=mt||process.env.SERVER_VERSION;const yt=parseInt(process.env.MWEB_HEIGHT||"",10)||432;const gt={error:(t,e)=>{if(e.cn){const{host:s,port:r,database:a,user:n,password:o}=e.cn;dt.debug(`Waiting for db to start { message:${t.message} host:${s}, port:${r}, database:${a}, user:${n}, password: ${o}`)}},noWarnings:!0};"true"===Q&&(v.isAttached()?v.detach():(v.attach(gt),v.setTheme("matrix")));const wt=f(gt)({host:H,port:parseInt(D,10),database:j,user:C,password:B,allowExitOnIdle:!0,idleTimeoutMillis:100});const{PreparedStatement:ft}=f;class vt{static async select(t){const e=new ft({name:`OffChain.select.${Math.random()}`,text:'SELECT "data" FROM "OffChain" WHERE "id" = $1',values:[t]});return wt.oneOrNone(e)}static async insert({id:t,data:e}){const s=new ft({name:`OffChain.insert.${Math.random()}`,text:'INSERT INTO "OffChain" ("id", "data") VALUES ($1, $2) ON CONFLICT DO NOTHING',values:[t,e]});return wt.none(s)}static async delete(t){const e=new ft({name:`OffChain.delete.${Math.random()}`,text:'WITH deleted AS (DELETE FROM "OffChain" WHERE "id" = $1 RETURNING *) SELECT count(*) FROM deleted;',values:[t]});return(await wt.any(e))[0].count>0}}class Et{static async select(t){const e=await vt.select(t);return e?.data||null}static async insert(t){return vt.insert(t)}static async delete(t){return vt.delete(t)}}const Tt=s.Router();Tt.get("/:id",(async({params:{id:t},url:e},s)=>{try{const e=await Et.select(t);e?s.status(200).json(e):s.status(403).json({error:"No entry found."})}catch(t){dt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),Tt.post("/",(async(t,e)=>{const{body:{data:s},url:r}=t;try{const r=i.sha256(Buffer.from(s)).toString("hex");await Et.insert({id:r,data:s});const a=`${at||t.protocol}://${t.get("host")}/store/${r}`;e.status(201).json({_url:a})}catch(t){dt.error(`POST ${r} failed with error '${t.message}'`),e.status(500).json({error:t.message})}})),Tt.delete("/:id",(async(t,e)=>{e.status(500).json({error:"Deletions are not supported yet."})}));const{PreparedStatement:Ot}=f;class St{static async getBalance(t){const e=new Ot({name:`Utxos.getBalance.${Math.random()}`,text:'SELECT sum("satoshis") as "satoshis" FROM "Utxos" WHERE "address" = $1',values:[t]});const s=await wt.oneOrNone(e);return parseInt(s?.satoshis,10)||0}static async select(t){const e=new Ot({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 wt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)||0})))}static async selectByScriptHex(t){const e=new Ot({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 wt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)||0})))}static async selectByPk(t){const e=new Ot({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 wt.any(e)).map((t=>({...t,satoshis:parseInt(t.satoshis,10)})))}}class $t{static async getBalance(t){return St.getBalance(t)}static async select(t){return St.select(t)}static async selectByScriptHex(t){return St.selectByScriptHex(t)}static async selectByPk(t){return St.selectByPk(t)}}class Rt{static getBalance=async t=>$t.getBalance(t);static select=async t=>$t.select(t);static selectByScriptHex=async t=>$t.selectByScriptHex(t);static selectByPk=async t=>$t.selectByPk(t)}const It={protocol:K,user:F,pass:U,host:k,port:parseInt(_,10)};const bt=new S(It);const xt=$.promisify(S.prototype.createwallet.bind(bt));const Mt=$.promisify(S.prototype.generateToAddress.bind(bt));const Nt=$.promisify(S.prototype.getaddressinfo.bind(bt));const Pt=$.promisify(S.prototype.getBlock.bind(bt));const At=$.promisify(S.prototype.getBlockchainInfo.bind(bt));const Lt=$.promisify(S.prototype.getBlockHash.bind(bt));const Ct=$.promisify(S.prototype.getRawTransaction.bind(bt));const Bt=$.promisify(S.prototype.getRawTransaction.bind(bt));const jt=$.promisify(S.prototype.getTransaction.bind(bt));const Ht=$.promisify(S.prototype.getNewAddress.bind(bt));const Dt={createwallet:xt,generateToAddress:Mt,getaddressinfo:Nt,getBlock:Pt,getBlockchainInfo:At,getBlockHash:Lt,getRawTransaction:Ct,getTransaction:jt,importaddress:$.promisify(S.prototype.importaddress.bind(bt)),listunspent:$.promisify(S.prototype.listunspent.bind(bt)),sendRawTransaction:$.promisify(S.prototype.sendRawTransaction.bind(bt)),getNewAddress:Ht,sendToAddress:$.promisify(S.prototype.sendToAddress.bind(bt)),getRawTransactionJSON:Bt};const Ft=(t,e)=>{const s=[];for(let r=0;r{const e=[];for(let s=1;s<=t;s+=2){const t=`($${s},$${s+1})`;e.push(t)}return e.join(",")};const kt=t=>{const e=[];for(let s=1;s<=t;s+=9){const t=`($${s},$${s+1},$${s+2},$${s+3},$${s+4},$${s+5},$${s+6},$${s+7},$${s+8})`;e.push(t)}return e.join(",")};const _t=t=>{try{return t()}catch{return null}};class Kt{static async getTransaction(t){const{result:e}=await Dt.getTransaction(t);return e}static async getBulkTransactions(t){return(await Promise.all(t.map((t=>Dt.getRawTransaction(t))))).map((t=>t.result))}static async getRawTransactionsJSON(t){return{txId:(e=(await Dt.getRawTransactionJSON(t,1)).result).txid,txHex:e.hex,vsize:e.vsize,version:e.version,locktime:e.locktime,ins:e.vin.map((t=>t.coinbase?{coinbase:t.coinbase,sequence:t.sequence}:{txId:t.txid,vout:t.vout,script:t.scriptSig.hex,sequence:t.sequence})),outs:e.vout.map((t=>{let e;return t.scriptPubKey.addresses?[e]=t.scriptPubKey.addresses:e=t.scriptPubKey.address?t.scriptPubKey.address:void 0,{address:e,script:t.scriptPubKey.hex,value:Math.round(1e8*t.value)}}))};var e}static async sendRawTransaction(t){const{result:e,error:s}=await Dt.sendRawTransaction(t);if(s)throw dt.error(s),new Error("Error sending transaction");return e}static getUtxos=async t=>(void 0===(await Dt.getaddressinfo(t)).result.timestamp&&(dt.info(`Importing address: ${t}`),await Dt.importaddress(t,!1)),(await Dt.listunspent(0,999999,[t])).result)}class Wt{static get=async t=>Kt.getTransaction(t);static getRaw=async t=>Kt.getBulkTransactions(t);static getRawJSON=async t=>Kt.getRawTransactionsJSON(t);static sendRaw=async t=>Kt.sendRawTransaction(t);static getUtxos=async t=>Kt.getUtxos(t)}const Yt={protocol:K,user:F,pass:U,host:k,port:parseInt(_,10)};const Gt=new S(Yt);const qt={};const Jt=JSON.parse(JSON.stringify(S.callspec));Object.keys(Jt).forEach((t=>{Jt[t.toLowerCase()]=Jt[t]}));const Vt={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(S.prototype).forEach((t=>{if(t&&"function"==typeof S.prototype[t]){const e=t.toLowerCase();qt[t]=$.promisify(S.prototype[t].bind(Gt)),qt[e]=$.promisify(S.prototype[e].bind(Gt))}}))}catch(t){dt.error(`Error occurred while binding RPC methods: ${t.message}`)}function zt(t){return/^[0-9A-Fa-f]{64}:\d+$/.test(t)}function Zt(t){if(!zt(t))throw new Error("Invalid rev")}const{PreparedStatement:Qt}=f;class Xt{static async listSentOutputs(t){const e=new Qt({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 wt.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listReceivedOutputs(t){const e=new Qt({name:`Output.listReceivedTxs.${Math.random()}`,text:'SELECT "Output"."rev" as "output", "Output"."satoshis" as "amount" FROM "Output" WHERE "address" = $1',values:[t]});return(await wt.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listTxs(t){const e=new Qt({name:`Output.listTxs.${Math.random()}`,text:'WITH\n -- List all txs sent from a given address\n SENT AS (\n SELECT split_part("Input"."spendingInput",\':\',1) as "txId", SUM("Output".satoshis) as "satoshis"\n FROM "Output" INNER JOIN "Input" ON "Output".rev = "Input"."outputSpent" \n WHERE "Output".address = $1\n GROUP BY split_part("Input"."spendingInput",\':\',1)\n ),\n -- List all tx received from a given address\n RECEIVED AS (\n SELECT SPLIT_PART("Output"."rev",\':\',1) as "txId", SUM("Output"."satoshis") as "satoshis" \n FROM "Output" \n WHERE "address" = $1\n GROUP BY "txId"\n )\n\n SELECT\n RECEIVED."txId", \n coalesce(SENT."satoshis", 0) as "inputsSatoshis", \n coalesce(RECEIVED."satoshis", 0) as "outputsSatoshis", \n coalesce(RECEIVED."satoshis",0) - coalesce(SENT."satoshis",0) as "satoshis"\n FROM\n SENT RIGHT JOIN RECEIVED ON SENT."txId" = RECEIVED."txId";',values:[t]});const s=(await wt.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 Qt({name:`Output.select.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "rev", "publicKeys", "hash", "mod", "isTbcOutput", "previous" FROM "Output" WHERE "address" = $1',values:[t]});return wt.any(e)}static async insert(t){await Promise.all(Ft(t,1111).map((t=>{const e=t.flatMap((({rev:t,address:e,satoshis:s,scriptPubKey:r,isTbcOutput:a,publicKeys:n,mod:o,previous:i,hash:c})=>[t,e,s,r,a,n,o,i,c]));return wt.none(new Qt({name:`Output.insert.${Math.random()}`,text:`INSERT INTO "Output"("rev", "address", "satoshis", "scriptPubKey", "isTbcOutput", "publicKeys", "mod", "previous", "hash") VALUES ${kt(e.length)} ON CONFLICT DO NOTHING`,values:e}))})))}static async getIdByRev(t){const e=new Qt({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON r."previous" = o."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=(await wt.any(e)).filter((t=>null===t.previous));return s[0]?.rev}static async getIdsByRevs(t){return Promise.all(t.map((t=>this.getIdByRev(t))))}static async getLatestRev(t){const e=new Qt({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON o."previous" = r."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=await wt.any(e);const r=Object.fromEntries(s.map((t=>[t.previous,t.rev])));let a=t;for(;r[a];)a=r[a];return a}static async getLatestRevs(t){return Promise.all(t.map(this.getLatestRev))}static async getIdsByMod(t){const e=new Qt({name:`Output.getIdsByMod.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1',values:[t]});return(await wt.any(e)).map((t=>t.rev))}static sqlSuffix(t,e,s){let r="";return s&&(r+=` order by "timestamp" ${s}`),r+=` limit ${t||nt}`,e&&(r+=` offset ${e}`),r}static async getRevsByPublicKey(t){const e=new Qt({name:`Output.getRevsByPublicKey.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE $1 = ANY("publicKeys")',values:[t]});return(await wt.any(e)).map((t=>t.rev))}static async getUnspentRevsByMod(t,e,s,r){const a=await this.getIdsByMod(t);const n=await this.getLatestRevs(a);const o=new Qt({name:`Output.getUnspentRevsByMod.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(e,s,r)}`,values:[n]});return(await wt.any(o)).map((t=>t.rev))}static async getUnspentRevsByPublicKey(t,e,s,r){const a=new Qt({name:`Output.getUnspentRevsByPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE $1 = ANY("publicKeys") AND "isTbcOutput" = true \n AND NOT EXISTS (SELECT 1 FROM "Input" ip WHERE "ip"."outputSpent" = "Output"."rev") \n ${this.sqlSuffix(e,s,r)}`,values:[t]});return(await wt.any(a)).map((t=>t.rev))}static async getUnspentRevsByModAndPublicKey(t,e,s,r,a){const n=await this.getUnspentRevsByPublicKey(e,s,r,a);const o=await this.getIdsByRevs(n);const i=new Qt({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1 AND "rev" = ANY($2)',values:[t,o]});const c=(await wt.any(i)).map((t=>t.rev));const u=await this.getLatestRevs(c);const p=new Qt({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(s,r,a)}`,values:[u]});return(await wt.any(p)).map((t=>t.rev))}static async query(t){const{publicKey:e,limit:s,offset:r,ids:a,mod:n,order:o}=t;const i=parseInt(nt||"",10);if(s&&parseInt(s||"",10)>i||a&&a.length>i)throw new Error(`Can't fetch more than ${nt} revs.`);if(o&&"ASC"!==o&&"DESC"!==o)throw new Error("Invalid order. Should be ASC or DESC.");return a?.length?(a.map(Zt),this.getLatestRevs(a)):n&&!e?this.getUnspentRevsByMod(n,s,r,o):!n&&e?this.getUnspentRevsByPublicKey(e,s,r,o):n&&e?this.getUnspentRevsByModAndPublicKey(n,e,s,r,o):[]}}class te{static async select(t){return Xt.select(t)}static async insert(t){return Xt.insert(t)}static async listSentOutputs(t){return Xt.listSentOutputs(t)}static async listReceivedOutputs(t){return Xt.listReceivedOutputs(t)}static async listTxs(t){return Xt.listTxs(t)}static async getLatestRev(t){return Xt.getLatestRev(t)}static async query(t){return Xt.query(t)}}class ee{static insert=async t=>{const e=function(t=P,e=A){switch(t){case"BTC":switch(e){case"mainnet":return c.bitcoin;case"testnet":return c.testnet;case"regtest":return c.regtest;default:throw new Error(`Invalid network ${e}`)}case"LTC":switch(e){case"mainnet":return c.litecoin;case"testnet":return c.litecointestnet;case"regtest":return c.litecoinregtest;default:throw new Error(`Invalid network ${e}`)}case"PEPE":switch(e){case"mainnet":return c.pepecoin;case"testnet":return c.pepecointestnet;case"regtest":return c.pepecoinregtest;default:throw new Error(`Invalid network ${e}`)}default:throw new Error(`Invalid chain ${t}`)}}(P,A);const s=t.flatMap((t=>{const{zip:s,ownerData:r,onChainMetaData:a}=t;const{exp:n="",mod:o=""}=a;return t.tx.outs.map((({script:a,value:c},u)=>{const l=up.fromOutputScript(a,e))),satoshis:Math.round(c),scriptPubKey:a.toString("hex"),isTbcOutput:l,publicKeys:l?r[u]._owners:[],mod:l?o:"",previous:l?s[u][0]:null,hash:l?i.sha256(Buffer.from(n||"")).toString("hex"):null}}))}));return te.insert(s)};static listSentOutputs=async t=>te.listSentOutputs(t);static listReceivedOutputs=async t=>te.listReceivedOutputs(t);static listTxs=async t=>te.listTxs(t);static getLatestRev=async t=>te.getLatestRev(t);static query=async t=>te.query(t)}const se=t=>new Promise((e=>setTimeout(e,t)));const re=O(o);const ae=c.regtest;const{PreparedStatement:ne}=f;class oe{static async select(t){const e=new ne({name:`Input.select.${Math.random()}`,text:'SELECT "outputSpent" FROM "Input" WHERE "outputSpent" = $1',values:[t]});return wt.any(e)}static async insert(t){await Promise.all(Ft(t,5e3).map((t=>{const e=t.flatMap((({outputSpent:t,spendingInput:e})=>[t,e]));return wt.none(new ne({name:`Input.insert.${Math.random()}`,text:`INSERT INTO "Input"("outputSpent", "spendingInput") VALUES ${Ut(e.length)} ON CONFLICT DO NOTHING`,values:e}))})))}static async count(t){const e=t.map((t=>t.outputSpent));const s=new ne({name:`Input.belong.${Math.random()}`,text:'SELECT count(*) FROM "Input" WHERE "outputSpent" LIKE ANY ($1)',values:[[e]]});const r=await wt.oneOrNone(s);return parseInt(r?.count,10)||0}}class ie{static async select(t){return oe.select(t)}static async insert(t){return oe.insert(t)}}class ce{static insert=async t=>{const e=t.flatMap((t=>t.tx.ins.map((e=>({input:e,txId:t.txId}))))).filter((({input:t})=>!m.isCoinbaseHash(t.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}));ie.insert(e)}}class ue{static rawTxSubscriber=async t=>{const e=t.toString("hex");if(dt.info(`ZMQ message { rawTx:${e} }`),"08"!==e.slice(10,12))try{const t=R.txFromHex({hex:e});await ee.insert([t]),await ce.insert([t])}catch(t){dt.error(`Error parsing transaction ${t.message} ${t.stack}`)}};static checkSyncStatus=async()=>{const t=await E((async()=>{const t=await Dt.getBlockchainInfo();const e=(100*parseFloat(t.result.verificationprogress)).toFixed(4);const{blocks:s}=t.result;if(dt.info(`Zmq. Bitcoind { percentage:${e}%, blocks:${s} }`),parseFloat(t.result.verificationprogress)<=.7)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;dt.info(`BCN reaches sync end...at { bitcoind.progress:${e}%, bitcoindSyncedHeight:${s} }`)};static createWallet=async()=>{try{await Dt.createwallet(Y,!1,!1,"",!1,!1)}catch(t){dt.error(`Wallet creation failed with error '${t.message}'`)}};static sub=async t=>{try{await this.createWallet(),"regtest"!==A&&await this.checkSyncStatus(),await(async()=>{if("regtest"===A){if(dt.info(`Node is starting for chain ${P} and network ${A}, \n\n. Starting Wallet setup.`),"LTC"===P){const{result:t}=await Dt.getBlockchainInfo();const e=t.blocks;if(e{try{const t="dev"===it?"bcn.test.config.json":"bcn.config.json";const e=M(N(import.meta.url));this.configFile=T.readFileSync(x.join(e,"..","..",t)),this.loaded=!0}catch(t){if(t.message.includes("ENOENT: no such file or directory"))return void(this.loaded=!0);throw dt.error(`Access-list failed with error '${t.message}'`),t}};middleware=({url:t},e,s)=>{if(void 0!==e.locals.authToken)if(this.loaded||(dt.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){dt.error(`Authorization failed at ${t} with error: '${s.message}'`),e.status(403).json({error:s.message})}else s();else s()}};let we;h(o);try{we=r.createServer(ye)}catch(t){throw dt.error(`Starting server failed with error '${t.message}'`),t}if(dt.info(`Server listening on port ${L}`),ye.use(e()),"true"===X){const t=n({windowMs:parseInt(tt,10),max:parseInt(et,10),standardHeaders:"true"===st,legacyHeaders:"true"===rt});ye.use(t)}ye.use(t.json({limit:"100mb"})),ye.use(t.urlencoded({limit:"100mb",extended:!0})),ye.get("/",((t,e)=>e.status(200).send(`\n

Bitcoin Computer Node

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

Bitcoin Computer Node

\n Status: Healthy
\n Version: ${ht}
\n Chain: ${P}
\n Network: ${A}\n `))),ge.loaded&&(ye.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 dt.error(a),void e.status(401).json({error:a})}const a=(t=>{const e=t.split(" ");if(2!==e.length||"Bearer"!==e[0])throw new Error("Authentication header is invalid.");const s=Buffer.from(e[1],"base64").toString().split(":");if(3!==s.length)throw new Error;return{signature:s[0],publicKey:s[1],timestamp:parseInt(s[2],10)}})(r);const{signature:n,publicKey:o,timestamp:i}=a;if(Date.now()-i>18e4)return void e.status(401).json({error:"Signature is too old."});const c=I.sha256().update(ot+i).digest("hex");if(!he.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 de.select(o);if(u){if(u.clientTimestamp>=i)return void e.status(401).json({error:"Please use a fresh authentication token."});await de.update({publicKey:o,clientTimestamp:i})}else await de.insert({publicKey:o,clientTimestamp:i});e.locals.authToken=a,s()}catch(t){dt.error(`Auth failed with error '${t.message}'`),e.status(401).json({error:t.message})}})),ye.use(ge.middleware));const fe=(()=>{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 Rt.select(e))}catch(t){dt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/wallet/:address/sent-outputs",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await ee.listSentOutputs(e))}catch(t){dt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/wallet/:address/received-outputs",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await ee.listReceivedOutputs(e))}catch(t){dt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/wallet/:address/list-txs",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await ee.listTxs(e))}catch(t){dt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.get("/non-standard-utxos",(async(t,e)=>{try{const s=new URLSearchParams(t.url.split("?")[1]);const r={mod:s.get("mod"),publicKey:s.get("publicKey"),limit:s.get("limit"),order:s.get("order"),offset:s.get("offset"),ids:JSON.parse(s.get("ids"))};const a=await ee.query(r);e.status(200).json(a)}catch(s){dt.error(`GET ${t.url} failed with error '${s.messages}'`),e.status(500).json({error:s.message})}})),t.get("/address/:address/balance",(async({params:t,url:e},s)=>{try{const{address:e}=t;s.status(200).json(await Rt.getBalance(e))}catch(t){dt.error(`GET ${e} failed with error '${t.message||t}'`),s.status(500).json({error:t.message})}})),t.post("/tx/bulk",(async({body:{txIds:t},url:e},s)=>{try{if(void 0===t||0===t.length)return void s.status(400).json({error:"Missing input txIds."});const e=await Wt.getRaw(t);e?s.status(200).json(e):s.status(404).json({error:"Not found"})}catch(t){dt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/tx/post",(async({body:{hex:t},url:e},s)=>{try{if(!t)return void s.status(400).json({error:"Missing input hex."});const e=await Wt.sendRaw(t);e?s.status(200).json(e):s.status(404).json({error:"Error Occured"})}catch(r){dt.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 qt.getnewaddress();if("string"!=typeof t)throw new Error("Please provide appropriate count");return await qt.generatetoaddress(parseInt(t,10)||1,e),s.status(200).json({success:!0})}catch(t){return dt.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 qt.getbestblockhash();e=t}const{result:r}=await qt.getblockheader(e,!0);return s.status(200).json({height:r.height})}catch(t){return dt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/faucet",(async({body:{address:t,value:e},url:s},r)=>{try{const s=parseInt(e,10)/1e8;const{result:a}=await qt.sendtoaddress(t,s);await qt.generateToAddress(1,"mvFeNF9DAR7WMuCpBPbKuTtheihLyxzj8i");const{result:n}=await qt.getrawtransaction(a,1);const o=n.vout.findIndex((t=>1e8*t.value===parseInt(e,10)));return r.status(200).json({txId:a,vout:o,height:-1,satoshis:e})}catch(t){return dt.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=re.makeRandom({network:ae});const a=l.p2pkh({pubkey:s.publicKey,network:ae});const{address:n}=a;const o=(await qt.sendtoaddress(n,2*parseInt(e,10)/1e8,"","")).result;let i;let c=10;for(;!i;)if(i=(await Rt.select(n)).filter((t=>t.txId===o))[0],!i){if(c-=1,c<=0)throw new Error("No outputs");await se(10)}const u=(await qt.getrawtransaction(i.txId,1)).result;const p=new d({network:ae});p.addInput({hash:i.txId,index:i.vout,nonWitnessUtxo:Buffer.from(u.hex,"hex")}),p.addOutput({script:Buffer.from(t,"hex"),value:parseInt(e,10)}),p.signInput(0,s),p.finalizeAllInputs();const m=p.extractTransaction();let h;for(await qt.sendrawtransaction(m.toHex()),c=5;!h;)if(h=(await Rt.selectByScriptHex(t)).filter((t=>t.txId===m.getId()))[0],!h){if(c-=1,c<=0)throw new Error("No outputs");await se(10)}return r.status(200).json({txId:m.getId(),vout:h.vout,height:-1,satoshis:h.satoshis})}catch(t){return dt.error(`POST ${s} failed with error '${t.message}'`),r.status(500).json({error:t.message})}})),t.get("/tx/:txId/json",(async({params:{txId:t},url:e},s)=>{try{if(!t)return void s.status(400).json({error:"Missing input txId."});const e=await Wt.getRawJSON(t);e?s.status(200).json(e):s.status(404).json({error:"Not found"})}catch(t){dt.error(`GET ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/revs",(async({body:{ids:t},url:e},s)=>{try{if(void 0===t||0===t.length)return void s.status(400).json({error:"Missing input object ids."});const e=await Qt.getLatestRevs(t);s.status(200).json(e)}catch(t){dt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/revToId",(async({body:{rev:t},url:e},s)=>{try{if(!zt(t))return void s.status(400).json({error:"Invalid rev id"});const e=await Qt.getIdByRev(t);e&&s.status(200).json(e),s.status(404).json()}catch(t){dt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/rpc",(async({body:t,url:e},s)=>{try{if(!t||!t.method)throw new Error("Please provide appropriate RPC method name");if(!new RegExp(G).test(t.method))throw new Error("Method is not allowed");const e=function(t,e){if(void 0===Jt[t]||null===Jt[t])throw new Error("This RPC method does not exist, or not supported");const s=e.trim().split(" ");const r=Jt[t].trim().split(" ");if(0===e.trim().length&&0!==Jt[t].trim().length)throw new Error(`Too few params provided. Expected ${r.length} Provided 0`);if(0!==e.trim().length&&0===Jt[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)=>Vt[r[e]](t)))}(t.method,t.params);const r=e.length?await qt[t.method](...e):await qt[t.method]();s.status(200).json({result:r})}catch(t){dt.error(`POST ${e} failed with error '${t.message}'`),s.status(500).json({error:t.message})}})),t.post("/non-standard-utxo",(async(t,e)=>{e.status(500).json({error:"Please upgrade to @bitcoin-computer/lib to the latest version."})})),t})();ye.use(`/v1/${P}/${A}`,fe),ye.use("/v1/store",Tt),we.listen(L,(()=>{dt.info(`\nStarted Bitcoin Computer Node Version ${ht}\nPORT ${L} \n`)})).on("error",(t=>{dt.error(t.message),process.exit(1)}));const ve=new a.Subscriber;ve.connect(W),ve.subscribe("rawtx"),dt.info(`ZMQ Subscriber connected to ${W}`),(async()=>{await(async()=>{await E((()=>wt.connect()),{startingDelay:500})})(),await ue.sub(ve)})(); diff --git a/packages/node/dist/bcn.sync.es.mjs b/packages/node/dist/bcn.sync.es.mjs index 5bd561c92..46273a50a 100644 --- a/packages/node/dist/bcn.sync.es.mjs +++ b/packages/node/dist/bcn.sync.es.mjs @@ -1 +1 @@ -import*as t from"@bitcoin-computer/secp256k1";import{bufferUtils as e,networks as s,Transaction as n,crypto as a,address as r,initEccLib as o}from"@bitcoin-computer/nakamotojs";import i from"node:cluster";import{availableParallelism as c}from"node:os";import{backOff as p}from"exponential-backoff";import{Computer as u}from"@bitcoin-computer/lib";import d from"dotenv";import l from"fs";import m from"winston";import y from"winston-daily-rotate-file";import h from"bitcoind-rpc";import v from"util";import E from"pg-promise";import w from"pg-monitor";d.config();const $=process.env.CHAIN;const S=process.env.NETWORK;const{PORT:f}=process.env;const{POSTGRES_USER:O}=process.env;const{POSTGRES_PASSWORD:g}=process.env;const{POSTGRES_DB:R}=process.env;const{POSTGRES_HOST:I}=process.env;const{POSTGRES_PORT:T}=process.env;const{RPC_USER:M}=process.env;const{RPC_PASSWORD:b}=process.env;process.env;const{RPC_HOST:x}=process.env;const{RPC_PORT:N}=process.env;const{RPC_PROTOCOL:L}=process.env;process.env,process.env,process.env;const{DEBUG_MODE:C}=process.env;const{LOG_MAX_FILES:k}=process.env;const{LOG_MAX_SIZE:P}=process.env;const{LOG_ZIP:A}=process.env;const{SHOW_CONSOLE_LOGS:B}=process.env;const{SHOW_DB_LOGS:D}=process.env;process.env,process.env,process.env,process.env,process.env;const{THREADS:H}=process.env;process.env,process.env;const Y=process.env.QUERY_LIMIT||"1000";const F=process.env.BCN_URL||`http://127.0.0.1:${f}`;process.env.BCN_ENV,d.config();const{version:U}=JSON.parse(l.readFileSync("package.json","utf8"));U||process.env.SERVER_VERSION,parseInt(process.env.MWEB_HEIGHT||"",10),m.addColors({error:"red",warn:"yellow",info:"green",http:"magenta",debug:"white"});const W=m.format.combine(m.format.colorize(),m.format.timestamp({format:"YYYY-MM-DD HH:mm:ss:ms"}),m.format.json(),m.format.printf((t=>`${t.timestamp} [${t.level.slice(5).slice(0,-5)}] ${t.message}`)));const _={zippedArchive:"true"===A,maxSize:P,maxFiles:k,dirname:"logs"};const K=[];"true"===B&&K.push(new m.transports.Console({format:m.format.combine(m.format.colorize(),m.format.timestamp({format:"MM-DD-YYYY HH:mm:ss"}),m.format.printf((t=>`${t.timestamp} ${t.level} ${t.message}`)))}));const G=parseInt(C,10);G>=0&&K.push(new y({filename:"error-%DATE%.log",datePattern:"YYYY-MM-DD",level:"error",..._})),G>=1&&K.push(new y({filename:"warn-%DATE%.log",datePattern:"YYYY-MM-DD",level:"warn",..._})),G>=2&&K.push(new y({filename:"info-%DATE%.log",datePattern:"YYYY-MM-DD",level:"info",..._})),G>=3&&K.push(new y({filename:"http-%DATE%.log",datePattern:"YYYY-MM-DD",level:"http",..._})),G>=4&&K.push(new y({filename:"debug-%DATE%.log",datePattern:"YYYY-MM-DD",level:"debug",..._}));const V=m.createLogger({levels:{error:0,warn:1,info:2,http:3,debug:4},format:W,transports:K,exceptionHandlers:[new m.transports.File({filename:"logs/exceptions.log"})],rejectionHandlers:[new m.transports.File({filename:"logs/rejections.log"})]});const j=new h({protocol:L,user:M,pass:b,host:x,port:parseInt(N,10)});const q=v.promisify(h.prototype.createwallet.bind(j));const J=v.promisify(h.prototype.generateToAddress.bind(j));const z=v.promisify(h.prototype.getaddressinfo.bind(j));const X=v.promisify(h.prototype.getBlock.bind(j));const Z=v.promisify(h.prototype.getBlockchainInfo.bind(j));const Q=v.promisify(h.prototype.getBlockHash.bind(j));const tt=v.promisify(h.prototype.getRawTransaction.bind(j));const et=v.promisify(h.prototype.getRawTransaction.bind(j));const st=v.promisify(h.prototype.getTransaction.bind(j));const nt=v.promisify(h.prototype.getNewAddress.bind(j));const at={createwallet:q,generateToAddress:J,getaddressinfo:z,getBlock:X,getBlockchainInfo:Z,getBlockHash:Q,getRawTransaction:tt,getTransaction:st,importaddress:v.promisify(h.prototype.importaddress.bind(j)),listunspent:v.promisify(h.prototype.listunspent.bind(j)),sendRawTransaction:v.promisify(h.prototype.sendRawTransaction.bind(j)),getNewAddress:nt,sendToAddress:v.promisify(h.prototype.sendToAddress.bind(j)),getRawTransactionJSON:et};const rt={error:(t,e)=>{if(e.cn){const{host:s,port:n,database:a,user:r,password:o}=e.cn;V.debug(`Waiting for db to start { message:${t.message} host:${s}, port:${n}, database:${a}, user:${r}, password: ${o}`)}},noWarnings:!0};"true"===D&&(w.isAttached()?w.detach():(w.attach(rt),w.setTheme("matrix")));const ot=E(rt)({host:I,port:parseInt(T,10),database:R,user:O,password:g,allowExitOnIdle:!0,idleTimeoutMillis:100});const{PreparedStatement:it}=E;class ct{static async select(t){const e=new it({name:`SyncStatus.select.${Math.random()}`,text:'SELECT "blockToSync", "workerId" FROM "SyncStatus" WHERE "workerId" = $1',values:[t]});return ot.oneOrNone(e)}static async update({blockToSync:t,workerId:e}){const s=new it({name:`SyncStatus.update.${Math.random()}`,text:'UPDATE "SyncStatus" SET "blockToSync" = $1 WHERE "workerId" = $2',values:[t,e]});await ot.any(s)}static async count(){const t=new it({name:`SyncStatus.count.${Math.random()}`,text:'SELECT COUNT(*) FROM "SyncStatus"'});const e=await ot.oneOrNone(t);return parseInt(e?.count,10)||0}static async min(){const t=new it({name:`SyncStatus.min.${Math.random()}`,text:'SELECT MIN("blockToSync") FROM "SyncStatus"'});const e=await ot.oneOrNone(t);return parseInt(e?.min,10)||0}static async delete(){const t=new it({name:`SyncStatus.delete.${Math.random()}`,text:'DELETE FROM "SyncStatus"'});await ot.any(t)}static async insertBatch(t){const e=[];for(let s=1;s<=t.length;s+=2)e.push(`($${s}, $${s+1})`);const s=e.join(",");const n=new it({name:`SyncStatus.reorg.${Math.random()}`,text:`INSERT INTO "SyncStatus"("workerId", "blockToSync") VALUES ${s}`,values:t});await ot.any(n)}}class pt{static async select(t){return ct.select(t)}static async update(t){await ct.update(t)}static async count(){return ct.count()}static async insertBatch(t){await ct.insertBatch(t)}static async min(){return ct.min()}static async delete(){await ct.delete()}}class ut{static update=async t=>pt.update(t);static select=async t=>pt.select(t);static setup=async t=>{if(await pt.count()===t)return void V.info(`[wid 1 pid: ${process.pid}: all ${t} workers have already registered.`);const e=[];let s=Math.max(1,await pt.min());for(let n=1;n<=t;n+=1,s+=1)e.push(n,s);V.info(`[wid 1 pid: ${process.pid}: reorging sync status for ${t} workers...${e}`),await pt.delete(),await pt.insertBatch(e)}}const dt=(t,e)=>{const s=[];for(let n=0;n{const e=[];for(let s=1;s<=t;s+=2){const t=`($${s},$${s+1})`;e.push(t)}return e.join(",")};const mt=t=>{const e=[];for(let s=1;s<=t;s+=9){const t=`($${s},$${s+1},$${s+2},$${s+3},$${s+4},$${s+5},$${s+6},$${s+7},$${s+8})`;e.push(t)}return e.join(",")};const yt=t=>{try{return t()}catch{return null}};const{PreparedStatement:ht}=E;class vt{static async select(t){const e=new ht({name:`Input.select.${Math.random()}`,text:'SELECT "outputSpent" FROM "Input" WHERE "outputSpent" = $1',values:[t]});return ot.any(e)}static async insert(t){await Promise.all(dt(t,5e3).map((t=>{const e=t.flatMap((({outputSpent:t,spendingInput:e})=>[t,e]));return ot.none(new ht({name:`Input.insert.${Math.random()}`,text:`INSERT INTO "Input"("outputSpent", "spendingInput") VALUES ${lt(e.length)} ON CONFLICT DO NOTHING`,values:e}))})))}static async count(t){const e=t.map((t=>t.outputSpent));const s=new ht({name:`Input.belong.${Math.random()}`,text:'SELECT count(*) FROM "Input" WHERE "outputSpent" LIKE ANY ($1)',values:[[e]]});const n=await ot.oneOrNone(s);return parseInt(n?.count,10)||0}}class Et{static async select(t){return vt.select(t)}static async insert(t){return vt.insert(t)}}class wt{static insert=async t=>{const s=t.flatMap((t=>t.tx.ins.map((e=>({input:e,txId:t.txId}))))).filter((({input:t})=>!n.isCoinbaseHash(t.hash))).map((({input:t,txId:s},n)=>{return{outputSpent:`${a=t.hash,e.reverseBuffer(Buffer.from(a)).toString("hex")}:${t.index}`,spendingInput:`${s}:${n}`};var a}));Et.insert(s)}}function $t(t){if(!function(t){return/^[0-9A-Fa-f]{64}:\d+$/.test(t)}(t))throw new Error("Invalid rev")}const{PreparedStatement:St}=E;class ft{static async listSentOutputs(t){const e=new St({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 ot.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listReceivedOutputs(t){const e=new St({name:`Output.listReceivedTxs.${Math.random()}`,text:'SELECT "Output"."rev" as "output", "Output"."satoshis" as "amount" FROM "Output" WHERE "address" = $1',values:[t]});return(await ot.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listTxs(t){const e=new St({name:`Output.listTxs.${Math.random()}`,text:'WITH\n -- List all txs sent from a given address\n SENT AS (\n SELECT split_part("Input"."spendingInput",\':\',1) as "txId", SUM("Output".satoshis) as "satoshis"\n FROM "Output" INNER JOIN "Input" ON "Output".rev = "Input"."outputSpent" \n WHERE "Output".address = $1\n GROUP BY split_part("Input"."spendingInput",\':\',1)\n ),\n -- List all tx received from a given address\n RECEIVED AS (\n SELECT SPLIT_PART("Output"."rev",\':\',1) as "txId", SUM("Output"."satoshis") as "satoshis" \n FROM "Output" \n WHERE "address" = $1\n GROUP BY "txId"\n )\n\n SELECT\n RECEIVED."txId", \n coalesce(SENT."satoshis", 0) as "inputsSatoshis", \n coalesce(RECEIVED."satoshis", 0) as "outputsSatoshis", \n coalesce(RECEIVED."satoshis",0) - coalesce(SENT."satoshis",0) as "satoshis"\n FROM\n SENT RIGHT JOIN RECEIVED ON SENT."txId" = RECEIVED."txId";',values:[t]});const s=(await ot.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 St({name:`Output.select.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "rev", "publicKeys", "hash", "mod", "isTbcOutput", "previous" FROM "Output" WHERE "address" = $1',values:[t]});return ot.any(e)}static async insert(t){await Promise.all(dt(t,1111).map((t=>{const e=t.flatMap((({rev:t,address:e,satoshis:s,scriptPubKey:n,isTbcOutput:a,publicKeys:r,mod:o,previous:i,hash:c})=>[t,e,s,n,a,r,o,i,c]));return ot.none(new St({name:`Output.insert.${Math.random()}`,text:`INSERT INTO "Output"("rev", "address", "satoshis", "scriptPubKey", "isTbcOutput", "publicKeys", "mod", "previous", "hash") VALUES ${mt(e.length)} ON CONFLICT DO NOTHING`,values:e}))})))}static async getIdByRev(t){const e=new St({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON r."previous" = o."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=(await ot.any(e)).filter((t=>null===t.previous));return s[0]?.rev}static async getIdsByRevs(t){return Promise.all(t.map((t=>this.getIdByRev(t))))}static async getLatestRev(t){const e=new St({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON o."previous" = r."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=await ot.any(e);const n=Object.fromEntries(s.map((t=>[t.previous,t.rev])));let a=t;for(;n[a];)a=n[a];return a}static async getLatestRevs(t){return Promise.all(t.map(this.getLatestRev))}static async getIdsByMod(t){const e=new St({name:`Output.getIdsByMod.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1',values:[t]});return(await ot.any(e)).map((t=>t.rev))}static sqlSuffix(t,e,s){let n="";return s&&(n+=` order by "timestamp" ${s}`),n+=` limit ${t||Y}`,e&&(n+=` offset ${e}`),n}static async getRevsByPublicKey(t){const e=new St({name:`Output.getRevsByPublicKey.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE $1 = ANY("publicKeys")',values:[t]});return(await ot.any(e)).map((t=>t.rev))}static async getUnspentRevsByMod(t,e,s,n){const a=await this.getIdsByMod(t);const r=await this.getLatestRevs(a);const o=new St({name:`Output.getUnspentRevsByMod.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(e,s,n)}`,values:[r]});return(await ot.any(o)).map((t=>t.rev))}static async getUnspentRevsByPublicKey(t,e,s,n){const a=new St({name:`Output.getUnspentRevsByPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE $1 = ANY("publicKeys") AND "isTbcOutput" = true \n AND NOT EXISTS (SELECT 1 FROM "Input" ip WHERE "ip"."outputSpent" = "Output"."rev") \n ${this.sqlSuffix(e,s,n)}`,values:[t]});return(await ot.any(a)).map((t=>t.rev))}static async getUnspentRevsByModAndPublicKey(t,e,s,n,a){const r=await this.getUnspentRevsByPublicKey(e,s,n,a);const o=await this.getIdsByRevs(r);const i=new St({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1 AND "rev" = ANY($2)',values:[t,o]});const c=(await ot.any(i)).map((t=>t.rev));const p=await this.getLatestRevs(c);const u=new St({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(s,n,a)}`,values:[p]});return(await ot.any(u)).map((t=>t.rev))}static async query(t){const{publicKey:e,limit:s,offset:n,ids:a,mod:r,order:o}=t;const i=parseInt(Y||"",10);if(s&&parseInt(s||"",10)>i||a&&a.length>i)throw new Error(`Can't fetch more than ${Y} revs.`);if(o&&"ASC"!==o&&"DESC"!==o)throw new Error("Invalid order. Should be ASC or DESC.");return a?.length?(a.map($t),this.getLatestRevs(a)):r&&!e?this.getUnspentRevsByMod(r,s,n,o):!r&&e?this.getUnspentRevsByPublicKey(e,s,n,o):r&&e?this.getUnspentRevsByModAndPublicKey(r,e,s,n,o):[]}}class Ot{static async select(t){return ft.select(t)}static async insert(t){return ft.insert(t)}static async listSentOutputs(t){return ft.listSentOutputs(t)}static async listReceivedOutputs(t){return ft.listReceivedOutputs(t)}static async listTxs(t){return ft.listTxs(t)}static async getLatestRev(t){return ft.getLatestRev(t)}static async query(t){return ft.query(t)}}class gt{static insert=async t=>{const e=function(t=$,e=S){switch(t){case"BTC":switch(e){case"mainnet":return s.bitcoin;case"testnet":return s.testnet;case"regtest":return s.regtest;default:throw new Error(`Invalid network ${e}`)}case"LTC":switch(e){case"mainnet":return s.litecoin;case"testnet":return s.litecointestnet;case"regtest":return s.litecoinregtest;default:throw new Error(`Invalid network ${e}`)}case"PEPE":switch(e){case"mainnet":return s.pepecoin;case"testnet":return s.pepecointestnet;case"regtest":return s.pepecoinregtest;default:throw new Error(`Invalid network ${e}`)}default:throw new Error(`Invalid chain ${t}`)}}($,S);const n=t.flatMap((t=>{const{zip:s,ownerData:n,onChainMetaData:o}=t;const{exp:i="",mod:c=""}=o;return t.tx.outs.map((({script:o,value:p},u)=>{const d=ur.fromOutputScript(o,e))),satoshis:Math.round(p),scriptPubKey:o.toString("hex"),isTbcOutput:d,publicKeys:d?n[u]._owners:[],mod:d?c:"",previous:d?s[u][0]:null,hash:d?a.sha256(Buffer.from(i||"")).toString("hex"):null}}))}));return Ot.insert(n)};static listSentOutputs=async t=>Ot.listSentOutputs(t);static listReceivedOutputs=async t=>Ot.listReceivedOutputs(t);static listTxs=async t=>Ot.listTxs(t);static getLatestRev=async t=>Ot.getLatestRev(t);static query=async t=>Ot.query(t)}class Rt{static waitForBlockHash=async(t,e)=>(await p((async()=>{let s;try{s=await at.getBlockHash(t)}catch(s){throw V.info(`[wid ${e} pid: ${process.pid}]: waiting for block ${t} ...`),s}return s}),{startingDelay:3e4,timeMultiple:1,numOfAttempts:720})).result;static syncBlock=async(t,e,s="LTC")=>{const n=await Rt.waitForBlockHash(t,e);const{result:a}=await at.getBlock(n,2);const{tx:r}=a;let o=r;"LTC"===s&&(o=r.filter((t=>"08"!==t.hex.slice(10,12))));const i=`[wid ${e} pid: ${process.pid}: backfilling height ${t} - backfilling ${o.length} txs `;"LTC"===s&&i.concat(`(${r.length-o.length} mweb tx's filtered)...`),V.info(i);const c=o.map((s=>{try{return u.txFromHex({hex:s.hex})}catch(n){n instanceof Error&&V.error(`[wid ${e} pid: ${process.pid}: failed to parse transaction in block ${t}\n error message: ${n.message}\n transaction: ${JSON.stringify(s)}`)}return null})).filter((t=>null!==t));try{await gt.insert(c),await wt.insert(c)}catch(s){V.error(`[wid ${e} pid: ${process.pid}: processing block ${t} failed with error '${s.message}'`)}};static sync=async(t,e,s)=>{for(;;){try{await this.syncBlock(e,t,$)}catch(s){V.error(`[wid ${t} pid: ${process.pid}: syncing block num ${e} failed with error '${s.message}'`)}e+=s,await ut.update({blockToSync:e,workerId:t})}}}o(t);let It=c();H&&parseInt(H,10)>0&&(It=parseInt(H,10));const Tt=i.worker?i.worker.id:0;V.info(`[wid ${Tt} pid: ${process.pid}]: starting with ${It} threads`);try{if(await(async()=>{await p((()=>ot.connect()),{startingDelay:500})})(),V.info(`[wid ${Tt} pid: ${process.pid}]: connected to the database successfully`),i.isPrimary){V.info(`[wid ${Tt} pid: ${process.pid}]: parameters { url: ${F}, chain:${$} network:${S} numWorkers: ${It}}`),await ut.setup(It);for(let t=1;t<=It;t+=1)V.info(`[wid ${Tt} pid: ${process.pid}: launching worker ${t}`),i.fork();i.on("exit",((t,e,s)=>{V.info(`[wid ${Tt} pid: ${process.pid}]: worker ${t.process.pid} died with code ${e} and signal ${s}`),V.error(`[wid ${Tt} pid: ${process.pid}]: aborting`),process.exit(0)}))}else"regtest"!==S&&(async(t,e)=>{const s=await ut.select(t);V.info(`[wid ${s.workerId} pid: ${process.pid}]: starting to sync block: ${s.blockToSync} - numWorkers: ${e}`),await Rt.sync(s.workerId,s.blockToSync,e)})(Tt,It)}catch(t){V.error(`[wid ${Tt} pid: ${process.pid}]: synchronizing failed with error '${t.message}'`)} +import*as t from"@bitcoin-computer/secp256k1";import{bufferUtils as e,networks as s,Transaction as n,crypto as a,address as r,initEccLib as o}from"@bitcoin-computer/nakamotojs";import i from"node:cluster";import{availableParallelism as c}from"node:os";import{backOff as p}from"exponential-backoff";import{Computer as u}from"@bitcoin-computer/lib";import d from"dotenv";import l from"fs";import m from"winston";import y from"winston-daily-rotate-file";import E from"bitcoind-rpc";import h from"util";import v from"pg-promise";import w from"pg-monitor";d.config();const S=process.env.CHAIN;const $=process.env.NETWORK;const{PORT:O}=process.env;const{POSTGRES_USER:f}=process.env;const{POSTGRES_PASSWORD:g}=process.env;const{POSTGRES_DB:R}=process.env;const{POSTGRES_HOST:T}=process.env;const{POSTGRES_PORT:I}=process.env;const{RPC_USER:M}=process.env;const{RPC_PASSWORD:b}=process.env;process.env;const{RPC_HOST:x}=process.env;const{RPC_PORT:N}=process.env;const{RPC_PROTOCOL:L}=process.env;process.env,process.env,process.env;const{DEBUG_MODE:C}=process.env;const{LOG_MAX_FILES:k}=process.env;const{LOG_MAX_SIZE:P}=process.env;const{LOG_ZIP:A}=process.env;const{SHOW_CONSOLE_LOGS:B}=process.env;const{SHOW_DB_LOGS:H}=process.env;process.env,process.env,process.env,process.env,process.env;const{THREADS:D}=process.env;process.env,process.env;const F=process.env.QUERY_LIMIT||"1000";const U=process.env.BCN_URL||`http://127.0.0.1:${O}`;process.env.BCN_ENV,d.config();const{version:Y}=JSON.parse(l.readFileSync("package.json","utf8"));Y||process.env.SERVER_VERSION,parseInt(process.env.MWEB_HEIGHT||"",10),m.addColors({error:"red",warn:"yellow",info:"green",http:"magenta",debug:"white"});const W=m.format.combine(m.format.colorize(),m.format.timestamp({format:"YYYY-MM-DD HH:mm:ss:ms"}),m.format.json(),m.format.printf((t=>`${t.timestamp} [${t.level.slice(5).slice(0,-5)}] ${t.message}`)));const _={zippedArchive:"true"===A,maxSize:P,maxFiles:k,dirname:"logs"};const K=[];"true"===B&&K.push(new m.transports.Console({format:m.format.combine(m.format.colorize(),m.format.timestamp({format:"MM-DD-YYYY HH:mm:ss"}),m.format.printf((t=>`${t.timestamp} ${t.level} ${t.message}`)))}));const G=parseInt(C,10);G>=0&&K.push(new y({filename:"error-%DATE%.log",datePattern:"YYYY-MM-DD",level:"error",..._})),G>=1&&K.push(new y({filename:"warn-%DATE%.log",datePattern:"YYYY-MM-DD",level:"warn",..._})),G>=2&&K.push(new y({filename:"info-%DATE%.log",datePattern:"YYYY-MM-DD",level:"info",..._})),G>=3&&K.push(new y({filename:"http-%DATE%.log",datePattern:"YYYY-MM-DD",level:"http",..._})),G>=4&&K.push(new y({filename:"debug-%DATE%.log",datePattern:"YYYY-MM-DD",level:"debug",..._}));const V=m.createLogger({levels:{error:0,warn:1,info:2,http:3,debug:4},format:W,transports:K,exceptionHandlers:[new m.transports.File({filename:"logs/exceptions.log"})],rejectionHandlers:[new m.transports.File({filename:"logs/rejections.log"})]});const q=new E({protocol:L,user:M,pass:b,host:x,port:parseInt(N,10)});const j=h.promisify(E.prototype.createwallet.bind(q));const J=h.promisify(E.prototype.generateToAddress.bind(q));const z=h.promisify(E.prototype.getaddressinfo.bind(q));const X=h.promisify(E.prototype.getBlock.bind(q));const Z=h.promisify(E.prototype.getBlockchainInfo.bind(q));const Q=h.promisify(E.prototype.getBlockHash.bind(q));const tt=h.promisify(E.prototype.getRawTransaction.bind(q));const et=h.promisify(E.prototype.getRawTransaction.bind(q));const st=h.promisify(E.prototype.getTransaction.bind(q));const nt=h.promisify(E.prototype.getNewAddress.bind(q));const at={createwallet:j,generateToAddress:J,getaddressinfo:z,getBlock:X,getBlockchainInfo:Z,getBlockHash:Q,getRawTransaction:tt,getTransaction:st,importaddress:h.promisify(E.prototype.importaddress.bind(q)),listunspent:h.promisify(E.prototype.listunspent.bind(q)),sendRawTransaction:h.promisify(E.prototype.sendRawTransaction.bind(q)),getNewAddress:nt,sendToAddress:h.promisify(E.prototype.sendToAddress.bind(q)),getRawTransactionJSON:et};const rt={error:(t,e)=>{if(e.cn){const{host:s,port:n,database:a,user:r,password:o}=e.cn;V.debug(`Waiting for db to start { message:${t.message} host:${s}, port:${n}, database:${a}, user:${r}, password: ${o}`)}},noWarnings:!0};"true"===H&&(w.isAttached()?w.detach():(w.attach(rt),w.setTheme("matrix")));const ot=v(rt)({host:T,port:parseInt(I,10),database:R,user:f,password:g,allowExitOnIdle:!0,idleTimeoutMillis:100});const{PreparedStatement:it}=v;class ct{static async select(t){const e=new it({name:`SyncStatus.select.${Math.random()}`,text:'SELECT "blockToSync", "workerId" FROM "SyncStatus" WHERE "workerId" = $1',values:[t]});return ot.oneOrNone(e)}static async update({blockToSync:t,workerId:e}){const s=new it({name:`SyncStatus.update.${Math.random()}`,text:'UPDATE "SyncStatus" SET "blockToSync" = $1 WHERE "workerId" = $2',values:[t,e]});await ot.any(s)}static async count(){const t=new it({name:`SyncStatus.count.${Math.random()}`,text:'SELECT COUNT(*) FROM "SyncStatus"'});const e=await ot.oneOrNone(t);return parseInt(e?.count,10)||0}static async min(){const t=new it({name:`SyncStatus.min.${Math.random()}`,text:'SELECT MIN("blockToSync") FROM "SyncStatus"'});const e=await ot.oneOrNone(t);return parseInt(e?.min,10)||0}static async delete(){const t=new it({name:`SyncStatus.delete.${Math.random()}`,text:'DELETE FROM "SyncStatus"'});await ot.any(t)}static async insertBatch(t){const e=[];for(let s=1;s<=t.length;s+=2)e.push(`($${s}, $${s+1})`);const s=e.join(",");const n=new it({name:`SyncStatus.reorg.${Math.random()}`,text:`INSERT INTO "SyncStatus"("workerId", "blockToSync") VALUES ${s}`,values:t});await ot.any(n)}}class pt{static async select(t){return ct.select(t)}static async update(t){await ct.update(t)}static async count(){return ct.count()}static async insertBatch(t){await ct.insertBatch(t)}static async min(){return ct.min()}static async delete(){await ct.delete()}}class ut{static update=async t=>pt.update(t);static select=async t=>pt.select(t);static setup=async t=>{if(await pt.count()===t)return void V.info(`[wid 1 pid: ${process.pid}: all ${t} workers have already registered.`);const e=[];let s=Math.max(1,await pt.min());for(let n=1;n<=t;n+=1,s+=1)e.push(n,s);V.info(`[wid 1 pid: ${process.pid}: reorging sync status for ${t} workers...${e}`),await pt.delete(),await pt.insertBatch(e)}}const dt=(t,e)=>{const s=[];for(let n=0;n{const e=[];for(let s=1;s<=t;s+=2){const t=`($${s},$${s+1})`;e.push(t)}return e.join(",")};const mt=t=>{const e=[];for(let s=1;s<=t;s+=9){const t=`($${s},$${s+1},$${s+2},$${s+3},$${s+4},$${s+5},$${s+6},$${s+7},$${s+8})`;e.push(t)}return e.join(",")};const yt=t=>{try{return t()}catch{return null}};const{PreparedStatement:Et}=v;class ht{static async select(t){const e=new Et({name:`Input.select.${Math.random()}`,text:'SELECT "outputSpent" FROM "Input" WHERE "outputSpent" = $1',values:[t]});return ot.any(e)}static async insert(t){await Promise.all(dt(t,5e3).map((t=>{const e=t.flatMap((({outputSpent:t,spendingInput:e})=>[t,e]));return ot.none(new Et({name:`Input.insert.${Math.random()}`,text:`INSERT INTO "Input"("outputSpent", "spendingInput") VALUES ${lt(e.length)} ON CONFLICT DO NOTHING`,values:e}))})))}static async count(t){const e=t.map((t=>t.outputSpent));const s=new Et({name:`Input.belong.${Math.random()}`,text:'SELECT count(*) FROM "Input" WHERE "outputSpent" LIKE ANY ($1)',values:[[e]]});const n=await ot.oneOrNone(s);return parseInt(n?.count,10)||0}}class vt{static async select(t){return ht.select(t)}static async insert(t){return ht.insert(t)}}class wt{static insert=async t=>{const s=t.flatMap((t=>t.tx.ins.map((e=>({input:e,txId:t.txId}))))).filter((({input:t})=>!n.isCoinbaseHash(t.hash))).map((({input:t,txId:s},n)=>{return{outputSpent:`${a=t.hash,e.reverseBuffer(Buffer.from(a)).toString("hex")}:${t.index}`,spendingInput:`${s}:${n}`};var a}));vt.insert(s)}}function St(t){if(!function(t){return/^[0-9A-Fa-f]{64}:\d+$/.test(t)}(t))throw new Error("Invalid rev")}const{PreparedStatement:$t}=v;class Ot{static async listSentOutputs(t){const e=new $t({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 ot.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listReceivedOutputs(t){const e=new $t({name:`Output.listReceivedTxs.${Math.random()}`,text:'SELECT "Output"."rev" as "output", "Output"."satoshis" as "amount" FROM "Output" WHERE "address" = $1',values:[t]});return(await ot.any(e)).map((t=>({...t,amount:parseInt(t.amount,10)||0})))}static async listTxs(t){const e=new $t({name:`Output.listTxs.${Math.random()}`,text:'WITH\n -- List all txs sent from a given address\n SENT AS (\n SELECT split_part("Input"."spendingInput",\':\',1) as "txId", SUM("Output".satoshis) as "satoshis"\n FROM "Output" INNER JOIN "Input" ON "Output".rev = "Input"."outputSpent" \n WHERE "Output".address = $1\n GROUP BY split_part("Input"."spendingInput",\':\',1)\n ),\n -- List all tx received from a given address\n RECEIVED AS (\n SELECT SPLIT_PART("Output"."rev",\':\',1) as "txId", SUM("Output"."satoshis") as "satoshis" \n FROM "Output" \n WHERE "address" = $1\n GROUP BY "txId"\n )\n\n SELECT\n RECEIVED."txId", \n coalesce(SENT."satoshis", 0) as "inputsSatoshis", \n coalesce(RECEIVED."satoshis", 0) as "outputsSatoshis", \n coalesce(RECEIVED."satoshis",0) - coalesce(SENT."satoshis",0) as "satoshis"\n FROM\n SENT RIGHT JOIN RECEIVED ON SENT."txId" = RECEIVED."txId";',values:[t]});const s=(await ot.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 $t({name:`Output.select.${Math.random()}`,text:'SELECT "address", "satoshis", "scriptPubKey", "rev", "publicKeys", "hash", "mod", "isTbcOutput", "previous" FROM "Output" WHERE "address" = $1',values:[t]});return ot.any(e)}static async insert(t){await Promise.all(dt(t,1111).map((t=>{const e=t.flatMap((({rev:t,address:e,satoshis:s,scriptPubKey:n,isTbcOutput:a,publicKeys:r,mod:o,previous:i,hash:c})=>[t,e,s,n,a,r,o,i,c]));return ot.none(new $t({name:`Output.insert.${Math.random()}`,text:`INSERT INTO "Output"("rev", "address", "satoshis", "scriptPubKey", "isTbcOutput", "publicKeys", "mod", "previous", "hash") VALUES ${mt(e.length)} ON CONFLICT DO NOTHING`,values:e}))})))}static async getIdByRev(t){const e=new $t({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON r."previous" = o."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=(await ot.any(e)).filter((t=>null===t.previous));return s[0]?.rev}static async getIdsByRevs(t){return Promise.all(t.map((t=>this.getIdByRev(t))))}static async getLatestRev(t){const e=new $t({name:`NonStandard.recursiveUpdates.${Math.random()}`,text:'WITH RECURSIVE revUpdates AS (\n SELECT "rev", "previous" FROM "Output" WHERE "isTbcOutput" = true and "rev" = $1\n UNION ALL\n SELECT o."rev", o."previous" FROM "Output" o\n INNER JOIN revUpdates r ON o."previous" = r."rev"\n )\n SELECT * FROM revUpdates',values:[t]});const s=await ot.any(e);const n=Object.fromEntries(s.map((t=>[t.previous,t.rev])));let a=t;for(;n[a];)a=n[a];return a}static async getLatestRevs(t){return Promise.all(t.map(this.getLatestRev))}static async getIdsByMod(t){const e=new $t({name:`Output.getIdsByMod.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1',values:[t]});return(await ot.any(e)).map((t=>t.rev))}static sqlSuffix(t,e,s){let n="";return s&&(n+=` order by "timestamp" ${s}`),n+=` limit ${t||F}`,e&&(n+=` offset ${e}`),n}static async getRevsByPublicKey(t){const e=new $t({name:`Output.getRevsByPublicKey.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE $1 = ANY("publicKeys")',values:[t]});return(await ot.any(e)).map((t=>t.rev))}static async getUnspentRevsByMod(t,e,s,n){const a=await this.getIdsByMod(t);const r=await this.getLatestRevs(a);const o=new $t({name:`Output.getUnspentRevsByMod.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(e,s,n)}`,values:[r]});return(await ot.any(o)).map((t=>t.rev))}static async getUnspentRevsByPublicKey(t,e,s,n){const a=new $t({name:`Output.getUnspentRevsByPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE $1 = ANY("publicKeys") AND "isTbcOutput" = true \n AND NOT EXISTS (SELECT 1 FROM "Input" ip WHERE "ip"."outputSpent" = "Output"."rev") \n ${this.sqlSuffix(e,s,n)}`,values:[t]});return(await ot.any(a)).map((t=>t.rev))}static async getUnspentRevsByModAndPublicKey(t,e,s,n,a){const r=await this.getUnspentRevsByPublicKey(e,s,n,a);const o=await this.getIdsByRevs(r);const i=new $t({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:'SELECT "rev" FROM "Output" WHERE "mod" = $1 AND "rev" = ANY($2)',values:[t,o]});const c=(await ot.any(i)).map((t=>t.rev));const p=await this.getLatestRevs(c);const u=new $t({name:`Output.getLatestRevsByModAndPublicKey.${Math.random()}`,text:`SELECT "rev" FROM "Output" WHERE "rev" = ANY($1) ${this.sqlSuffix(s,n,a)}`,values:[p]});return(await ot.any(u)).map((t=>t.rev))}static async getUnspentTbcOutputs(t,e,s){const n=new $t({name:`Output.getUnspentTbcOutputs.${Math.random()}`,text:`SELECT "rev", "address", "satoshis", "scriptPubKey", "publicKeys", "timestamp"\n FROM "Output" WHERE "isTbcOutput" = true AND NOT EXISTS\n (SELECT 1 FROM "Input" ip WHERE "ip"."outputSpent" = "Output"."rev") ${this.sqlSuffix(t,e,s)}`});return(await ot.any(n)).map((t=>t.rev))}static async query(t){const{publicKey:e,limit:s,offset:n,ids:a,mod:r,order:o}=t;const i=parseInt(F||"",10);if(s&&parseInt(s||"",10)>i||a&&a.length>i)throw new Error(`Can't fetch more than ${F} revs.`);if(o&&"ASC"!==o&&"DESC"!==o)throw new Error("Invalid order. Should be ASC or DESC.");return a?(a.map(St),this.getLatestRevs(a)):r&&!e?this.getUnspentRevsByMod(r,s,n,o):!r&&e?this.getUnspentRevsByPublicKey(e,s,n,o):r&&e?this.getUnspentRevsByModAndPublicKey(r,e,s,n,o):this.getUnspentTbcOutputs(s,n,o)}}class ft{static async select(t){return Ot.select(t)}static async insert(t){return Ot.insert(t)}static async listSentOutputs(t){return Ot.listSentOutputs(t)}static async listReceivedOutputs(t){return Ot.listReceivedOutputs(t)}static async listTxs(t){return Ot.listTxs(t)}static async getLatestRev(t){return Ot.getLatestRev(t)}static async query(t){return Ot.query(t)}}class gt{static insert=async t=>{const e=function(t=S,e=$){switch(t){case"BTC":switch(e){case"mainnet":return s.bitcoin;case"testnet":return s.testnet;case"regtest":return s.regtest;default:throw new Error(`Invalid network ${e}`)}case"LTC":switch(e){case"mainnet":return s.litecoin;case"testnet":return s.litecointestnet;case"regtest":return s.litecoinregtest;default:throw new Error(`Invalid network ${e}`)}case"PEPE":switch(e){case"mainnet":return s.pepecoin;case"testnet":return s.pepecointestnet;case"regtest":return s.pepecoinregtest;default:throw new Error(`Invalid network ${e}`)}default:throw new Error(`Invalid chain ${t}`)}}(S,$);const n=t.flatMap((t=>{const{zip:s,ownerData:n,onChainMetaData:o}=t;const{exp:i="",mod:c=""}=o;return t.tx.outs.map((({script:o,value:p},u)=>{const d=ur.fromOutputScript(o,e))),satoshis:Math.round(p),scriptPubKey:o.toString("hex"),isTbcOutput:d,publicKeys:d?n[u]._owners:[],mod:d?c:"",previous:d?s[u][0]:null,hash:d?a.sha256(Buffer.from(i||"")).toString("hex"):null}}))}));return ft.insert(n)};static listSentOutputs=async t=>ft.listSentOutputs(t);static listReceivedOutputs=async t=>ft.listReceivedOutputs(t);static listTxs=async t=>ft.listTxs(t);static getLatestRev=async t=>ft.getLatestRev(t);static query=async t=>ft.query(t)}class Rt{static waitForBlockHash=async(t,e)=>(await p((async()=>{let s;try{s=await at.getBlockHash(t)}catch(s){throw V.info(`[wid ${e} pid: ${process.pid}]: waiting for block ${t} ...`),s}return s}),{startingDelay:3e4,timeMultiple:1,numOfAttempts:720})).result;static syncBlock=async(t,e,s="LTC")=>{const n=await Rt.waitForBlockHash(t,e);const{result:a}=await at.getBlock(n,2);const{tx:r}=a;let o=r;"LTC"===s&&(o=r.filter((t=>"08"!==t.hex.slice(10,12))));const i=`[wid ${e} pid: ${process.pid}: backfilling height ${t} - backfilling ${o.length} txs `;"LTC"===s&&i.concat(`(${r.length-o.length} mweb tx's filtered)...`),V.info(i);const c=o.map((s=>{try{return u.txFromHex({hex:s.hex})}catch(n){n instanceof Error&&V.error(`[wid ${e} pid: ${process.pid}: failed to parse transaction in block ${t}\n error message: ${n.message}\n transaction: ${JSON.stringify(s)}`)}return null})).filter((t=>null!==t));try{await gt.insert(c),await wt.insert(c)}catch(s){V.error(`[wid ${e} pid: ${process.pid}: processing block ${t} failed with error '${s.message}'`)}};static sync=async(t,e,s)=>{for(;;){try{await this.syncBlock(e,t,S)}catch(s){V.error(`[wid ${t} pid: ${process.pid}: syncing block num ${e} failed with error '${s.message}'`)}e+=s,await ut.update({blockToSync:e,workerId:t})}}}o(t);let Tt=c();D&&parseInt(D,10)>0&&(Tt=parseInt(D,10));const It=i.worker?i.worker.id:0;V.info(`[wid ${It} pid: ${process.pid}]: starting with ${Tt} threads`);try{if(await(async()=>{await p((()=>ot.connect()),{startingDelay:500})})(),V.info(`[wid ${It} pid: ${process.pid}]: connected to the database successfully`),i.isPrimary){V.info(`[wid ${It} pid: ${process.pid}]: parameters { url: ${U}, chain:${S} network:${$} numWorkers: ${Tt}}`),await ut.setup(Tt);for(let t=1;t<=Tt;t+=1)V.info(`[wid ${It} pid: ${process.pid}: launching worker ${t}`),i.fork();i.on("exit",((t,e,s)=>{V.info(`[wid ${It} pid: ${process.pid}]: worker ${t.process.pid} died with code ${e} and signal ${s}`),V.error(`[wid ${It} pid: ${process.pid}]: aborting`),process.exit(0)}))}else"regtest"!==$&&(async(t,e)=>{const s=await ut.select(t);V.info(`[wid ${s.workerId} pid: ${process.pid}]: starting to sync block: ${s.blockToSync} - numWorkers: ${e}`),await Rt.sync(s.workerId,s.blockToSync,e)})(It,Tt)}catch(t){V.error(`[wid ${It} pid: ${process.pid}]: synchronizing failed with error '${t.message}'`)}