diff --git a/.eslintrc b/.eslintrc index ab4ab1c..4423c1f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -31,6 +31,6 @@ "prefer-template": 0, "no-restricted-syntax": 0, "strict": 0, - "no-underscore-dangle": ["error", {"allow": ["__set__"]}] + "no-underscore-dangle": ["warn", {"allow": ["__set__", "_*"]}] } } diff --git a/.gitignore b/.gitignore index df33f2a..f81968b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ log.txt npm-debug.log config/default.json .idea/ + +\.DS_Store diff --git a/dist/navtech.js b/dist/navtech.js index 0fb18fa..8b1014c 100644 --- a/dist/navtech.js +++ b/dist/navtech.js @@ -1,3 +1,3 @@ -!function(e){function n(r){if(t[r])return t[r].exports;var i=t[r]={exports:{},id:r,loaded:!1};return e[r].call(i.exports,i,i.exports,n),i.loaded=!0,i.exports}var t={};return n.m=e,n.c=t,n.p="",n(0)}([function(e,n,t){"use strict";function r(e){e?i():T.writeLog("APP_002","invalid server settings",S)}function i(){w.navClient=new s({username:S.navCoin.user,password:S.navCoin.pass,port:S.navCoin.port,host:S.navCoin.host}),w.subClient=new s({username:S.subChain.user,password:S.subChain.pass,port:S.subChain.port,host:S.subChain.host}),S.ssl?d.exists(S.ssl.key,function(e){d.exists(S.ssl.crt,function(n){if(!e||!n)return void T.writeLog("APP_003","unable to find user defined ssl certificate",S.ssl);var t={key:d.readFileSync(S.ssl.key),cert:d.readFileSync(S.ssl.crt),requestCert:!1,rejectUnauthorized:!1};k.use(u.json()),k.use(u.urlencoded({extended:!0})),a.createServer(t,k).listen(S.local.port,function(){L()})})}):c.createCertificate({days:1,selfSigned:!0},function(e,n){var t={key:n.serviceKey,cert:n.certificate,requestCert:!1,rejectUnauthorized:!1};k.use(u.json()),k.use(u.urlencoded({extended:!0})),a.createServer(t,k).listen(S.local.port,function(){L()})})}var s=t(1),o=t(2),a=t(3),c=t(4),u=t(5),d=t(6),l=t(7),g=t(8),p=t(9),v=t(31),m=t(36),f=t(17),y=t(19),h=t(21),b=t(10),T=t(13),A=t(15),C=l.get("GLOBAL"),S=!1;"INCOMING"===C.serverType&&(S=l.get("INCOMING")),"OUTGOING"===C.serverType&&(S=l.get("OUTGOING")),T.writeLog("SYS_001","Server Starting",{memes:["harambe","rustled jimmies"]},!0);var k=o(),w={};S?m.validateSettings({settings:S},r):T.writeLog("APP_001","invalid global server type",C.serverType);var L=function(){"INCOMING"===C.serverType?(p.init(),P()):"OUTGOING"===C.serverType&&(v.init(),P())},P=function(){k.get("/",function(e,n){g("dist/navtech.js",function(e,t){return e?void n.send(JSON.stringify({status:200,type:"FAILURE",message:"error generating md5 hash",serverType:C.serverType,error:e})):void n.send(JSON.stringify({status:200,type:"SUCCESS",message:"server is running!",serverType:C.serverType,anonhash:t}))})}),k.post("/api/test-decryption",function(e,n){return w.runtime={},w.runtime.req=e,w.runtime.res=n,e.body&&e.body.encrypted_data?void y.decryptData({encryptedData:w.runtime.req.body.encrypted_data},w.checkDecrypted):(T.writeLog("APP_004","failed to receive params",{body:e.body}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_004",message:"failed to receive params"})))}),w.checkDecrypted=function(e,n){return e&&n&&n.decrypted?void w.runtime.res.send(JSON.stringify({status:200,type:"SUCCESS",message:"decryption test successful"})):(T.writeLog("APP_005","unable to derypt the data",{success:e,data:n,body:w.runtime.req.body}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_005",message:"ERROR: unable to decrypt the data"})))},k.post("/api/get-addresses",function(e,n){return w.runtime={},w.runtime.req=e,w.runtime.res=n,w.runtime.req.body&&w.runtime.req.body.num_addresses&&w.runtime.req.body.type&&w.runtime.req.body.account?(w.runtime.accountToUse=A.account[w.runtime.req.body.account],w.runtime.accountToUse||w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_006A",message:"ERROR: invalid account",body:w.runtime.req.body})),"OUTGOING"===C.serverType?void w.checkIpAddress({allowedIps:S.remote},w.getAddresses):void w.getAddresses()):(T.writeLog("APP_006","failed to receive params",{body:e.body}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_006",message:"ERROR: invalid params",body:w.runtime.req.body})))}),w.getAddresses=function(){w.runtime.numAddresses=parseInt(w.runtime.req.body.num_addresses,10),"SUBCHAIN"===w.runtime.req.body.type?w.runtime.clientToUse=w.subClient:w.runtime.clientToUse=w.navClient,h.getRandomAccountAddresses({client:w.runtime.clientToUse,accountName:w.runtime.accountToUse,numAddresses:w.runtime.numAddresses},w.returnAddresses)},w.returnAddresses=function(e,n){return e&&n&&n.pickedAddresses?void w.runtime.res.send(JSON.stringify({status:200,type:"SUCCESS",data:{addresses:n.pickedAddresses}})):(T.writeLog("APP_008","failed to pick random addresses",{success:e,data:n}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_008",message:"failed to pick random addresses"})))},k.get("/api/get-nav-balance",function(e,n){w.runtime={},w.runtime.req=e,w.runtime.res=n,w.navClient.getBalance().then(function(e){w.runtime.res.send(JSON.stringify({status:200,type:"SUCCESS",data:{nav_balance:e}}))}).catch(function(e){T.writeLog("APP_009","failed to get the NAV balance",{error:e}),w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_009",message:"failed to get the NAV balance"}))})}),w.checkIpAddress=function(e,n){for(var t=w.runtime.req.connection.remoteAddress||w.runtime.req.socket.remoteAddress,r=!1,i=0;i0){var r=!0,i=!1,s=void 0;try{for(var o,a=n.currentPending[Symbol.iterator]();!(r=(o=a.next()).done);r=!0){var c=o.value;c.confirmations>t&&(t=c.confirmations)}}catch(e){i=!0,s=e}finally{try{!r&&a.return&&a.return()}finally{if(i)throw s}}if(t>60)return T.writeLog("APP_030B","the queue is too long",{highestConf:t},!0),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_030B",message:"the queue is too long"}))}b.getEncryptionKeys({},w.testKeyPair)},w.testKeyPair=function(e,n){return e&&n&&n.privKeyFile&&n.pubKeyFile?void b.testKeyPair({pubKeyFile:n.pubKeyFile,privKeyFile:n.privKeyFile},w.testedKeypair):(T.writeLog("APP_031","failed to get the current keys",{success:e,data:n}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_031",message:"failed to get the current keys"})))},w.testedKeypair=function(e,n){return e&&n&&n.publicKey?(w.runtime.publicKey=n.publicKey,void h.getRandomAccountAddresses({client:w.navClient,accountName:A.account[C.serverType],numAddresses:w.runtime.numAddresses},w.hasRandomAddresses)):(T.writeLog("APP_032","failed to encrypt with selected keypair",{success:e,data:n}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_032",message:"failed to encrypt with selected keypair"})))},w.hasRandomAddresses=function(e,n){return e&&n&&n.pickedAddresses?(w.runtime.navAddresses=n.pickedAddresses,void w.getHash()):(T.writeLog("APP_033","failed to retrieve nav addresses",{success:e,data:n}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_033",message:"failed to retrieve nav addresses"})))},w.hasRandomSubAddresses=function(e,n){return e&&n&&n.pickedAddresses?(w.runtime.subAddresses=n.pickedAddresses,void w.getHash()):(T.writeLog("APP_034","failed to retrieve subchain addresses",{success:e,data:n}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_034",message:"failed to retrieve subchain addresses"})))},w.getHash=function(){g("dist/navtech.js",function(e,n){return e?(T.writeLog("APP_035A","error generating md5 hash",{err:e,hash:n}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAILURE",message:"error generating md5 hash",serverType:C.serverType,error:e}))):void w.returnCheckedNode(n)})},w.returnCheckedNode=function(e){var n=S.local.host?S.local.host:S.local.ipAddress,t={nav_balance:w.runtime.navBalance,sub_balance:w.runtime.subBalance,public_key:w.runtime.publicKey,server_type:C.serverType,min_amount:S.minAmount,max_amount:S.maxAmount,transaction_fee:S.anonFeePercent,server:n,server_port:S.local.port?S.local.port:443,md5:e};t.nav_addresses=w.runtime.navAddresses,w.runtime.res.send(JSON.stringify({status:200,type:"SUCCESS",data:t}))},k.get("/api/status",function(e,n){if(w.runtime={},w.runtime.req=e,w.runtime.res=n,"INCOMING"===C.serverType){var t=new Date,r=t-p.runtime.cycleStart,i=S.scriptInterval-r;w.runtime.res.send(JSON.stringify({status:200,type:"SUCCESS",data:{processing:p.processing,paused:p.paused,nextCycleStart:Math.round(i/1e3)+" seconds"}}))}else if("OUTGOING"===C.serverType){var s=new Date,o=s-v.runtime.cycleStart,a=S.scriptInterval-o;w.runtime.res.send(JSON.stringify({status:200,type:"SUCCESS",data:{processing:v.processing,paused:v.paused,nextCycleStart:Math.round(a/1e3)+" seconds"}}))}})}},function(e,n){e.exports=require("bitcoin-core")},function(e,n){e.exports=require("express")},function(e,n){e.exports=require("https")},function(e,n){e.exports=require("pem")},function(e,n){e.exports=require("body-parser")},function(e,n){e.exports=require("fs")},function(e,n){e.exports=require("config")},function(e,n){e.exports=require("md5-file")},function(e,n,t){"use strict";var r=t(1),i=t(7),s=t(10),o=t(13),a=t(16),c=t(18),u=t(22),d=t(24),l=t(26),g=t(27),p=t(28),v=t(30),m=i.get("INCOMING"),f={processing:!1,paused:!1,runtime:{}};f.init=function(){f.navClient=new r({username:m.navCoin.user,password:m.navCoin.pass,port:m.navCoin.port,host:m.navCoin.host}),f.subClient=new r({username:m.subChain.user,password:m.subChain.pass,port:m.subChain.port,host:m.subChain.host}),o.writeLog("INC_000","server starting"),s.findKeysToRemove({type:"private"},f.startProcessing),setInterval(function(){f.paused===!1?s.findKeysToRemove({type:"private"},f.startProcessing):o.writeLog("INC_001","processing paused",{paused:f.paused})},m.scriptInterval)},f.startProcessing=function(){return f.processing?void o.writeLog("INC_002","server still processing",{processing:f.processing}):(f.processing=!0,f.runtime={},f.runtime.cycleStart=new Date,void a.run({navClient:f.navClient,subClient:f.subClient,settings:m},f.preFlightComplete))},f.preFlightComplete=function(e,n){return e?(f.runtime.navBalance=n.navBalance,f.runtime.subBalance=n.subBalance,void c.run({navClient:f.navClient},f.holdingProcessed)):(o.writeLog("INC_003","preflight checks failed",{success:e,data:n},!0),void(f.processing=!1))},f.holdingProcessed=function(e,n){return e?void u.run({settings:m,navClient:f.navClient},f.outgoingSelected):(o.writeLog("INC_004","failed to process the holding account",{success:e,data:n},!0),void(f.processing=!1))},f.outgoingSelected=function(e,n){return e?n.returnAllToSenders?void d.run({navClient:f.navClient},f.allPendingReturned):(f.runtime.chosenOutgoing=n.chosenOutgoing,f.runtime.outgoingNavBalance=n.outgoingNavBalance,f.runtime.holdingEncrypted=n.holdingEncrypted,f.runtime.outgoingPubKey=n.outgoingPubKey,void l.run({navClient:f.navClient,outgoingNavBalance:n.outgoingNavBalance,subBalance:f.runtime.subBalance},f.currentBatchPrepared)):(o.writeLog("INC_005","failed to find outgoing server",{success:e,data:n},!0),void(f.processing=!1))},f.allPendingReturned=function(e,n){return console.log("STATUS: IncomingServer.allPendingReturned",e,n),e?(o.writeLog("INC_007","returned all pending to sender",{success:e,data:n},!0),void(f.processing=!1)):(o.writeLog("INC_006","failed to return all pending to sender",{success:e,data:n},!0),void(f.processing=!1))},f.currentBatchPrepared=function(e,n){return e&&n&&n.currentBatch?(f.runtime.currentBatch=n.currentBatch,void g.run({subClient:f.subClient,chosenOutgoing:f.runtime.chosenOutgoing,currentBatch:n.currentBatch},f.retrievedSubchainAddresses)):void(f.processing=!1)},f.retrievedSubchainAddresses=function(e,n){return e&&n&&n.subAddresses?void p.run({currentBatch:f.runtime.currentBatch,outgoingPubKey:f.runtime.outgoingPubKey,subClient:f.subClient,navClient:f.navClient,subAddresses:n.subAddresses,settings:m},f.transactionsProcessed):(o.writeLog("INC_009","failed to retrieve subchain addresses",{success:e,data:n},!0),void d.run({navClient:f.navClient},f.allPendingReturned))},f.transactionsProcessed=function(e,n){return e&&n?(f.runtime.successfulSubTransactions=n.successfulSubTransactions,f.runtime.transactionsToReturn=n.transactionsToReturn,f.runtime.transactionsToReturn&&f.runtime.transactionsToReturn.length>0?(o.writeLog("INC_011","failed to process some transactions",{success:e,data:n},!0),void d.fromList({navClient:f.navClient,transactionsToReturn:n.transactionsToReturn},f.failedTransactionsReturned)):void f.failedTransactionsReturned(!0)):(o.writeLog("INC_010","failed to process transactions",{success:e,data:n},!0),void d.run({navClient:f.navClient},f.allPendingReturned))},f.failedTransactionsReturned=function(e,n){e||o.writeLog("INC_012","failed to return failed transactions to sender",{success:e,data:n},!0),v.run({successfulSubTransactions:f.runtime.successfulSubTransactions,holdingEncrypted:f.runtime.holdingEncrypted,navClient:f.navClient},f.spentToHolding)},f.spentToHolding=function(e,n){e||(o.writeLog("INC_013","failed to spend successful to holding",{success:e,data:n},!0),f.paused=!0,f.processing=!1),f.processing=!1},e.exports=f},function(e,n,t){"use strict";var r=t(6),i=t(11),s=t(7),o=t(12),a=t(13),c=t(15),u=s.get("GLOBAL"),d={};d.testKeyPair=function(e,n){var t=["pubKeyFile","privKeyFile"];if(o.intersection(Object.keys(e),t).length!==t.length)return a.writeLog("ENC_001","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to EncryptionKeys.testKeyPair"});try{var s="NWMZ2atWCbUnVDKgmPHeTbGLmMUXZxZ3J3",c=i.createPublicKey(r.readFileSync(e.pubKeyFile)),u=c.encrypt(s,"utf8","base64",i.RSA_PKCS1_PADDING),d=i.createPrivateKey(r.readFileSync(e.privKeyFile)),l=d.decrypt(u,"base64","utf8",i.RSA_PKCS1_PADDING);if(l!==s)return a.writeLog("ENC_002","failed to decrypt",{decrypted:l,address:s}),void n(!1,{message:"failed to decrypt"});r.readFile(e.pubKeyFile,"utf-8",function(e,t){return e?(a.writeLog("ENC_003","failed to get public key contents",{error:e,fileData:t}),void n(!1,{message:e})):void n(!0,{publicKey:t})})}catch(e){a.writeLog("ENC_004","failed to encrypt",{error:e}),n(!1,{message:"failed to encrypt"})}},d.getEncryptionKeys=function(e,n){var t=new Date,i=d.getMidnight(t),s=c.keyFolders.private.path+i+c.keyFolders.private.suffix,o=c.keyFolders.public.path+i+c.keyFolders.public.suffix;r.exists(s,function(e){r.exists(o,function(t){return e&&t?void n(!0,{privKeyFile:s,pubKeyFile:o}):void d.generateKeys({privKeyFile:s,pubKeyFile:o},n)})})},d.generateKeys=function(e,n){var t=["pubKeyFile","privKeyFile"];if(o.intersection(Object.keys(e),t).length!==t.length)return a.writeLog("ENC_005","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to EncryptionKeys.generateKeys"});var s=c.encryptionStrength[u.serverType],d=new i.generatePrivateKey(s,65537),l=d.toPrivatePem().toString("ascii"),g=d.toPublicPem().toString("ascii");r.writeFile(e.privKeyFile,l,function(t){return t?(a.writeLog("ENC_005","unable to write private key to file system",{privKeyFile:e.privKeyFile,error:t}),void n(!1,{message:"unable to write private key to file system"})):void r.writeFile(e.pubKeyFile,g,function(t){return t?(a.writeLog("ENC_007","unable to write public key to file system",{pubKeyFile:e.pubKeyFile,error:t}),void n(!1)):void n(!0,{privKeyFile:e.privKeyFile,pubKeyFile:e.pubKeyFile})})})},d.findKeysToRemove=function(e,n){var t=["type"];return o.intersection(Object.keys(e),t).length!==t.length?(a.writeLog("ENC_008","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to EncryptionKeys.findKeysToRemove"})):void r.readdir(c.keyFolders[e.type].path,function(t,r){if(t)return a.writeLog("ENC_009","failed to open the keys directory",{error:t,files:r}),void n(!1,{message:"failed to open directory"});for(var i=new Date,s=d.getMidnight(i),o=[],u=0;uc.keyPeriod&&o.push(r[u])}o.length>0?d.removeKeys({forRemoval:o,type:e.type},n):"private"===e.type?d.findKeysToRemove({type:"public"},n):n(!0,{message:"nothing to remove"})})},d.removeKeys=function(e,n){0===e.forRemoval.length?"private"===e.type?d.findKeysToRemove({type:"public"},n):n(!0,{message:"all keys removed"}):r.exists(c.keyFolders[e.type].path+e.forRemoval[0],function(t){t?r.unlink(c.keyFolders[e.type].path+e.forRemoval[0],function(t){t&&a.writeLog("ENC_010","failed to remove the key",{error:t,key:e.forRemoval}),d.removeKeys({forRemoval:e.forRemoval.slice(1),type:e.type},n)}):d.removeKeys({forRemoval:e.forRemoval.slice(1),type:e.type},n)})},d.getMidnight=function(e){return parseInt(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate()),10)},e.exports=d},function(e,n){e.exports=require("ursa")},function(e,n){e.exports=require("lodash")},function(e,n,t){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i=t(14),s=t(7),o=s.get("GLOBAL"),a=!1;"INCOMING"===o.serverType&&(a=s.get("INCOMING")),"OUTGOING"===o.serverType&&(a=s.get("OUTGOING"));var c=encodeURIComponent(a.smtp.user)+":"+encodeURIComponent(a.smtp.pass),u={};u.transporter=i.createTransport("smtps://"+c+"@"+a.smtp.server),u.writeLog=function(e,n,t,i){i&&u.sendMail(e,n,t);var s=new Date,o="\r\n";o+="Date: "+s+"\r\n",o+="Error Code: "+e+"\r\n",o+="Error Message: "+n+"\r\n";for(var a in t)if(t.hasOwnProperty(a)){var c=t[a];"object"===r(t[a])&&(c=JSON.stringify(t[a])),o+=a+": "+c+"\r\n"}o+="\r\n-----------------------------------------------------------\r\n",console.log(o)},u.sendMail=function(e,n,t){var r={from:'"Navtech System" <'+a.smtp.user+">",to:a.notificationEmail,subject:"Navtech System Message - "+a.local.ipAddress+" ("+o.serverType+") "+e,text:e+" - "+n,attachments:[{filename:"data.json",content:JSON.stringify(t)}]};u.transporter.sendMail(r,function(e,n){return e?console.log("nodemail error",e):console.log("nodemail success: "+n.response)})},e.exports=u},function(e,n){e.exports=require("nodemailer")},function(e,n){e.exports={keyFolders:{private:{path:"./keys/private/",suffix:"_private.pem"},public:{path:"./keys/public/",suffix:"_public.pub"}},keyPeriod:6048e5,minKeyStamp:14647392e5,account:{INCOMING:"incomingAccount",OUTGOING:"outgoingAccount",HOLDING:"holdingAccount"},maxAddresses:250,maxHolding:100,minNavTransactions:3,maxNavTransactions:8,txFee:.001,maxEncryptionAttempts:10,encryptionStrength:{INCOMING:2048,OUTGOING:1024},encryptionOutput:{INCOMING:344,OUTGOING:172},subCoinsPerTx:10,subChainTxFee:.001,minConfs:1,blockThreshold:{checking:5,processing:3}}},function(e,n,t){"use strict";var r=t(12),i=t(15),s=t(13),o=t(17),a={};a.run=function(e,n){var t=["navClient","subClient","settings"];return r.intersection(Object.keys(e),t).length!==t.length?(s.writeLog("PRE_001","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to Preflight.checkNavBlocks"})):(a.runtime={callback:n,navClient:e.navClient,subClient:e.subClient,settings:e.settings},void o.checkBlockHeight({client:a.runtime.navClient,blockThreshold:i.blockThreshold.processing},a.navBlocksChecked))},a.navBlocksChecked=function(e,n){return e&&n?(a.runtime.navBalance=n.balance,void a.runtime.navClient.setTxFee(parseFloat(i.txFee)).then(function(){o.checkBlockHeight({client:a.runtime.subClient,blockThreshold:i.blockThreshold.processing},a.subBlocksChecked)}).catch(function(e){s.writeLog("PRE_003","failed to set NAV tx fee",{err:e}),a.runtime.callback(!1,{message:"failed to set NAV tx fee"})})):(s.writeLog("PRE_002","navClient block check failed",{status:e,data:n}),void a.runtime.callback(!1,{message:"navClient block check failed"}))},a.subBlocksChecked=function(e,n){return e&&n?(a.runtime.subBalance=n.balance,void o.unlockWallet({settings:a.runtime.settings,client:a.runtime.navClient,type:"navCoin"},a.navClientUnlocked)):(s.writeLog("PRE_004","subClient block check failed",{status:e,data:n}),void a.runtime.callback(!1,{message:"subClient block check failed"}))},a.navClientUnlocked=function(e,n){return e?void o.unlockWallet({settings:a.runtime.settings,client:a.runtime.subClient,type:"subChain"},a.subClientUnlocked):(s.writeLog("PRE_005","navClient failed to unlock",{status:e,data:n}),void a.runtime.callback(!1,{message:"navClient failed to unlock"}))},a.subClientUnlocked=function(e,n){return e?void a.runtime.subClient.setTxFee(parseFloat(i.subChainTxFee)).then(function(){a.runtime.callback(!0,{navBalance:a.runtime.navBalance,subBalance:a.runtime.subBalance})}).catch(function(e){s.writeLog("PRE_003","failed to set SUB tx fee",{err:e}),a.runtime.callback(!1,{message:"failed to set SUB tx fee"})}):(s.writeLog("PRE_006","subClient failed to unlock",{status:e,data:n}),void a.runtime.callback(!1,{message:"subClient failed to unlock"}))},e.exports=a},function(e,n,t){"use strict";var r=t(7),i=t(12),s=t(13),o=r.get("GLOBAL"),a={};a.unlockWallet=function(e,n){var t=["settings","client","type"];if(i.intersection(Object.keys(e),t).length!==t.length)return s.writeLog("NAV_001","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to NavCoin.unlockWallet"});if(o.encryptedWallet===!1)return void n(!0);var r=e.settings.scriptInterval/1e3;e.client.walletPassphrase(e.settings[e.type].walletPassphrase,r).then(function(){n(!0)}).catch(function(t){switch(t.code){case-17:n(!0);break;default:return n(!1,{message:"failed to unlock"}),void s.writeLog("NAV_002","failed to unlock "+e.type+" wallet",{error:t})}})},a.lockWallet=function(e,n){var t=["type","client"];return i.intersection(Object.keys(e),t).length!==t.length?(s.writeLog("NAV_003","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to NavCoin.lockWallet"})):void e.client.walletLock().then(function(){s.writeLog("NAV_004","locked the "+e.type+" wallet",e),a.unlockWallet(e,n)}).catch(function(t){n(!1,{message:"failed to lock"}),s.writeLog("NAV_005","failed to lock "+e.type+" wallet",{error:t})})},a.filterUnspent=function(e,n){var t=["unspent","client","accountName"];if(i.intersection(Object.keys(e),t).length!==t.length)return s.writeLog("NAV_006","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to NavCoin.filterUnspent"});try{e.client.getAddressesByAccount(e.accountName).then(function(t){var r=!1,i=[],s=!0,o=!1,a=void 0;try{for(var c,u=e.unspent[Symbol.iterator]();!(s=(c=u.next()).done);s=!0){var d=c.value;t.indexOf(d.address)!==-1&&(r=!0,i.push(d))}}catch(e){o=!0,a=e}finally{try{!s&&u.return&&u.return()}finally{if(o)throw a}}return r?void n(!0,{currentPending:i}):void n(!0)}).catch(function(t){s.writeLog("NAV_007","failed to get address by account",{error:t,options:e}),n(!1)})}catch(n){s.writeLog("NAV_008","failed to filter",{error:n,options:e})}},a.checkBlockHeight=function(e,n){var t=["client","blockThreshold"];return i.intersection(Object.keys(e),t).length!==t.length?(s.writeLog("NAV_009","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to NavCoin.checkBlockHeight"})):void e.client.getInfo().then(function(t){e.client.getBlockCount().then(function(r){return parseInt(r,10)-e.blockThreshold>parseInt(t.blocks,10)?(s.writeLog("NAV_010","client is not synced with the latest blocks",{walletInfo:t,blockCount:r}),void n(!1,{message:"client is not synced with the latest blocks"})):void n(!0,{balance:t.balance})}).catch(function(t){s.writeLog("NAV_011","failed to get block count",{error:t,options:e}),n(!1,{message:"failed to get block count"})})}).catch(function(t){s.writeLog("NAV_012","failed to get info",{error:t,options:e}),n(!1,{message:"failed to get info"})})},a.validateAddresses=function(e,n){var t=["client","addresses"];return i.intersection(Object.keys(e),t).length!==t.length?(s.writeLog("NAV_013","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to NavCoin.validateAddresses"})):0===e.addresses.length?void n(!0):void e.client.validateAddress(e.addresses[0]).then(function(t){return t.isvalid!==!0?(s.writeLog("NAV_014","provided address is invalid",{address:e.addresses[0]}),void n(!1,{message:"provided address is invalid"})):void a.validateAddresses({addresses:e.addresses.slice(1),client:e.client},n)}).catch(function(t){s.writeLog("NAV_015","failed to validate address",{error:t,options:e}),n(!1,{message:"failed to validate address"})})},e.exports=a},function(e,n,t){"use strict";var r=t(12),i=t(15),s=t(13),o=t(17),a=t(19),c=t(20),u=t(21),d={};d.run=function(e,n){var t=["navClient"];return r.intersection(Object.keys(e),t).length!==t.length?(s.writeLog("RFL_001","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to RefillOutgoing.checkHoldingAccount"})):(d.runtime={callback:n,navClient:e.navClient},void d.getUnspent())},d.getUnspent=function(){d.runtime.navClient.listUnspent().then(function(e){return e.length<1?(s.writeLog("RFL_002","no unspent in holding account",{unspent:e}),void d.runtime.callback(!0,{message:"no unspent in holding account"})):void o.filterUnspent({unspent:e,client:d.runtime.navClient,accountName:i.account.HOLDING},d.holdingFiltered)}).catch(function(e){s.writeLog("RFL_003","failed to list unspent",{error:e}),d.runtime.callback(!1,{message:"failed to list unspent"})})},d.holdingFiltered=function(e,n){return!e||!n||!n.currentPending||n.currentPending.length<1?void d.runtime.callback(!0,{message:"no pending to clear from account"}):(d.runtime.currentHolding=n.currentPending,void d.processHolding())},d.processHolding=function(){return d.runtime.currentHolding.length<1?(s.writeLog("RFL_005","all holding processed",{currentHolding:d.runtime.currentHolding}),void d.runtime.callback(!0,{message:"all holding processed"})):void d.checkIfHoldingIsSpendable()},d.checkIfHoldingIsSpendable=function(){return d.runtime.currentHolding[0].confirmations>i.minConfs?void a.getEncrypted({transaction:d.runtime.currentHolding[0],client:d.runtime.navClient},d.holdingDecrypted):(s.writeLog("RFL_006","holding account transaction not spendable",{currentHolding:d.runtime.currentHolding}),d.runtime.currentHolding.splice(0,1),void d.processHolding())},d.holdingDecrypted=function(e,n){if(!(e&&n&&n.decrypted&&n.transaction))return s.writeLog("RFL_007","failed to decrypt holding transaction data",{success:e,data:n}),d.runtime.currentHolding.splice(0,1),void d.processHolding();d.runtime.holdingTransaction=n.transaction;var t=JSON.parse(n.decrypted);if(t.constructor!==Array)return s.writeLog("RFL_007A","decrypted data not an array of addresses",{currentHolding:d.runtime.currentHolding}),d.runtime.currentHolding.splice(0,1),void d.processHolding();for(var r=Math.ceil(Math.random()*(t.length-(i.minNavTransactions-1)))+(i.minNavTransactions-1),o=[];o.lengthi-a||l===o.runtime.addresses.length-1){var v=Math.round(i-a);o.runtime.transactions[o.runtime.addresses[l]]=v/e,a+=v}else o.runtime.transactions[o.runtime.addresses[l]]=p/e,a+=p}o.runtime.callback(!0,{transactions:o.runtime.transactions})},o.outgoing=function(e,n){var t=["transaction","address","amount"];return r.intersection(Object.keys(e),t).length!==t.length?(i.writeLog("RND_002","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to RandomizeTransactions.outgoing"})):(o.runtime={callback:n,transaction:e.transaction,address:e.address,amount:e.amount,transactions:[]},void o.randomizeOutgoing())},o.randomizeOutgoing=function(){for(var e=Math.ceil(Math.random()*(s.maxNavTransactions-s.minNavTransactions))+s.minNavTransactions,n=o.runtime.amount,t=1e8,r=n*t,i=0,a=r/e,c=Math.floor(1.5*a*t),u=Math.floor(.5*a*t),d=0;dr-i||d===e-1){var p=Math.round(r-i);o.runtime.transactions.push(p/t),i+=p}else o.runtime.transactions.push(g/t),i+=g}o.runtime.callback(!0,{partialTransactions:o.runtime.transactions})},o.getRandomAccountAddresses=function(e,n){var t=["client","accountName","numAddresses"];return r.intersection(Object.keys(e),t).length!==t.length?(i.writeLog("RND_003","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to RandomizeTransactions.getRandomAccountAddresses"})):void e.client.getAddressesByAccount(e.accountName).then(function(t){o.chooseRandomAddresses({accountName:e.accountName,numAddresses:e.numAddresses,addresses:t},n)}).catch(function(t){i.writeLog("RND_004","get account address failed",{options:e,error:t}),n(!1,{message:"get account address failed"})})},o.chooseRandomAddresses=function(e,n){var t=["addresses","accountName","numAddresses"];if(r.intersection(Object.keys(e),t).length!==t.length)return i.writeLog("RND_005","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to RandomizeTransactions.chooseRandomAddresses"});for(var s=[],o=0;s.length=a.maxEncryptionAttempts)return s.writeLog("PROI_006","max public key encryption failures",{transaction:n,counter:t,encrypted:c}),void u.transactionFailed();u.makeSubchainTx(c,n)}catch(e){return s.writeLog("PROI_007","encrypted address invalid",{transaction:n,error:e}),void u.transactionFailed()}},u.makeSubchainTx=function(e,n){c.send({client:u.runtime.subClient,address:u.runtime.subAddresses[0],amount:a.subCoinsPerTx,transaction:n,encrypted:e},u.sentSubToOutgoing)},u.sentSubToOutgoing=function(e,n){return e&&n&&n.sendOutcome?(u.runtime.successfulSubTransactions.push(n.transaction),u.runtime.subAddresses.splice(0,1),u.runtime.remainingTransactions.splice(0,1),void u.processPending()):(s.writeLog("PROI_008","failed subClient send to address",{transaction:n.transaction,error:n.error}),void u.transactionFailed())},e.exports=u},function(e,n,t){"use strict";var r=t(7),i=t(12),s=t(13),o=t(17),a=r.get("GLOBAL"),c=!1;"INCOMING"===a.serverType&&(c=r.get("INCOMING")),"OUTGOING"===a.serverType&&(c=r.get("OUTGOING"));var u={};u.send=function(e,n){var t=["client","address","amount","transaction"];if(i.intersection(Object.keys(e),t).length!==t.length)return s.writeLog("STA_001","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to SelectOutgoing.run"});if(a.preventSend)return s.writeLog("STA_TEST_001","preventSend triggered",{options:e}),void n(!0,{sendOutcome:"dummy-tx-id",transaction:e.transaction});if(u.runtime={},e.counter&&e.counter>7)return s.writeLog("STA_002","max send attempts reached",{transaction:e.transaction,counter:e.counter}),void n(!1,{transaction:e.transaction,error:"max send attempts reached"});var r=1e8,d=Math.round(e.amount*r),l=d/r;e.client.sendToAddress(e.address,l,null,null,e.encrypted).then(function(t){if(t)return void n(!0,{sendOutcome:t,transaction:e.transaction})}).catch(function(t){if(t.code===-13&&!e.triedToUnlock){u.runtime.options=e,u.runtime.callback=n;var r=e.client.port===c.navCoin.port?"navCoin":"subChain";return void o.unlockWallet({settings:c,client:e.client,type:r},u.walletUnlocked)}s.writeLog("STA_003","failed send to address",{transaction:e.transaction,error:t}),setTimeout(function(){var t=e.counter?e.counter+1:1,r={client:e.client,address:e.address,amount:e.amount,transaction:e.transaction,encrypted:e.encrypted,counter:t,triedToUnlock:!1};u.send(r,n)},3e4)})},u.walletUnlocked=function(e,n){if(!e)return s.writeLog("STA_004","unable to unlock wallet",{success:e,data:n}),void u.runtime.callback(!1,{transaction:u.runtime.options.transaction,error:n});var t={client:u.runtime.options.client,address:u.runtime.options.address,amount:u.runtime.options.amount,transaction:u.runtime.options.transaction,encrypted:u.runtime.options.encrypted,counter:u.runtime.options.counter,triedToUnlock:!0};u.send(t,u.runtime.callback)},e.exports=u},function(e,n,t){"use strict";var r=t(12),i=t(13),s=t(21),o=t(15),a=t(20),c={};c.run=function(e,n){var t=["successfulSubTransactions","holdingEncrypted","navClient"];return r.intersection(Object.keys(e),t).length!==t.length?(i.writeLog("STH_001","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to SpendToHolding.run"})):(c.runtime={callback:n,successfulSubTransactions:e.successfulSubTransactions,holdingEncrypted:e.holdingEncrypted,navClient:e.navClient},void s.getRandomAccountAddresses({client:c.runtime.navClient,accountName:o.account.HOLDING,numAddresses:1},c.createHoldingTransactions))},c.createHoldingTransactions=function(e,n){if(!e)return i.writeLog("STH_002","could not retrieve holding addresses",{success:e,data:n}),void c.runtime.callback(!1,{message:"could not retrieve holding addresses"});var t=1e8,r=0,s=!0,u=!1,d=void 0;try{for(var l,g=c.runtime.successfulSubTransactions[Symbol.iterator]();!(s=(l=g.next()).done);s=!0){var p=l.value;r+=Math.round(p.amount*t)-Math.round(o.txFee*t)}}catch(e){u=!0,d=e}finally{try{!s&&g.return&&g.return()}finally{if(u)throw d}}for(var v=r/t,m=[],f=0;fd.runtime.settings.maxAmount?(o.writeLog("PREPO_006","decrypted amount is larger than maxAmount",{success:e}),void d.failedTransaction()):(n.decrypted.s!==d.runtime.settings.secret&&(o.writeLog("PREPO_007","secret mismatch",{success:e}),d.failedTransaction()),void d.testDecrypted(n.decrypted,n.transaction)):(o.writeLog("PREPO_005","transaction has invalid params",{success:e}),void d.failedTransaction()):(o.writeLog("PREPO_004","failed to decrypt transaction",{success:e}),void d.failedTransaction())},d.testDecrypted=function(e,n){d.runtime.navClient.validateAddress(e.a).then(function(t){return t.isvalid!==!0?(o.writeLog("PREPO_008","recipient address is invalid",{transaction:n}),void d.failedTransaction()):d.runtime.navBalance>d.runtime.sumPending+parseFloat(e.n)?(d.runtime.sumPending=d.runtime.sumPending+parseFloat(e.n), -d.runtime.currentBatch.push({decrypted:e,transaction:n}),d.runtime.currentPending.splice(0,1),void d.processTransaction()):void d.runtime.callback(!0,{failedSubTransactions:d.runtime.failedSubTransactions,currentBatch:d.runtime.currentBatch})}).catch(function(t){o.writeLog("PREPO_009","navClient failed validate address",{decrypted:e,transaction:n,error:t}),d.failedTransaction()})},e.exports=d},function(e,n,t){"use strict";var r=t(12),i=t(13),s=t(21),o=t(29),a={};a.run=function(e,n){var t=["currentBatch","settings","navClient"];return r.intersection(Object.keys(e),t).length!==t.length?(i.writeLog("PROO_001","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to ProcessOutgoing.run"})):(a.runtime={callback:n,currentBatch:e.currentBatch,settings:e.settings,navClient:e.navClient,successfulTransactions:[],failedTransactions:[]},a.runtime.remainingTransactions=e.currentBatch,void a.processPending())},a.processPending=function(){return a.runtime.remainingTransactions.length<1?void a.runtime.callback(!0,{successfulTransactions:a.runtime.successfulTransactions,failedTransactions:a.runtime.failedTransactions}):(a.runtime.partialTransactions=[],void s.outgoing({transaction:a.runtime.remainingTransactions[0],amount:a.runtime.remainingTransactions[0].decrypted.n,address:a.runtime.remainingTransactions[0].decrypted.a},a.amountsRandomized))},a.transactionFailed=function(){a.runtime.failedTransactions.push(a.runtime.remainingTransactions[0]),a.runtime.remainingTransactions.splice(0,1),a.processPending()},a.amountsRandomized=function(e,n){return e&&n?(a.runtime.partialTransactions=n.partialTransactions,void a.createNavTransactions()):(i.writeLog("PROO_002","failed to randomize transaction",{success:e,data:n},!0),void a.transactionFailed())},a.mockSend=function(){i.writeLog("PROO_003A","mock nav sent",{transaction:a.runtime.remainingTransactions[0]}),a.runtime.successfulTransactions.push({transaction:a.runtime.remainingTransactions[0].transaction}),a.runtime.remainingTransactions.splice(0,1),a.processPending()},a.createNavTransactions=function(){return a.runtime.partialTransactions.length<1?(i.writeLog("PROO_003","all partial nav sent",{transaction:a.runtime.remainingTransactions[0].transaction}),a.runtime.successfulTransactions.push({transaction:a.runtime.remainingTransactions[0].transaction}),a.runtime.remainingTransactions.splice(0,1),void a.processPending()):void o.send({client:a.runtime.navClient,address:a.runtime.remainingTransactions[0].decrypted.a,amount:a.runtime.partialTransactions[0],transaction:a.runtime.remainingTransactions[0]},a.sentPartialNav)},a.sentPartialNav=function(e,n){return e&&n&&n.sendOutcome?(a.runtime.partialTransactions.splice(0,1),void a.createNavTransactions()):(i.writeLog("PROO_004","failed nav send to address",n,!0),void a.runtime.callback(!1,{message:"failed sending partial transaction to address",failedTransaction:a.runtime.remainingTransactions[0],remainingPartials:a.runtime.partialTransactions}))},e.exports=a},function(e,n,t){"use strict";var r=t(12),i=t(13),s=t(29),o=t(15),a={};a.run=function(e,n){var t=["settings","navClient"];return r.intersection(Object.keys(e),t).length!==t.length?(i.writeLog("PAY_001","invalid options",{options:e,required:t}),void n(!1,{message:"invalid options provided to PayoutFee.run"})):(a.runtime={callback:n,settings:e.settings,navClient:e.navClient},void a.send())},a.send=function(){a.runtime.navClient.getBalance().then(function(e){if(e65535)&&(C.errors.push("PORT_OUT_OF_RANGE for "+t+", must be between 1 and 65535 "),!0)}function c(e,n,t){var r=parseInt(e,10);return n.min&&rn.max)||(C.errors.push("INT_TOO_LARGE for "+t+", must be smaller than "+n.max),!1)}function u(e,n,t){var r=parseFloat(e);if(n.min&&rn.max)return C.errors.push("FLOAT_TOO_LARGE for "+t+", must be smaller than "+n.max),!1;if(n.decimals&&2===n.decimals){var i=e.toString().search(A)>=0;if(!i)return C.errors.push("FLOAT_INCORRECT_FORMAT for "+t+", must have a maximum of 2 decimal places "),!1}return!0}function d(e,n,t){return!!f.isEmail(e)||(C.errors.push("INVALID_DOMAIN for "+t+", must be a valid fully qualified domain name"),!1)}function l(e,n,t){return 34===e.length||"N"===e.charAt(0)||(C.errors.push("INVALID_NAV_ADDRESSS for "+t+", must be 34 characters and starting with N"),!1)}function g(e,n,t){return!n.length||e.length===n.length||(C.errors.push("INCORRECT_LENGTH for "+t+", must be "+n.length+" characters"+e.length+" provided"),!1)}function p(e,n,t){return e===n.value||(C.errors.push("INCORRECT_VALUE for "+t+", must equal "+n.value),!1)}var v="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},m=t(7),f=t(37),y=t(13),h=m.get("GLOBAL"),b=t(38),T=t(39),A=/^(\d+)?([.]?\d{0,2})?$/,C={errors:[]};C.validateSettings=function(e,n){if(C.errors=[],"INCOMING"===h.serverType)r(e.settings,e.ignore,b);else{if("OUTGOING"!==h.serverType)return y.writeLog("VAL_001","invalid server type",{options:e}),void n(!1);r(e.settings,e.ignore,T)}C.errors.length<1?n(!0):(y.writeLog("VAL_002","invalid settings",{errors:C.errors}),n(!1))},e.exports=C},function(e,n){e.exports=require("validator")},function(e,n){e.exports={local:{ipAddress:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"PORT"}},remote:{host:{required:!1,type:"DOMAIN"},ipAddress:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"PORT"}},scriptInterval:{required:!0,type:"INT",min:3e4},minAmount:{required:!0,type:"INT",min:10},maxAmount:{required:!0,type:"INT",min:10},anonFeePercent:{required:!0,type:"FLOAT",min:0,max:99,decimals:2},notificationEmail:{required:!0,type:"EMAIL"},smtp:{user:{required:!0,type:"EMAIL"},pass:{required:!0,type:"STRING"},server:{required:!0,type:"DOMAIN"}},navCoin:{user:{required:!0,type:"STRING"},pass:{required:!0,type:"STRING"},ip:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"VALUE",value:"44444"},walletPassphrase:{required:{conditional:"GLOBAL.encryptedWallet"},type:"STRING"}},subChain:{user:{required:!0,type:"STRING"},pass:{required:!0,type:"STRING"},ip:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"VALUE",value:"33333"},walletPassphrase:{required:{conditional:"GLOBAL.encryptedWallet"},type:"STRING"}},secretOptions:{salt:{required:!0,type:"STRING",length:32},saltRounds:{required:!0,type:"INT",min:1,max:20}},secret:{required:!0,type:"STRING",length:42}}},function(e,n){e.exports={local:{ipAddress:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"PORT"}},remote:{host:{required:!1,type:"DOMAIN"},ipAddress:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"PORT"}},scriptInterval:{required:!0,type:"INT",min:12e4},minAmount:{required:!0,type:"INT",min:10},maxAmount:{required:!0,type:"INT",min:10},navPoolAmount:{required:!0,type:"INT",min:5e4},txFeePayoutMin:{required:!0,type:"INT",min:1},anonTxFeeAddress:{required:!0,type:"NAV_ADDRESS"},notificationEmail:{required:!0,type:"EMAIL"},smtp:{user:{required:!0,type:"EMAIL"},pass:{required:!0,type:"STRING"},server:{required:!0,type:"DOMAIN"}},navCoin:{user:{required:!0,type:"STRING"},pass:{required:!0,type:"STRING"},ip:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"VALUE",value:"44444"},walletPassphrase:{required:{conditional:"GLOBAL.encryptedWallet"},type:"STRING"}},subChain:{user:{required:!0,type:"STRING"},pass:{required:!0,type:"STRING"},ip:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"VALUE",value:"33333"},walletPassphrase:{required:{conditional:"GLOBAL.encryptedWallet"},type:"STRING"}},secret:{required:!0,type:"STRING",length:42}}}]); \ No newline at end of file +!function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return e[r].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){e?i():T.writeLog("APP_002","invalid server settings",k)}function i(){w.navClient=new s({username:k.navCoin.user,password:k.navCoin.pass,port:k.navCoin.port,host:k.navCoin.host}),w.subClient=new s({username:k.subChain.user,password:k.subChain.pass,port:k.subChain.port,host:k.subChain.host}),k.ssl?d.exists(k.ssl.key,function(e){d.exists(k.ssl.crt,function(t){if(!e||!t)return void T.writeLog("APP_003","unable to find user defined ssl certificate",k.ssl);var n={key:d.readFileSync(k.ssl.key),cert:d.readFileSync(k.ssl.crt),requestCert:!1,rejectUnauthorized:!1};P.use(c.json()),P.use(c.urlencoded({extended:!0})),o.createServer(n,P).listen(k.local.port,function(){S()})})}):u.createCertificate({days:1,selfSigned:!0},function(e,t){var n={key:t.serviceKey,cert:t.certificate,requestCert:!1,rejectUnauthorized:!1};P.use(c.json()),P.use(c.urlencoded({extended:!0})),o.createServer(n,P).listen(k.local.port,function(){S()})})}var s=n(1),a=n(2),o=n(3),u=n(4),c=n(5),d=n(6),l=n(7),g=n(8),p=n(9),m=n(33),v=n(38),f=n(17),h=n(19),y=n(21),b=n(11),T=n(13),A=n(15),C=l.get("GLOBAL"),k=!1;"INCOMING"===C.serverType&&(k=l.get("INCOMING")),"OUTGOING"===C.serverType&&(k=l.get("OUTGOING")),T.writeLog("SYS_001","Server Starting",{memes:["harambe","rustled jimmies"]},!0);var P=a(),w={};k?v.validateSettings({settings:k},r):T.writeLog("APP_001","invalid global server type",C.serverType);var S=function(){"INCOMING"===C.serverType?(p.init(),L()):"OUTGOING"===C.serverType&&(m.init(),L())},L=function(){P.get("/",function(e,t){g("dist/navtech.js",function(e,n){return e?void t.send(JSON.stringify({status:200,type:"FAILURE",message:"error generating md5 hash",serverType:C.serverType,error:e})):void t.send(JSON.stringify({status:200,type:"SUCCESS",message:"server is running!",serverType:C.serverType,anonhash:n}))})}),P.post("/api/test-decryption",function(e,t){return w.runtime={},w.runtime.req=e,w.runtime.res=t,e.body&&e.body.encrypted_data?void h.decryptData({encryptedData:w.runtime.req.body.encrypted_data},w.checkDecrypted):(T.writeLog("APP_004","failed to receive params",{body:e.body}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_004",message:"failed to receive params"})))}),w.checkDecrypted=function(e,t){return e&&t&&t.decrypted?void w.runtime.res.send(JSON.stringify({status:200,type:"SUCCESS",message:"decryption test successful"})):(T.writeLog("APP_005","unable to derypt the data",{success:e,data:t,body:w.runtime.req.body}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_005",message:"ERROR: unable to decrypt the data"})))},P.post("/api/get-addresses",function(e,t){return w.runtime={},w.runtime.req=e,w.runtime.res=t,w.runtime.req.body&&w.runtime.req.body.num_addresses&&w.runtime.req.body.type&&w.runtime.req.body.account?(w.runtime.accountToUse=A.account[w.runtime.req.body.account],w.runtime.accountToUse||w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_006A",message:"ERROR: invalid account",body:w.runtime.req.body})),"OUTGOING"===C.serverType?void w.checkIpAddress({allowedIps:k.remote},w.getAddresses):void w.getAddresses()):(T.writeLog("APP_006","failed to receive params",{body:e.body}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_006",message:"ERROR: invalid params",body:w.runtime.req.body})))}),w.getAddresses=function(){w.runtime.numAddresses=parseInt(w.runtime.req.body.num_addresses,10),"SUBCHAIN"===w.runtime.req.body.type?w.runtime.clientToUse=w.subClient:w.runtime.clientToUse=w.navClient,y.getRandomAccountAddresses({client:w.runtime.clientToUse,accountName:w.runtime.accountToUse,numAddresses:w.runtime.numAddresses},w.returnAddresses)},w.returnAddresses=function(e,t){return e&&t&&t.pickedAddresses?void w.runtime.res.send(JSON.stringify({status:200,type:"SUCCESS",data:{addresses:t.pickedAddresses}})):(T.writeLog("APP_008","failed to pick random addresses",{success:e,data:t}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_008",message:"failed to pick random addresses"})))},P.get("/api/get-nav-balance",function(e,t){w.runtime={},w.runtime.req=e,w.runtime.res=t,w.navClient.getBalance().then(function(e){w.runtime.res.send(JSON.stringify({status:200,type:"SUCCESS",data:{nav_balance:e}}))}).catch(function(e){T.writeLog("APP_009","failed to get the NAV balance",{error:e}),w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_009",message:"failed to get the NAV balance"}))})}),w.checkIpAddress=function(e,t){for(var n=w.runtime.req.connection.remoteAddress||w.runtime.req.socket.remoteAddress,r=!1,i=0;i0){var r=!0,i=!1,s=void 0;try{for(var a,o=t.currentPending[Symbol.iterator]();!(r=(a=o.next()).done);r=!0){var u=a.value;u.confirmations>n&&(n=u.confirmations)}}catch(e){i=!0,s=e}finally{try{!r&&o.return&&o.return()}finally{if(i)throw s}}if(n>A.maxQueue)return T.writeLog("APP_030B","the queue is too long",{highestConf:n},!0),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_030B",message:"the queue is too long"}))}b.getEncryptionKeys({},w.testKeyPair)},w.testKeyPair=function(e,t){return e&&t&&t.privKeyFile&&t.pubKeyFile?void b.testKeyPair({pubKeyFile:t.pubKeyFile,privKeyFile:t.privKeyFile},w.testedKeypair):(T.writeLog("APP_031","failed to get the current keys",{success:e,data:t}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_031",message:"failed to get the current keys"})))},w.testedKeypair=function(e,t){return e&&t&&t.publicKey?(w.runtime.publicKey=t.publicKey,void y.getRandomAccountAddresses({client:w.navClient,accountName:A.account[C.serverType],numAddresses:w.runtime.numAddresses},w.hasRandomAddresses)):(T.writeLog("APP_032","failed to encrypt with selected keypair",{success:e,data:t}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_032",message:"failed to encrypt with selected keypair"})))},w.hasRandomAddresses=function(e,t){return e&&t&&t.pickedAddresses?(w.runtime.navAddresses=t.pickedAddresses,void w.getHash()):(T.writeLog("APP_033","failed to retrieve nav addresses",{success:e,data:t}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_033",message:"failed to retrieve nav addresses"})))},w.hasRandomSubAddresses=function(e,t){return e&&t&&t.pickedAddresses?(w.runtime.subAddresses=t.pickedAddresses,void w.getHash()):(T.writeLog("APP_034","failed to retrieve subchain addresses",{success:e,data:t}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAIL",code:"APP_034",message:"failed to retrieve subchain addresses"})))},w.getHash=function(){g("dist/navtech.js",function(e,t){return e?(T.writeLog("APP_035A","error generating md5 hash",{err:e,hash:t}),void w.runtime.res.send(JSON.stringify({status:200,type:"FAILURE",message:"error generating md5 hash",serverType:C.serverType,error:e}))):void w.returnCheckedNode(t)})},w.returnCheckedNode=function(e){var t=k.local.host?k.local.host:k.local.ipAddress,n={nav_balance:w.runtime.navBalance,sub_balance:w.runtime.subBalance,public_key:w.runtime.publicKey,server_type:C.serverType,min_amount:k.minAmount,max_amount:k.maxAmount,transaction_fee:k.anonFeePercent,server:t,server_port:k.local.port?k.local.port:443,md5:e};n.nav_addresses=w.runtime.navAddresses,w.runtime.res.send(JSON.stringify({status:200,type:"SUCCESS",data:n}))},P.get("/api/status",function(e,t){if(w.runtime={},w.runtime.req=e,w.runtime.res=t,"INCOMING"===C.serverType){var n=new Date,r=n-p.runtime.cycleStart,i=k.scriptInterval-r;w.runtime.res.send(JSON.stringify({status:200,type:"SUCCESS",data:{processing:p.processing,paused:p.paused,nextCycleStart:Math.round(i/1e3)+" seconds"}}))}else if("OUTGOING"===C.serverType){var s=new Date,a=s-m.runtime.cycleStart,o=k.scriptInterval-a;w.runtime.res.send(JSON.stringify({status:200,type:"SUCCESS",data:{processing:m.processing,paused:m.paused,nextCycleStart:Math.round(o/1e3)+" seconds"}}))}})}},function(e,t){e.exports=require("bitcoin-core")},function(e,t){e.exports=require("express")},function(e,t){e.exports=require("https")},function(e,t){e.exports=require("pem")},function(e,t){e.exports=require("body-parser")},function(e,t){e.exports=require("fs")},function(e,t){e.exports=require("config")},function(e,t){e.exports=require("md5-file")},function(e,t,n){"use strict";var r=n(1),i=n(7),s=n(10),a=n(11),o=n(13),u=n(16),c=n(18),d=n(22),l=n(24),g=n(26),p=n(29),m=n(30),v=n(32),f=i.get("INCOMING"),h={processing:!1,paused:!1,runtime:{}};h.init=function(){h.navClient=new r({username:f.navCoin.user,password:f.navCoin.pass,port:f.navCoin.port,host:f.navCoin.host}),h.subClient=new r({username:f.subChain.user,password:f.subChain.pass,port:f.subChain.port,host:f.subChain.host}),o.writeLog("INC_000","server starting"),a.findKeysToRemove({type:"private"},h.startProcessing),h.cron=setInterval(function(){h.paused===!1?a.findKeysToRemove({type:"private"},h.startProcessing):(clearInterval(h.cron),o.writeLog("INC_001","processing paused",{paused:h.paused}))},f.scriptInterval)},h.startProcessing=function(){return h.processing?void o.writeLog("INC_002","server still processing",{processing:h.processing}):(h.processing=!0,h.runtime={},h.runtime.cycleStart=new Date,void u.run({navClient:h.navClient,subClient:h.subClient,settings:f},h.preFlightComplete))},h.preFlightComplete=function(e,t){return e?(h.runtime.navBalance=t.navBalance,h.runtime.subBalance=t.subBalance,void c.run({navClient:h.navClient},h.holdingProcessed)):(o.writeLog("INC_003","preflight checks failed",{success:e,data:t},!0),void(h.processing=!1))},h.holdingProcessed=function(e,t){return e?void d.run({settings:f,navClient:h.navClient},h.outgoingSelected):(o.writeLog("INC_004","failed to process the holding account",{success:e,data:t},!0),h.processing=!1,void(h.paused=!0))},h.outgoingSelected=function(e,t){return e?t.returnAllToSenders?(t.pause&&(h.paused=!0),void l.run({navClient:h.navClient},h.allPendingReturned)):(h.runtime.chosenOutgoing=t.chosenOutgoing,h.runtime.outgoingNavBalance=t.outgoingNavBalance,h.runtime.holdingEncrypted=t.holdingEncrypted,h.runtime.outgoingPubKey=t.outgoingPubKey,void g.run({navClient:h.navClient,outgoingNavBalance:t.outgoingNavBalance,subBalance:h.runtime.subBalance,settings:f},h.currentBatchPrepared)):(o.writeLog("INC_005","failed to find outgoing server",{success:e,data:t},!0),void(h.processing=!1))},h.allPendingReturned=function(e,t){return e?(o.writeLog("INC_007","returned all pending to sender",{success:e,data:t},!0),void(h.processing=!1)):(o.writeLog("INC_006","failed to return all pending to sender",{success:e,data:t},!0),h.processing=!1,void(h.paused=!0))},h.currentBatchPrepared=function(e,t){return e&&t&&(t.currentBatch&&t.currentFlattened&&t.numFlattened||t.pendingToReturn)?(h.runtime.currentBatch=t.currentBatch,h.runtime.currentFlattened=t.currentFlattened,h.runtime.numFlattened=t.numFlattened,h.runtime.pendingToReturn=t.pendingToReturn,h.runtime.pendingToReturn&&h.runtime.pendingToReturn.length>0?(o.writeLog("INC_011","failed to process some transactions",{success:e,data:t},!0),void l.fromList({navClient:h.navClient,transactionsToReturn:h.runtime.pendingToReturn},h.pendingFailedReturned)):h.runtime.currentBatch&&0!==s.size(h.runtime.currentBatch)?void p.run({subClient:h.subClient,chosenOutgoing:h.runtime.chosenOutgoing,numAddresses:h.runtime.numFlattened},h.retrievedSubchainAddresses):(o.writeLog("INC_011B","no currentBatch to process",{currentBatch:h.runtime.currentBatch}),void(h.processing=!1))):(o.writeLog("INC_011D","prepareIncoming returned bad data",{success:e,data:t}),void(h.processing=!1))},h.pendingFailedReturned=function(e,t){return e||(o.writeLog("INC_011A","failed to return failed pending to sender",{success:e,data:t},!0),h.paused=!0,l.run({navClient:h.navClient},h.allPendingReturned)),h.runtime.currentBatch&&0!==s.size(h.runtime.currentBatch)?void p.run({subClient:h.subClient,chosenOutgoing:h.runtime.chosenOutgoing,numAddresses:h.runtime.numFlattened},h.retrievedSubchainAddresses):(o.writeLog("INC_011C","no currentBatch to process",{currentBatch:h.runtime.currentBatch}),void(h.processing=!1))},h.retrievedSubchainAddresses=function(e,t){return e&&t&&t.subAddresses?void m.run({currentBatch:h.runtime.currentBatch,currentFlattened:h.runtime.currentFlattened,outgoingPubKey:h.runtime.outgoingPubKey,subClient:h.subClient,navClient:h.navClient,subAddresses:t.subAddresses,settings:f},h.transactionsProcessed):(o.writeLog("INC_009","failed to retrieve subchain addresses",{success:e,data:t},!0),void l.run({navClient:h.navClient},h.allPendingReturned))},h.transactionsProcessed=function(e,t){if(!e||!t)return t&&t.partialFailure?(o.writeLog("INC_010A","failed part way through processing subchain transactions",{success:e,data:t},!0),h.paused=!0,void(h.processing=!1)):(o.writeLog("INC_010","failed to process transactions",{success:e,data:t},!0),h.paused=!0,void l.run({navClient:h.navClient},h.allPendingReturned));if(h.runtime.successfulTxGroups=t.successfulTxGroups,h.runtime.txGroupsToReturn=t.txGroupsToReturn,h.runtime.transactionsToReturn=[],h.runtime.txGroupsToReturn&&h.runtime.txGroupsToReturn.length>0){o.writeLog("INC_011","failed to process some transactions",{success:e,data:t},!0);for(var n=0;nu.keyPeriod&&a.push(r[c])}a.length>0?d.removeKeys({forRemoval:a,type:e.type},t):"private"===e.type?d.findKeysToRemove({type:"public"},t):t(!0,{message:"nothing to remove"})})},d.removeKeys=function(e,t){0===e.forRemoval.length?"private"===e.type?d.findKeysToRemove({type:"public"},t):t(!0,{message:"all keys removed"}):r.exists(u.keyFolders[e.type].path+e.forRemoval[0],function(n){n?r.unlink(u.keyFolders[e.type].path+e.forRemoval[0],function(n){n&&o.writeLog("ENC_010","failed to remove the key",{error:n,key:e.forRemoval}),d.removeKeys({forRemoval:e.forRemoval.slice(1),type:e.type},t)}):d.removeKeys({forRemoval:e.forRemoval.slice(1),type:e.type},t)})},d.getMidnight=function(e){return parseInt(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate()),10)},e.exports=d},function(e,t){e.exports=require("ursa")},function(e,t,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i=n(14),s=n(7),a=s.get("GLOBAL"),o=!1;"INCOMING"===a.serverType&&(o=s.get("INCOMING")),"OUTGOING"===a.serverType&&(o=s.get("OUTGOING"));var u=n(15),c=u.version.major+"."+u.version.minor+"."+u.version.patch,d=encodeURIComponent(o.smtp.user)+":"+encodeURIComponent(o.smtp.pass),l={};l.transporter=i.createTransport("smtps://"+d+"@"+o.smtp.server);var g=new Date,p="\r\n";p+="Version: "+c+"\r\n",p+="Date: "+g+"\r\n",p+="\r\n-----------------------------------------------------------\r\n",console.log(p),l.writeLog=function(e,t,n,i){i&&l.sendMail(e,t,n);var s=new Date,a="\r\n";a+="Date: "+s+"\r\n",a+="Error Code: "+e+"\r\n",a+="Error Message: "+t+"\r\n";for(var o in n)if(n.hasOwnProperty(o)){var u=n[o];"object"===r(n[o])&&(u=JSON.stringify(n[o])),a+=o+": "+u+"\r\n"}a+="\r\n-----------------------------------------------------------\r\n",console.log(a)},l.sendMail=function(e,t,n){var r={from:'"Navtech System" <'+o.smtp.user+">",to:o.notificationEmail,subject:"Navtech System Message - "+o.local.ipAddress+" ("+a.serverType+") "+e,text:e+" - "+t,attachments:[{filename:"data.json",content:JSON.stringify(n)}]};l.transporter.sendMail(r,function(e,t){return e?console.log("nodemail error",e):console.log("nodemail success: "+t.response)})},e.exports=l},function(e,t){e.exports=require("nodemailer")},function(e,t){e.exports={version:{major:1,minor:2,patch:0},keyFolders:{private:{path:"./keys/private/",suffix:"_private.pem"},public:{path:"./keys/public/",suffix:"_public.pub"}},keyPeriod:6048e5,minKeyStamp:14647392e5,account:{INCOMING:"incomingAccount",OUTGOING:"outgoingAccount",HOLDING:"holdingAccount"},maxAddresses:1e3,maxHolding:200,minNavTransactions:3,maxNavTransactions:8,txFee:1e-4,maxEncryptionAttempts:10,encryptionStrength:{INCOMING:2048,OUTGOING:1024},encryptionOutput:{INCOMING:344,OUTGOING:172},subCoinsPerTx:1,subChainTxFee:1e-4,minConfs:1,maxConfs:60,maxQueue:120,blockThreshold:{checking:5,processing:3}}},function(e,t,n){"use strict";var r=n(10),i=n(15),s=n(13),a=n(17),o={};o.run=function(e,t){var n=["navClient","subClient","settings"];return r.intersection(Object.keys(e),n).length!==n.length?(s.writeLog("PRE_001","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to Preflight.checkNavBlocks"})):(o.runtime={callback:t,navClient:e.navClient,subClient:e.subClient,settings:e.settings},void a.checkBlockHeight({client:o.runtime.navClient,blockThreshold:i.blockThreshold.processing},o.navBlocksChecked))},o.navBlocksChecked=function(e,t){return e&&t?(o.runtime.navBalance=t.balance,void o.runtime.navClient.setTxFee(parseFloat(i.txFee)).then(function(){a.checkBlockHeight({client:o.runtime.subClient,blockThreshold:i.blockThreshold.processing},o.subBlocksChecked)}).catch(function(e){s.writeLog("PRE_003","failed to set NAV tx fee",{err:e}),o.runtime.callback(!1,{message:"failed to set NAV tx fee"})})):(s.writeLog("PRE_002","navClient block check failed",{status:e,data:t}),void o.runtime.callback(!1,{message:"navClient block check failed"}))},o.subBlocksChecked=function(e,t){return e&&t?(o.runtime.subBalance=t.balance,void a.unlockWallet({settings:o.runtime.settings,client:o.runtime.navClient,type:"navCoin"},o.navClientUnlocked)):(s.writeLog("PRE_004","subClient block check failed",{status:e,data:t}),void o.runtime.callback(!1,{message:"subClient block check failed"}))},o.navClientUnlocked=function(e,t){return e?void a.unlockWallet({settings:o.runtime.settings,client:o.runtime.subClient,type:"subChain"},o.subClientUnlocked):(s.writeLog("PRE_005","navClient failed to unlock",{status:e,data:t}),void o.runtime.callback(!1,{message:"navClient failed to unlock"}))},o.subClientUnlocked=function(e,t){return e?void o.runtime.subClient.setTxFee(parseFloat(i.subChainTxFee)).then(function(){o.runtime.callback(!0,{navBalance:o.runtime.navBalance,subBalance:o.runtime.subBalance})}).catch(function(e){s.writeLog("PRE_003","failed to set SUB tx fee",{err:e}),o.runtime.callback(!1,{message:"failed to set SUB tx fee"})}):(s.writeLog("PRE_006","subClient failed to unlock",{status:e,data:t}),void o.runtime.callback(!1,{message:"subClient failed to unlock"}))},e.exports=o},function(e,t,n){"use strict";var r=n(7),i=n(10),s=n(13),a=r.get("GLOBAL"),o={};o.unlockWallet=function(e,t){var n=["settings","client","type"];if(i.intersection(Object.keys(e),n).length!==n.length)return s.writeLog("NAV_001","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to NavCoin.unlockWallet"});if(a.encryptedWallet===!1)return void t(!0);var r=e.settings.scriptInterval/1e3;e.client.walletPassphrase(e.settings[e.type].walletPassphrase,r).then(function(){t(!0)}).catch(function(n){switch(n.code){case-17:t(!0);break;default:return t(!1,{message:"failed to unlock"}),void s.writeLog("NAV_002","failed to unlock "+e.type+" wallet",{error:n})}})},o.lockWallet=function(e,t){var n=["type","client"];return i.intersection(Object.keys(e),n).length!==n.length?(s.writeLog("NAV_003","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to NavCoin.lockWallet"})):void e.client.walletLock().then(function(){s.writeLog("NAV_004","locked the "+e.type+" wallet",e),o.unlockWallet(e,t)}).catch(function(n){t(!1,{message:"failed to lock"}),s.writeLog("NAV_005","failed to lock "+e.type+" wallet",{error:n})})},o.filterUnspent=function(e,t){var n=["unspent","client","accountName"];if(i.intersection(Object.keys(e),n).length!==n.length)return s.writeLog("NAV_006","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to NavCoin.filterUnspent"});try{e.client.getAddressesByAccount(e.accountName).then(function(n){var r=!1,i=[],s=!0,a=!1,o=void 0;try{for(var u,c=e.unspent[Symbol.iterator]();!(s=(u=c.next()).done);s=!0){var d=u.value;n.indexOf(d.address)!==-1&&(r=!0,i.push(d))}}catch(e){a=!0,o=e}finally{try{!s&&c.return&&c.return()}finally{if(a)throw o}}return r?void t(!0,{currentPending:i}):void t(!0)}).catch(function(n){s.writeLog("NAV_007","failed to get address by account",{error:n,options:e}),t(!1)})}catch(t){s.writeLog("NAV_008","failed to filter",{error:t,options:e})}},o.checkBlockHeight=function(e,t){var n=["client","blockThreshold"];return i.intersection(Object.keys(e),n).length!==n.length?(s.writeLog("NAV_009","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to NavCoin.checkBlockHeight"})):void e.client.getInfo().then(function(n){e.client.getBlockCount().then(function(r){return parseInt(r,10)-e.blockThreshold>parseInt(n.blocks,10)?(s.writeLog("NAV_010","client is not synced with the latest blocks",{walletInfo:n,blockCount:r}),void t(!1,{message:"client is not synced with the latest blocks"})):void t(!0,{balance:n.balance})}).catch(function(n){s.writeLog("NAV_011","failed to get block count",{error:n,options:e}),t(!1,{message:"failed to get block count"})})}).catch(function(n){s.writeLog("NAV_012","failed to get info",{error:n,options:e}),t(!1,{message:"failed to get info"})})},o.validateAddresses=function(e,t){var n=["client","addresses"];return i.intersection(Object.keys(e),n).length!==n.length?(s.writeLog("NAV_013","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to NavCoin.validateAddresses"})):0===e.addresses.length?void t(!0):void e.client.validateAddress(e.addresses[0]).then(function(n){return n.isvalid!==!0?(s.writeLog("NAV_014","provided address is invalid",{address:e.addresses[0]}),void t(!1,{message:"provided address is invalid"})):void o.validateAddresses({addresses:e.addresses.slice(1),client:e.client},t)}).catch(function(n){s.writeLog("NAV_015","failed to validate address",{error:n,options:e}),t(!1,{message:"failed to validate address"})})},e.exports=o},function(e,t,n){"use strict";var r=n(10),i=n(15),s=n(13),a=n(17),o=n(19),u=n(20),c=n(21),d={};d.run=function(e,t){var n=["navClient"];return r.intersection(Object.keys(e),n).length!==n.length?(s.writeLog("RFL_001","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to RefillOutgoing.checkHoldingAccount"})):(d.runtime={callback:t,navClient:e.navClient},void d.getUnspent())},d.getUnspent=function(){d.runtime.navClient.listUnspent().then(function(e){return e.length<1?(s.writeLog("RFL_002","no unspent in holding account",{unspent:e}),void d.runtime.callback(!0,{message:"no unspent in holding account"})):void a.filterUnspent({unspent:e,client:d.runtime.navClient,accountName:i.account.HOLDING},d.holdingFiltered)}).catch(function(e){s.writeLog("RFL_003","failed to list unspent",{error:e}),d.runtime.callback(!1,{message:"failed to list unspent"})})},d.holdingFiltered=function(e,t){return!e||!t||!t.currentPending||t.currentPending.length<1?void d.runtime.callback(!0,{message:"no pending to clear from account"}):(d.runtime.currentHolding=t.currentPending,void d.processHolding())},d.processHolding=function(){return d.runtime.currentHolding.length<1?(s.writeLog("RFL_005","all holding processed",{currentHolding:d.runtime.currentHolding}),void d.runtime.callback(!0,{message:"all holding processed"})):void d.checkIfHoldingIsSpendable()},d.checkIfHoldingIsSpendable=function(){return d.runtime.currentHolding[0].confirmations>i.minConfs?void o.getEncrypted({transaction:d.runtime.currentHolding[0],client:d.runtime.navClient},d.holdingDecrypted):(s.writeLog("RFL_006","holding account transaction not spendable",{currentHolding:d.runtime.currentHolding}),d.runtime.currentHolding.splice(0,1),void d.processHolding())},d.holdingDecrypted=function(e,t){if(!(e&&t&&t.decrypted&&t.transaction))return s.writeLog("RFL_007","failed to decrypt holding transaction data",{success:e,data:t}),d.runtime.currentHolding.splice(0,1),void d.processHolding();if(d.runtime.holdingTransaction=t.transaction,t.decrypted.constructor!==Array)return s.writeLog("RFL_007A","decrypted data not an array",{currentHolding:d.runtime.currentHolding}),d.runtime.currentHolding.splice(0,1),void d.processHolding();var n=t.decrypted.slice(0);if(n.constructor!==Array)return s.writeLog("RFL_007A","decrypted data not an array of addresses",{currentHolding:d.runtime.currentHolding}),d.runtime.currentHolding.splice(0,1),void d.processHolding();for(var r=Math.ceil(Math.random()*(n.length-(i.minNavTransactions-1)))+(i.minNavTransactions-1),a=[];a.length=10?void c.runtime.callback(!1,{error:e}):(s.writeLog("RAW_007","retrying",{error:e,counter:c.runtime.counter}),void setTimeout(function(){c.runtime.counter+=1,c.create()},c.runtime.retryDelay))},e.exports=c},function(e,t,n){"use strict";var r=n(10),i=n(13),s=n(15),a={};a.incoming=function(e,t){var n=["totalToSend","addresses"];return r.intersection(Object.keys(e),n).length!==n.length?(i.writeLog("RND_001","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to RandomizeTransactions.incoming"})):(a.runtime={callback:t,totalToSend:e.totalToSend,addresses:e.addresses,transactions:{}},void a.randomizeIncoming())},a.randomizeIncoming=function(){for(var e=1e8,t=a.runtime.totalToSend*e,n=s.txFee*e,r=n*a.runtime.addresses.length,i=t-r,o=0,u=i/a.runtime.addresses.length,c=Math.floor(1.5*u*e),d=Math.floor(.5*u*e),l=0;li-o||l===a.runtime.addresses.length-1){var m=Math.round(i-o);a.runtime.transactions[a.runtime.addresses[l]]=m/e,o+=m}else a.runtime.transactions[a.runtime.addresses[l]]=p/e,o+=p}a.runtime.callback(!0,{transactions:a.runtime.transactions})},a.outgoing=function(e,t){var n=["transaction","address","amount"];return r.intersection(Object.keys(e),n).length!==n.length?(i.writeLog("RND_002","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to RandomizeTransactions.outgoing"})):(a.runtime={callback:t,transaction:e.transaction,address:e.address,amount:e.amount,transactions:[]},void a.randomizeOutgoing())},a.randomizeOutgoing=function(){for(var e=Math.ceil(Math.random()*(s.maxNavTransactions-s.minNavTransactions))+s.minNavTransactions,t=a.runtime.amount,n=1e8,r=t*n,i=0,o=r/e,u=Math.floor(1.5*o*n),c=Math.floor(.5*o*n),d=0;dr-i||d===e-1){var p=Math.round(r-i);a.runtime.transactions.push(p/n),i+=p}else a.runtime.transactions.push(g/n),i+=g}a.runtime.callback(!0,{partialTransactions:a.runtime.transactions})},a.getRandomAccountAddresses=function(e,t){var n=["client","accountName","numAddresses"];return r.intersection(Object.keys(e),n).length!==n.length?(i.writeLog("RND_003","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to RandomizeTransactions.getRandomAccountAddresses"})):void e.client.getAddressesByAccount(e.accountName).then(function(n){a.chooseRandomAddresses({accountName:e.accountName,numAddresses:e.numAddresses,addresses:n},t)}).catch(function(n){i.writeLog("RND_004","get account address failed",{options:e,error:n}),t(!1,{message:"get account address failed"})})},a.chooseRandomAddresses=function(e,t){var n=["addresses","accountName","numAddresses"];if(r.intersection(Object.keys(e),n).length!==n.length)return i.writeLog("RND_005","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to RandomizeTransactions.chooseRandomAddresses"});for(var s=[],a=0;s.length=a.maxAddresses?void l.runtime.callback(!0,{currentBatch:l.runtime.currentBatch,currentFlattened:l.runtime.currentFlattened,numFlattened:l.runtime.numFlattened,pendingToReturn:l.runtime.transactionsToReturn}):(l.runtime.numFlattened+=t.flattened.length,l.runtime.currentFlattened[l.runtime.remainingToFlatten[0].unique]=t.flattened,l.runtime.remainingToFlatten.splice(0,1),0===l.runtime.remainingToFlatten.length?void l.runtime.callback(!0,{currentBatch:l.runtime.currentBatch,currentFlattened:l.runtime.currentFlattened,numFlattened:l.runtime.numFlattened,pendingToReturn:l.runtime.transactionsToReturn}):void c.incoming({amountToFlatten:l.runtime.remainingToFlatten[0].amount,anonFeePercent:l.runtime.settings.anonFeePercent},l.flattened)):(o.writeLog("PREPI_004","failed to flatten transactions",{success:e,data:t,runtime:l.runtime}),l.runtime.remainingToFlatten.splice(0,1),0===l.runtime.remainingToFlatten.length?void l.runtime.callback(!0,{currentBatch:l.runtime.currentBatch,currentFlattened:l.runtime.currentFlattened,numFlattened:l.runtime.numFlattened,pendingToReturn:l.runtime.transactionsToReturn}):void c.incoming({amountToFlatten:l.runtime.remainingToFlatten[0].amount,anonFeePercent:l.runtime.settings.anonFeePercent},l.flattened))},e.exports=l},function(e,t,n){"use strict";var r=n(10),i=n(13),s={};s.incoming=function(e,t){var n=["amountToFlatten","anonFeePercent"];if(r.intersection(Object.keys(e),n).length!==n.length)return i.writeLog("FLT_001","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to FlattenTransactions.incoming"});var a=e.amountToFlatten/(1+e.anonFeePercent/100);s.runtime={callback:t,amountToFlatten:s.satoshiParser(a)},s.flattenIncoming()},s.flattenIncoming=function(){for(var e=Math.floor(s.runtime.amountToFlatten),t=e.toString(),n=s.runtime.amountToFlatten-e,a=s.satoshiParser(n),o=[],u=0;u0&&r.sum(o)===e-c?o.push(s.satoshiParser(parseInt(c,10)+a)):o.push(parseInt(c,10));if(1===o.length){o=[];for(var g=0;g<10;g++)a>0&&9===g?o.push(s.satoshiParser(e/10+a)):o.push(e/10)}var p=o.reduce(function(e,t){return t+e},0),m=s.satoshiParser(p);return m!==s.runtime.amountToFlatten?(i.writeLog("FLT_002","unable to correctly flatten amount",{runtime:s.runtime,flattened:o}),void s.runtime.callback(!1,{flattened:o})):void s.runtime.callback(!0,{flattened:o})},s.satoshiParser=function(e){var t=1e8;return Math.round(e*t)/t},e.exports=s},function(e,t,n){"use strict";var r=n(10),i=n(13),s=n(19),a=n(15),o={};o.run=function(e,t){var n=["currentPending","client"];return r.intersection(Object.keys(e),n).length!==n.length?(i.writeLog("GRP_001","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to GroupPartials.run"})):(o.runtime={client:e.client,currentPending:e.currentPending,remainingToDecrypt:e.currentPending,transactionsToReturn:[],readyToProcess:{},partials:{},callback:t},void o.getDecryptedData())},o.getDecryptedData=function(){return o.runtime.remainingToDecrypt.length<1?void o.checkPartials():void s.getEncrypted({transaction:o.runtime.remainingToDecrypt[0],client:o.runtime.client},o.checkDecrypted)},o.partialFailed=function(e){var t=r.findIndex(o.runtime.transactionsToReturn,function(t){return t.txid===e.txid});t===-1&&o.runtime.transactionsToReturn.push(e),o.runtime.remainingToDecrypt.splice(0,1),o.getDecryptedData()},o.checkDecrypted=function(e,t){return e&&t&&t.decrypted&&t.transaction?t.decrypted.n&&t.decrypted.t&&t.decrypted.p&&t.decrypted.o&&t.decrypted.u?void o.runtime.client.validateAddress(t.decrypted.n).then(function(n){return n.isvalid!==!0?(i.writeLog("GRP_003A","encrypted address invalid",{success:e,data:t}),void o.partialFailed(t.transaction)):void o.groupPartials(t.decrypted,t.transaction)}).catch(function(n){i.writeLog("GRP_003B","failed to decrypt transaction data",{success:e,error:n}),o.partialFailed(t.transaction)}):(i.writeLog("GRP_003","failed to receive correct encrypted params",{n:!!t.decrypted.n,t:t.decrypted.t,p:t.decrypted.p,o:t.decrypted.o,u:t.decrypted.u,data:t}),void o.partialFailed(t.transaction)):(i.writeLog("GRP_002","failed to decrypt transaction data",{success:e}),void o.partialFailed(o.runtime.remainingToDecrypt[0]))},o.groupPartials=function(e,t){if(o.runtime.partials[e.u]||(o.runtime.partials[e.u]={destination:e.n,unique:e.u,timeDelay:parseInt(e.t,10),parts:parseInt(e.o,10),partsSum:0,amount:0,transactions:{},readyToProcess:!1}),o.runtime.partials[e.u].readyToProcess===!0)return i.writeLog("GRP_006","this partial group is already flagged as completed",{partials:o.runtime.partials[e.u],transaction:t,n:!!e.n,t:e.t,p:e.p,o:e.o,u:e.u}),void o.partialFailed(t);if(o.runtime.partials[e.u].destination!==e.n)return i.writeLog("GRP_004","decrypted address different from other partials",{partials:o.runtime.partials[e.u],transaction:t}),void o.partialFailed(t);if(o.runtime.partials[e.u].transactions[t.txid])return i.writeLog("GRP_005","txid already processed",{partials:o.runtime.partials[e.u],transaction:t}),void o.partialFailed(t);var n=Math.round(1e8*o.runtime.partials[e.u].amount),s=Math.round(1e8*t.amount),a=(n+s)/1e8;o.runtime.partials[e.u].amount=a,o.runtime.partials[e.u].partsSum+=parseInt(e.p,10),o.runtime.partials[e.u].transactions[t.txid]={txid:t.txid,amount:t.amount,confirmations:t.confirmations,vout:t.vout,vin:t.vin,part:e.p};var u=o.runtime.partials[e.u].parts,c=r.size(o.runtime.partials[e.u].transactions);c===u&&u*(u+1)/2===o.runtime.partials[e.u].partsSum&&(o.runtime.partials[e.u].readyToProcess=!0,o.runtime.readyToProcess[e.u]=o.runtime.partials[e.u]),o.runtime.remainingToDecrypt.splice(0,1),o.getDecryptedData()},o.checkPartials=function(){r.forEach(o.runtime.partials,function(e){e.readyToProcess||r.forEach(e.transactions,function(e){var t=r.findIndex(o.runtime.transactionsToReturn,function(t){return t.txid===e.txid});t===-1&&e.confirmations>a.maxConfs&&o.runtime.transactionsToReturn.push(e)})}),o.runtime.callback(!0,{readyToProcess:o.runtime.readyToProcess,transactionsToReturn:o.runtime.transactionsToReturn})},e.exports=o},function(e,t,n){"use strict";var r=n(10),i=n(23),s=n(13),a=n(17),o={};o.run=function(e,t){var n=["subClient","chosenOutgoing","numAddresses"];return r.intersection(Object.keys(e),n).length!==n.length?(s.writeLog("RSC_001","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to RetrieveSubchainAddresses.run"})):(o.runtime={callback:t,subClient:e.subClient,chosenOutgoing:e.chosenOutgoing,numAddresses:e.numAddresses},void o.getSubAddresses())},o.getSubAddresses=function(){var e=o.runtime.chosenOutgoing,t=e.port?e.ipAddress+":"+e.port:e.ipAddress;o.runtime.outgoingAddress=t;var n={uri:"https://"+t+"/api/get-addresses",method:"POST",timeout:6e4,rejectUnauthorized:!1,requestCert:!0,agent:!1,form:{type:"SUBCHAIN",account:"OUTGOING",num_addresses:o.runtime.numAddresses}};i(n,o.requestResponse)},o.requestResponse=function(e,t,n){return e?(s.writeLog("RSC_004","failed to query outgoing server",{error:e,outgoingAddress:o.runtime.outgoingAddress}),void o.runtime.callback(!1,{message:"failed to query outgoing server"})):void o.checkOutgoingCanTransact(n,o.runtime.outgoingAddress)},o.checkOutgoingCanTransact=function(e,t){try{var n=JSON.parse(e);if("SUCCESS"!==n.type)return s.writeLog("RSC_005","outgoing server returned failure",{body:n,outgoingAddress:t}),void o.runtime.callback(!1,{message:"outgoing server returned failure"});if(!n.data||!n.data.addresses||n.data.addresses.constructor!==Array)return s.writeLog("RSC_006","outgoing server returned incorrect params",{body:n,outgoingAddress:t}),void o.runtime.callback(!1,{message:"outgoing server returned failure"});o.checkSubAddresses(n.data.addresses)}catch(n){return s.writeLog("RSC_005A","outgoing server returned non json response",{body:e,error:n,outgoingAddress:t}),void o.runtime.callback(!1,{message:"outgoing server returned non json response"})}},o.checkSubAddresses=function(e){return e.length<1?(s.writeLog("RSC_007","outgoing server must provide at least one sub address",{subAddresses:e}),void o.runtime.callback(!1,{message:"outgoing server must provide at least one sub address"})):e.lengthu.runtime.remainingFlattened.length?(s.writeLog("PROI_009","partial subchain transaction failure",{ +txGroup:e,runtime:u.runtime}),void u.runtime.callback(!1,{partialFailure:!0})):(s.writeLog("PROI_009A","complete group failure",{txGroup:e,runtime:u.runtime}),u.runtime.txGroupsToReturn.push(t),u.runtime.remainingTxGroups.splice(0,1),void u.processPending())},u.reEncryptAddress=function(e,t,n,r,o){try{var c={n:e,v:r,s:u.runtime.settings.secret,t:u.runtime.currentBlockHeight+Math.round(Math.random()*t)},d=u.runtime.outgoingPubKey.encrypt(JSON.stringify(c),"utf8","base64",i.RSA_PKCS1_PADDING);if(d.length!==a.encryptionOutput.OUTGOING&&o=a.maxEncryptionAttempts)return s.writeLog("PROI_006","max public key encryption failures",{txGroup:n,counter:o,encrypted:d}),void u.partialFailed(n);u.makeSubchainTx(d,n)}catch(e){return s.writeLog("PROI_007","encrypted address invalid",{txGroup:n,error:e}),void u.partialFailed(n)}},u.makeSubchainTx=function(e,t){o.send({client:u.runtime.subClient,address:u.runtime.subAddresses[0],amount:a.subCoinsPerTx,transaction:t,encrypted:e},u.sentSubToOutgoing)},u.sentSubToOutgoing=function(e,t){return e&&t&&t.sendOutcome?(u.runtime.subAddresses.splice(0,1),u.runtime.remainingFlattened.splice(0,1),void u.processPartial()):(s.writeLog("PROI_008","failed subClient send to address",{transaction:t.transaction,error:t.error}),void u.partialFailed())},e.exports=u},function(e,t,n){"use strict";var r=n(7),i=n(10),s=n(13),a=n(17),o=r.get("GLOBAL"),u=!1;"INCOMING"===o.serverType&&(u=r.get("INCOMING")),"OUTGOING"===o.serverType&&(u=r.get("OUTGOING"));var c={};c.send=function(e,t){var n=["client","address","amount","transaction"];if(i.intersection(Object.keys(e),n).length!==n.length)return s.writeLog("STA_001","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to SelectOutgoing.run"});if(o.preventSend)return s.writeLog("STA_TEST_001","preventSend triggered",{options:e}),void t(!0,{sendOutcome:"dummy-tx-id",transaction:e.transaction});if(c.runtime={},e.counter&&e.counter>7)return s.writeLog("STA_002","max send attempts reached",{transaction:e.transaction,counter:e.counter}),void t(!1,{transaction:e.transaction,error:"max send attempts reached"});var r=1e8,d=Math.round(e.amount*r),l=d/r;e.client.sendToAddress(e.address,l,null,null,e.encrypted).then(function(n){if(n)return void t(!0,{sendOutcome:n,transaction:e.transaction})}).catch(function(n){if(n.code===-13&&!e.triedToUnlock){c.runtime.options=e,c.runtime.callback=t;var r=e.client.port===u.navCoin.port?"navCoin":"subChain";return void a.unlockWallet({settings:u,client:e.client,type:r},c.walletUnlocked)}s.writeLog("STA_003","failed send to address",{transaction:e.transaction,error:n}),setTimeout(function(){var n=e.counter?e.counter+1:1,r={client:e.client,address:e.address,amount:e.amount,transaction:e.transaction,encrypted:e.encrypted,counter:n,triedToUnlock:!1};c.send(r,t)},3e4)})},c.walletUnlocked=function(e,t){if(!e)return s.writeLog("STA_004","unable to unlock wallet",{success:e,data:t}),void c.runtime.callback(!1,{transaction:c.runtime.options.transaction,error:t});var n={client:c.runtime.options.client,address:c.runtime.options.address,amount:c.runtime.options.amount,transaction:c.runtime.options.transaction,encrypted:c.runtime.options.encrypted,counter:c.runtime.options.counter,triedToUnlock:!0};c.send(n,c.runtime.callback)},e.exports=c},function(e,t,n){"use strict";var r=n(10),i=n(13),s=n(21),a=n(15),o=n(20),u={};u.run=function(e,t){var n=["successfulSubTransactions","holdingEncrypted","navClient"];return r.intersection(Object.keys(e),n).length!==n.length?(i.writeLog("STH_001","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to SpendToHolding.run"})):(u.runtime={callback:t,successfulSubTransactions:e.successfulSubTransactions,holdingEncrypted:e.holdingEncrypted,navClient:e.navClient},void s.getRandomAccountAddresses({client:u.runtime.navClient,accountName:a.account.HOLDING,numAddresses:1},u.createHoldingTransactions))},u.createHoldingTransactions=function(e,t){if(!e)return i.writeLog("STH_002","could not retrieve holding addresses",{success:e,data:t}),void u.runtime.callback(!1,{message:"could not retrieve holding addresses"});var n=1e8,r=0,s=!0,c=!1,d=void 0;try{for(var l,g=u.runtime.successfulSubTransactions[Symbol.iterator]();!(s=(l=g.next()).done);s=!0){var p=l.value;r+=Math.round(p.amount*n)-Math.round(a.txFee*n)}}catch(e){c=!0,d=e}finally{try{!s&&g.return&&g.return()}finally{if(c)throw d}}for(var m=r/n,v=[],f=0;f0&&i.writeLog("OUT_003A","failed to prepare some subtransactions",{success:e,data:t},!0),void(p.processing=!1))},p.transactionsProcessed=function(e,t){return e&&t?!t.successfulTransactions||t.successfulTransactions.length<1?(i.writeLog("OUT_005","all transactions failed",t,!0),p.processing=!1,void(p.paused=!0)):(t.failedTransactions&&t.failedTransactions.length>0&&i.writeLog("OUT_005A","failed to send send some transactions",{success:e,data:t},!0),p.runtime.successfulTransactions=t.successfulTransactions,void d.run({transactions:p.runtime.successfulTransactions,subClient:p.subClient,settings:g},p.subnavReturned)):(i.writeLog("OUT_004","failed to process transactions",{success:e,data:t},!0),p.processing=!1,void(p.paused=!0))},p.subnavReturned=function(e,t){return e?void(p.processing=!1):(i.writeLog("OUT_007","unable to return subnav to incoming server",{transactions:p.runtime.successfulTransactions,data:t},!0),p.paused=!0,void(p.processing=!1))},e.exports=p},function(e,t,n){"use strict";var r=n(10),i=n(7),s=i.get("GLOBAL"),a=n(13),o=n(17),u=n(19),c=n(15),d={};d.run=function(e,t){var n=["navClient","subClient","navBalance","settings"];return r.intersection(Object.keys(e),n).length!==n.length?(a.writeLog("PREPO_001","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to ReturnAllToSenders.run"})):(d.runtime={callback:t,navClient:e.navClient,subClient:e.subClient,navBalance:e.navBalance,settings:e.settings,failedSubTransactions:[],currentBatch:[],sumPending:0},void d.runtime.navClient.getBlockCount().then(function(e){d.runtime.currentBlockHeight=e,d.getUnspent()}).catch(function(e){a.writeLog("PREPO_001A","failed to get the current blockheight",{error:e}),t(!1,{message:"failed to get the current blockheight"})}))},d.getUnspent=function(){d.runtime.subClient.listUnspent().then(function(e){return e.length<1?void d.runtime.callback(!1,{message:"no unspent transactions found"}):void o.filterUnspent({unspent:e,client:d.runtime.subClient,accountName:c.account[s.serverType]},d.unspentFiltered)}).catch(function(e){a.writeLog("PREPO_002","failed to list unspent",e),d.runtime.callback(!1,{message:"failed to list unspent"})})},d.unspentFiltered=function(e,t){return!e||!t||!t.currentPending||t.currentPending.length<1?(a.writeLog("PREPO_003","no current pending to return",t),void d.runtime.callback(!1,{message:"no current pending to return"})):(d.runtime.currentPending=t.currentPending,void d.processTransaction())},d.processTransaction=function(){return d.runtime.currentPending.length<1?void d.runtime.callback(!0,{failedSubTransactions:d.runtime.failedSubTransactions,currentBatch:d.runtime.currentBatch}):void u.getEncrypted({transaction:d.runtime.currentPending[0],client:d.runtime.subClient},d.checkDecrypted)},d.failedTransaction=function(){d.runtime.failedSubTransactions.push(d.runtime.currentPending[0]),d.runtime.currentPending.splice(0,1),d.processTransaction()},d.checkDecrypted=function(e,t){if(!(e&&t&&t.decrypted&&t.transaction))return a.writeLog("PREPO_004","failed to decrypt transaction",{success:e}),void d.failedTransaction();if(!t.decrypted.n||!t.decrypted.v||!t.decrypted.s)return a.writeLog("PREPO_005","transaction has invalid params",{success:e}),void d.failedTransaction();if(parseFloat(t.decrypted.v)>d.runtime.settings.maxAmount)return a.writeLog("PREPO_006","decrypted amount is larger than maxAmount",{success:e}),void d.failedTransaction();t.decrypted.s!==d.runtime.settings.secret&&(a.writeLog("PREPO_007","secret mismatch",{success:e}),d.failedTransaction());var n=t.decrypted;n.t||(n.t=0),d.testDecrypted(n,t.transaction)},d.testDecrypted=function(e,t){d.runtime.navClient.validateAddress(e.n).then(function(n){return n.isvalid!==!0?(a.writeLog("PREPO_008","recipient address is invalid",{transaction:t}),void d.failedTransaction()):e.t>d.runtime.currentBlockHeight?(d.runtime.currentPending.splice(0,1),void d.processTransaction()):d.runtime.navBalance>d.runtime.sumPending+parseFloat(e.v)?(d.runtime.sumPending=d.runtime.sumPending+parseFloat(e.v),d.runtime.currentBatch.push({decrypted:e,transaction:t}),d.runtime.currentPending.splice(0,1),void d.processTransaction()):void d.runtime.callback(!0,{failedSubTransactions:d.runtime.failedSubTransactions,currentBatch:d.runtime.currentBatch})}).catch(function(n){a.writeLog("PREPO_009","navClient failed validate address",{decrypted:e,transaction:t,error:n}),d.failedTransaction()})},e.exports=d},function(e,t,n){"use strict";var r=n(10),i=n(13),s=(n(21),n(31)),a={};a.run=function(e,t){var n=["currentBatch","settings","navClient"];return r.intersection(Object.keys(e),n).length!==n.length?(i.writeLog("PROO_001","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to ProcessOutgoing.run"})):(a.runtime={callback:t,currentBatch:e.currentBatch,settings:e.settings,navClient:e.navClient,successfulTransactions:[],failedTransactions:[]},a.runtime.remainingTransactions=e.currentBatch,void a.processPending())},a.processPending=function(){return a.runtime.remainingTransactions.length<1?void a.runtime.callback(!0,{successfulTransactions:a.runtime.successfulTransactions,failedTransactions:a.runtime.failedTransactions}):void s.send({client:a.runtime.navClient,address:a.runtime.remainingTransactions[0].decrypted.n,amount:a.runtime.remainingTransactions[0].decrypted.v,transaction:a.runtime.remainingTransactions[0]},a.sentNav)},a.transactionFailed=function(){a.runtime.failedTransactions.push(a.runtime.remainingTransactions[0]),a.runtime.remainingTransactions.splice(0,1),a.processPending()},a.mockSend=function(){i.writeLog("PROO_003A","mock nav sent",{transaction:a.runtime.remainingTransactions[0]}),a.runtime.successfulTransactions.push({transaction:a.runtime.remainingTransactions[0].transaction}),a.runtime.remainingTransactions.splice(0,1),a.processPending()},a.sentNav=function(e,t){e&&t&&t.sendOutcome?a.runtime.successfulTransactions.push(a.runtime.remainingTransactions[0]):(i.writeLog("PROO_004","failed nav send to address",t,!0),a.runtime.failedTransactions.push(a.runtime.remainingTransactions[0])),a.runtime.remainingTransactions.splice(0,1),a.processPending()},e.exports=a},function(e,t,n){"use strict";var r=n(10),i=n(13),s=n(31),a=n(15),o={};o.run=function(e,t){var n=["settings","navClient"];return r.intersection(Object.keys(e),n).length!==n.length?(i.writeLog("PAY_001","invalid options",{options:e,required:n}),void t(!1,{message:"invalid options provided to PayoutFee.run"})):(o.runtime={callback:t,settings:e.settings,navClient:e.navClient},void o.send())},o.send=function(){o.runtime.navClient.listUnspent(20).then(function(e){var t=0,n=1e8,r=!0,u=!1,c=void 0;try{for(var d,l=e[Symbol.iterator]();!(r=(d=l.next()).done);r=!0){var g=d.value;t+=Math.round(g.amount*n)}}catch(e){u=!0,c=e}finally{try{!r&&l.return&&l.return()}finally{if(u)throw c}}var p=t/n;if(p65535)&&(C.errors.push("PORT_OUT_OF_RANGE for "+n+", must be between 1 and 65535 "),!0)}function u(e,t,n){var r=parseInt(e,10);return t.min&&rt.max)||(C.errors.push("INT_TOO_LARGE for "+n+", must be smaller than "+t.max),!1)}function c(e,t,n){var r=parseFloat(e);if(t.min&&rt.max)return C.errors.push("FLOAT_TOO_LARGE for "+n+", must be smaller than "+t.max),!1;if(t.decimals&&2===t.decimals){var i=e.toString().search(A)>=0;if(!i)return C.errors.push("FLOAT_INCORRECT_FORMAT for "+n+", must have a maximum of 2 decimal places "),!1}return!0}function d(e,t,n){return!!f.isEmail(e)||(C.errors.push("INVALID_DOMAIN for "+n+", must be a valid fully qualified domain name"),!1)}function l(e,t,n){return 34===e.length||"N"===e.charAt(0)||(C.errors.push("INVALID_NAV_ADDRESSS for "+n+", must be 34 characters and starting with N"),!1)}function g(e,t,n){return!t.length||e.length===t.length||(C.errors.push("INCORRECT_LENGTH for "+n+", must be "+t.length+" characters"+e.length+" provided"),!1)}function p(e,t,n){return e===t.value||(C.errors.push("INCORRECT_VALUE for "+n+", must equal "+t.value),!1)}var m="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},v=n(7),f=n(39),h=n(13),y=v.get("GLOBAL"),b=n(40),T=n(41),A=/^(\d+)?([.]?\d{0,2})?$/,C={errors:[]};C.validateSettings=function(e,t){if(C.errors=[],"INCOMING"===y.serverType)r(e.settings,e.ignore,b);else{if("OUTGOING"!==y.serverType)return h.writeLog("VAL_001","invalid server type",{options:e}),void t(!1);r(e.settings,e.ignore,T)}C.errors.length<1?t(!0):(h.writeLog("VAL_002","invalid settings",{errors:C.errors}),t(!1))},e.exports=C},function(e,t){e.exports=require("validator")},function(e,t){e.exports={local:{ipAddress:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"PORT"}},remote:{host:{required:!1,type:"DOMAIN"},ipAddress:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"PORT"}},scriptInterval:{required:!0,type:"INT",min:3e4},minAmount:{required:!0,type:"INT",min:1},maxAmount:{required:!0,type:"INT",min:10},anonFeePercent:{required:!0,type:"FLOAT",min:0,max:99,decimals:2},notificationEmail:{required:!0,type:"EMAIL"},smtp:{user:{required:!0,type:"EMAIL"},pass:{required:!0,type:"STRING"},server:{required:!0,type:"DOMAIN"}},navCoin:{user:{required:!0,type:"STRING"},pass:{required:!0,type:"STRING"},ip:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"VALUE",value:"44444"},walletPassphrase:{required:{conditional:"GLOBAL.encryptedWallet"},type:"STRING"}},subChain:{user:{required:!0,type:"STRING"},pass:{required:!0,type:"STRING"},ip:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"VALUE",value:"33333"},walletPassphrase:{required:{conditional:"GLOBAL.encryptedWallet"},type:"STRING"}},secretOptions:{salt:{required:!0,type:"STRING",length:32},saltRounds:{required:!0,type:"INT",min:1,max:20}},secret:{required:!0,type:"STRING",length:32}}},function(e,t){e.exports={local:{ipAddress:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"PORT"}},remote:{host:{required:!1,type:"DOMAIN"},ipAddress:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"PORT"}},scriptInterval:{required:!0,type:"INT",min:12e4},minAmount:{required:!0,type:"INT",min:1},maxAmount:{required:!0,type:"INT",min:10},navPoolAmount:{required:!0,type:"INT",min:5e4},txFeePayoutMin:{required:!0,type:"INT",min:1},anonTxFeeAddress:{required:!0,type:"NAV_ADDRESS"},notificationEmail:{required:!0,type:"EMAIL"},smtp:{user:{required:!0,type:"EMAIL"},pass:{required:!0,type:"STRING"},server:{required:!0,type:"DOMAIN"}},navCoin:{user:{required:!0,type:"STRING"},pass:{required:!0,type:"STRING"},ip:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"VALUE",value:"44444"},walletPassphrase:{required:{conditional:"GLOBAL.encryptedWallet"},type:"STRING"}},subChain:{user:{required:!0,type:"STRING"},pass:{required:!0,type:"STRING"},ip:{required:!0,type:"IP_ADDRESS"},port:{required:!0,type:"VALUE",value:"33333"},walletPassphrase:{required:{conditional:"GLOBAL.encryptedWallet"},type:"STRING"}},secret:{required:!0,type:"STRING",length:32}}}]); \ No newline at end of file diff --git a/dist/vendor.js b/dist/vendor.js index f2b2494..acf79a5 100644 --- a/dist/vendor.js +++ b/dist/vendor.js @@ -1 +1 @@ -!function(r){function e(o){if(t[o])return t[o].exports;var n=t[o]={exports:{},id:o,loaded:!1};return r[o].call(n.exports,n,n.exports,e),n.loaded=!0,n.exports}var t={};return e.m=r,e.c=t,e.p="",e(0)}([function(r,e,t){t(1),t(6),t(2),t(3),t(4),t(5),t(23),t(11),t(40),t(14),t(37),t(12),r.exports=t(8)},function(r,e){r.exports=require("bitcoin-core")},function(r,e){r.exports=require("express")},function(r,e){r.exports=require("https")},function(r,e){r.exports=require("pem")},function(r,e){r.exports=require("body-parser")},function(r,e){r.exports=require("fs")},,function(r,e){r.exports=require("md5-file")},,,function(r,e){r.exports=require("ursa")},function(r,e){r.exports=require("lodash")},,function(r,e){r.exports=require("nodemailer")},,,,,,,,,function(r,e){r.exports=require("request")},,,,,,,,,,,,,,function(r,e){r.exports=require("validator")},,,function(r,e){r.exports=require("bcrypt")}]); \ No newline at end of file +!function(r){function e(o){if(t[o])return t[o].exports;var n=t[o]={exports:{},id:o,loaded:!1};return r[o].call(n.exports,n,n.exports,e),n.loaded=!0,n.exports}var t={};return e.m=r,e.c=t,e.p="",e(0)}([function(r,e,t){t(1),t(6),t(2),t(3),t(4),t(5),t(23),t(12),t(42),t(14),t(39),t(10),r.exports=t(8)},function(r,e){r.exports=require("bitcoin-core")},function(r,e){r.exports=require("express")},function(r,e){r.exports=require("https")},function(r,e){r.exports=require("pem")},function(r,e){r.exports=require("body-parser")},function(r,e){r.exports=require("fs")},,function(r,e){r.exports=require("md5-file")},,function(r,e){r.exports=require("lodash")},,function(r,e){r.exports=require("ursa")},,function(r,e){r.exports=require("nodemailer")},,,,,,,,,function(r,e){r.exports=require("request")},,,,,,,,,,,,,,,,function(r,e){r.exports=require("validator")},,,function(r,e){r.exports=require("bcrypt")}]); \ No newline at end of file diff --git a/src/app.js b/src/app.js index 969e4af..2fd5c3f 100644 --- a/src/app.js +++ b/src/app.js @@ -245,6 +245,7 @@ const apiInit = () => { })) return } + // Logger.writeLog('APP_TEST_001', 'success get-addresses', { data }) NavtechApi.runtime.res.send(JSON.stringify({ status: 200, type: 'SUCCESS', @@ -324,6 +325,18 @@ const apiInit = () => { return } + if (globalSettings.serverType === 'INCOMING' && IncomingServer.paused + || globalSettings.serverType === 'OUTGOING' && OutgoingServer.paused) { + Logger.writeLog('APP_026A', 'this server is paused for manual recovery', { body: req.body }) + NavtechApi.runtime.res.send(JSON.stringify({ + status: 200, + type: 'FAIL', + code: 'APP_026A', + message: 'server is not accepting transactions', + })) + return + } + NavtechApi.runtime.numAddresses = parseInt(NavtechApi.runtime.req.body.num_addresses, 10) if (globalSettings.serverType === 'INCOMING') { @@ -452,7 +465,7 @@ const apiInit = () => { highestConf = pending.confirmations } } - if (highestConf > 60) { + if (highestConf > privateSettings.maxQueue) { Logger.writeLog('APP_030B', 'the queue is too long', { highestConf }, true) NavtechApi.runtime.res.send(JSON.stringify({ status: 200, @@ -571,6 +584,11 @@ const apiInit = () => { returnData.nav_addresses = NavtechApi.runtime.navAddresses + // Logger.writeLog('APP_TEST_002', 'success check-node', { + // returnData, + // request: NavtechApi.runtime.req.body, + // }) + NavtechApi.runtime.res.send(JSON.stringify({ status: 200, type: 'SUCCESS', @@ -578,6 +596,8 @@ const apiInit = () => { })) } + // @TODO check if server paused before returning as valid incoming server + // -------------- CHECK IF SERVER IS PROCESSING ------------------------------------------------------------------------------------------ app.get('/api/status', (req, res) => { diff --git a/src/incoming.js b/src/incoming.js index 4a76ac6..4d30ba9 100644 --- a/src/incoming.js +++ b/src/incoming.js @@ -2,17 +2,18 @@ const Client = require('bitcoin-core') const config = require('config') +const lodash = require('lodash') -const EncryptionKeys = require('./lib/EncryptionKeys.js') -const Logger = require('./lib/Logger.js') -const PreFlight = require('./lib/PreFlight.js') -const RefillOutgoing = require('./lib/RefillOutgoing.js') -const SelectOutgoing = require('./lib/SelectOutgoing.js') -const ReturnAllToSenders = require('./lib/ReturnAllToSenders.js') -const PrepareIncoming = require('./lib/PrepareIncoming.js') -const RetrieveSubchainAddresses = require('./lib/RetrieveSubchainAddresses.js') -const ProcessIncoming = require('./lib/ProcessIncoming.js') -const SpendToHolding = require('./lib/SpendToHolding.js') +let EncryptionKeys = require('./lib/EncryptionKeys.js') //eslint-disable-line +let Logger = require('./lib/Logger.js') //eslint-disable-line +let PreFlight = require('./lib/PreFlight.js') //eslint-disable-line +let RefillOutgoing = require('./lib/RefillOutgoing.js') //eslint-disable-line +let SelectOutgoing = require('./lib/SelectOutgoing.js') //eslint-disable-line +let ReturnAllToSenders = require('./lib/ReturnAllToSenders.js') //eslint-disable-line +let PrepareIncoming = require('./lib/PrepareIncoming.js') //eslint-disable-line +let RetrieveSubchainAddresses = require('./lib/RetrieveSubchainAddresses.js') //eslint-disable-line +let ProcessIncoming = require('./lib/ProcessIncoming.js') //eslint-disable-line +let SpendToHolding = require('./lib/SpendToHolding.js') //eslint-disable-line const settings = config.get('INCOMING') @@ -41,10 +42,11 @@ IncomingServer.init = () => { Logger.writeLog('INC_000', 'server starting') EncryptionKeys.findKeysToRemove({ type: 'private' }, IncomingServer.startProcessing) - setInterval(() => { + IncomingServer.cron = setInterval(() => { if (IncomingServer.paused === false) { EncryptionKeys.findKeysToRemove({ type: 'private' }, IncomingServer.startProcessing) } else { + clearInterval(IncomingServer.cron) Logger.writeLog('INC_001', 'processing paused', { paused: IncomingServer.paused }) } }, settings.scriptInterval) @@ -80,6 +82,7 @@ IncomingServer.holdingProcessed = (success, data) => { if (!success) { Logger.writeLog('INC_004', 'failed to process the holding account', { success, data }, true) IncomingServer.processing = false + IncomingServer.paused = true return } SelectOutgoing.run({ @@ -96,6 +99,9 @@ IncomingServer.outgoingSelected = (success, data) => { } if (data.returnAllToSenders) { + if (data.pause) { + IncomingServer.paused = true + } ReturnAllToSenders.run({ navClient: IncomingServer.navClient, }, IncomingServer.allPendingReturned) @@ -111,14 +117,15 @@ IncomingServer.outgoingSelected = (success, data) => { navClient: IncomingServer.navClient, outgoingNavBalance: data.outgoingNavBalance, subBalance: IncomingServer.runtime.subBalance, + settings, }, IncomingServer.currentBatchPrepared) } IncomingServer.allPendingReturned = (success, data) => { - console.log('STATUS: IncomingServer.allPendingReturned', success, data) if (!success) { Logger.writeLog('INC_006', 'failed to return all pending to sender', { success, data }, true) IncomingServer.processing = false + IncomingServer.paused = true return } Logger.writeLog('INC_007', 'returned all pending to sender', { success, data }, true) @@ -127,15 +134,55 @@ IncomingServer.allPendingReturned = (success, data) => { } IncomingServer.currentBatchPrepared = (success, data) => { - if (!success || !data || !data.currentBatch) { + if (!success || !data || ((!data.currentBatch || !data.currentFlattened || !data.numFlattened) && !data.pendingToReturn)) { + Logger.writeLog('INC_011D', 'prepareIncoming returned bad data', { success, data }) IncomingServer.processing = false return } + IncomingServer.runtime.currentBatch = data.currentBatch + IncomingServer.runtime.currentFlattened = data.currentFlattened + IncomingServer.runtime.numFlattened = data.numFlattened + IncomingServer.runtime.pendingToReturn = data.pendingToReturn + + if (IncomingServer.runtime.pendingToReturn && IncomingServer.runtime.pendingToReturn.length > 0) { + Logger.writeLog('INC_011', 'failed to process some transactions', { success, data }, true) + ReturnAllToSenders.fromList({ + navClient: IncomingServer.navClient, + transactionsToReturn: IncomingServer.runtime.pendingToReturn, + }, IncomingServer.pendingFailedReturned) + return + } + + if (!IncomingServer.runtime.currentBatch || lodash.size(IncomingServer.runtime.currentBatch) === 0) { + Logger.writeLog('INC_011B', 'no currentBatch to process', { currentBatch: IncomingServer.runtime.currentBatch }) + IncomingServer.processing = false + return + } + RetrieveSubchainAddresses.run({ + subClient: IncomingServer.subClient, + chosenOutgoing: IncomingServer.runtime.chosenOutgoing, + numAddresses: IncomingServer.runtime.numFlattened, + }, IncomingServer.retrievedSubchainAddresses) +} + +IncomingServer.pendingFailedReturned = (success, data) => { + if (!success) { + Logger.writeLog('INC_011A', 'failed to return failed pending to sender', { success, data }, true) + IncomingServer.paused = true + ReturnAllToSenders.run({ + navClient: IncomingServer.navClient, + }, IncomingServer.allPendingReturned) + } + if (!IncomingServer.runtime.currentBatch || lodash.size(IncomingServer.runtime.currentBatch) === 0) { + Logger.writeLog('INC_011C', 'no currentBatch to process', { currentBatch: IncomingServer.runtime.currentBatch }) + IncomingServer.processing = false + return + } RetrieveSubchainAddresses.run({ subClient: IncomingServer.subClient, chosenOutgoing: IncomingServer.runtime.chosenOutgoing, - currentBatch: data.currentBatch, + numAddresses: IncomingServer.runtime.numFlattened, }, IncomingServer.retrievedSubchainAddresses) } @@ -147,8 +194,10 @@ IncomingServer.retrievedSubchainAddresses = (success, data) => { }, IncomingServer.allPendingReturned) return } + // @TODO compile the correct transactions to return ProcessIncoming.run({ currentBatch: IncomingServer.runtime.currentBatch, + currentFlattened: IncomingServer.runtime.currentFlattened, outgoingPubKey: IncomingServer.runtime.outgoingPubKey, subClient: IncomingServer.subClient, navClient: IncomingServer.navClient, @@ -159,21 +208,38 @@ IncomingServer.retrievedSubchainAddresses = (success, data) => { IncomingServer.transactionsProcessed = (success, data) => { if (!success || !data) { + if (data && data.partialFailure) { + Logger.writeLog('INC_010A', 'failed part way through processing subchain transactions', { success, data }, true) + IncomingServer.paused = true + IncomingServer.processing = false + return + } Logger.writeLog('INC_010', 'failed to process transactions', { success, data }, true) + IncomingServer.paused = true ReturnAllToSenders.run({ navClient: IncomingServer.navClient, }, IncomingServer.allPendingReturned) return } - IncomingServer.runtime.successfulSubTransactions = data.successfulSubTransactions - IncomingServer.runtime.transactionsToReturn = data.transactionsToReturn + IncomingServer.runtime.successfulTxGroups = data.successfulTxGroups + IncomingServer.runtime.txGroupsToReturn = data.txGroupsToReturn + IncomingServer.runtime.transactionsToReturn = [] - if (IncomingServer.runtime.transactionsToReturn && IncomingServer.runtime.transactionsToReturn.length > 0) { + if (IncomingServer.runtime.txGroupsToReturn && IncomingServer.runtime.txGroupsToReturn.length > 0) { Logger.writeLog('INC_011', 'failed to process some transactions', { success, data }, true) + + // extract the relevant transactions to return from the txGroupsToReturn + for (let i = 0; i < IncomingServer.runtime.txGroupsToReturn.length; i++) { + const txGroup = IncomingServer.runtime.txGroupsToReturn[i] + for (let j = 0; j < txGroup.transactions.length; j++) { + IncomingServer.runtime.transactionsToReturn.push(txGroup.transactions[j]) + } + } + ReturnAllToSenders.fromList({ navClient: IncomingServer.navClient, - transactionsToReturn: data.transactionsToReturn, + transactionsToReturn: IncomingServer.runtime.transactionsToReturn, }, IncomingServer.failedTransactionsReturned) return } @@ -182,8 +248,18 @@ IncomingServer.transactionsProcessed = (success, data) => { IncomingServer.failedTransactionsReturned = (success, data) => { if (!success) { + IncomingServer.paused = true Logger.writeLog('INC_012', 'failed to return failed transactions to sender', { success, data }, true) } + IncomingServer.runtime.successfulSubTransactions = [] + // extract the relevant transactions to return from the txGroupsToReturn + for (let i = 0; i < IncomingServer.runtime.successfulTxGroups.length; i++) { + const txGroup = IncomingServer.runtime.successfulTxGroups[i] + lodash.forEach(txGroup.transactions, (transaction) => { + IncomingServer.runtime.successfulSubTransactions.push(transaction) + }) + } + SpendToHolding.run({ successfulSubTransactions: IncomingServer.runtime.successfulSubTransactions, holdingEncrypted: IncomingServer.runtime.holdingEncrypted, diff --git a/src/lib/EncryptedData.js b/src/lib/EncryptedData.js index 9f5713a..c2deb91 100644 --- a/src/lib/EncryptedData.js +++ b/src/lib/EncryptedData.js @@ -63,7 +63,8 @@ EncryptedData.decryptData = (options, callback) => { const key = ursa.createPrivateKey(fs.readFileSync(keyFile)) const msg = key.decrypt(options.encryptedData, 'base64', 'utf8', ursa.RSA_PKCS1_PADDING) successfulDecryption = true - decrypted = (globalSettings.serverType === 'INCOMING') ? msg : JSON.parse(msg) + // Logger.writeLog('ECD_TEST', 'decrypted', { msg }) + decrypted = JSON.parse(msg) } catch (err2) { // do nothing } diff --git a/src/lib/FlattenTransactions.js b/src/lib/FlattenTransactions.js new file mode 100644 index 0000000..9966879 --- /dev/null +++ b/src/lib/FlattenTransactions.js @@ -0,0 +1,74 @@ +const lodash = require('lodash') + +let Logger = require('./Logger.js') //eslint-disable-line + +const FlattenTransactions = {} + +FlattenTransactions.incoming = (options, callback) => { + const required = ['amountToFlatten', 'anonFeePercent'] + if (lodash.intersection(Object.keys(options), required).length !== required.length) { + Logger.writeLog('FLT_001', 'invalid options', { options, required }) + callback(false, { message: 'invalid options provided to FlattenTransactions.incoming' }) + return + } + const unsafeAmount = options.amountToFlatten / (1 + (options.anonFeePercent / 100)) + FlattenTransactions.runtime = { + callback, + amountToFlatten: FlattenTransactions.satoshiParser(unsafeAmount), + } + FlattenTransactions.flattenIncoming() +} + +FlattenTransactions.flattenIncoming = () => { + const totalInt = Math.floor(FlattenTransactions.runtime.amountToFlatten) + const totalIntString = totalInt.toString() + const decimal = FlattenTransactions.runtime.amountToFlatten - totalInt + const safeDecimal = FlattenTransactions.satoshiParser(decimal) + + let flattened = [] + + for (let i = 0; i < totalIntString.length; i++) { + const factor = 1 * Math.pow(10, totalIntString.length - (i + 1)) + const numFactors = parseInt(totalIntString[i], 10) + for (let j = 0; j < numFactors; j++) { + if (safeDecimal > 0 && lodash.sum(flattened) === totalInt - factor) { + flattened.push(FlattenTransactions.satoshiParser(parseInt(factor, 10) + safeDecimal)) + } else { + flattened.push(parseInt(factor, 10)) + } + } + } + + if (flattened.length === 1) { + flattened = [] + for (let k = 0; k < 10; k++) { + if (safeDecimal > 0 && k === 9) { + flattened.push(FlattenTransactions.satoshiParser((totalInt / 10) + safeDecimal)) + } else { + flattened.push(totalInt / 10) + } + } + } + + const reduced = flattened.reduce((acc, x) => x + acc, 0) + const safeReduced = FlattenTransactions.satoshiParser(reduced) + + if (safeReduced !== FlattenTransactions.runtime.amountToFlatten) { + Logger.writeLog('FLT_002', 'unable to correctly flatten amount', { runtime: FlattenTransactions.runtime, flattened }) + FlattenTransactions.runtime.callback(false, { + flattened, + }) + return + } + + FlattenTransactions.runtime.callback(true, { + flattened, + }) +} + +FlattenTransactions.satoshiParser = (unsafe) => { + const satoshiFactor = 100000000 + return Math.round(unsafe * satoshiFactor) / satoshiFactor +} + +module.exports = FlattenTransactions diff --git a/src/lib/GroupPartials.js b/src/lib/GroupPartials.js new file mode 100644 index 0000000..5be81e6 --- /dev/null +++ b/src/lib/GroupPartials.js @@ -0,0 +1,177 @@ +const lodash = require('lodash') + +let Logger = require('./Logger.js') // eslint-disable-line +let EncryptedData = require('./EncryptedData.js') // eslint-disable-line + +let privateSettings = require('../settings/private.settings') //eslint-disable-line +const GroupPartials = {} + +GroupPartials.run = (options, callback) => { + const required = ['currentPending', 'client'] + if (lodash.intersection(Object.keys(options), required).length !== required.length) { + Logger.writeLog('GRP_001', 'invalid options', { options, required }) + callback(false, { message: 'invalid options provided to GroupPartials.run' }) + return + } + + GroupPartials.runtime = { + client: options.client, + currentPending: options.currentPending, + remainingToDecrypt: options.currentPending, + transactionsToReturn: [], + readyToProcess: {}, + partials: {}, + callback, + } + GroupPartials.getDecryptedData() +} + +GroupPartials.getDecryptedData = () => { + if (GroupPartials.runtime.remainingToDecrypt.length < 1) { + GroupPartials.checkPartials() + return + } + + EncryptedData.getEncrypted({ + transaction: GroupPartials.runtime.remainingToDecrypt[0], + client: GroupPartials.runtime.client, + }, GroupPartials.checkDecrypted) +} + +GroupPartials.partialFailed = (transaction) => { + const returnIndex = lodash.findIndex(GroupPartials.runtime.transactionsToReturn, (tx) => tx.txid === transaction.txid) + if (returnIndex === -1) { + GroupPartials.runtime.transactionsToReturn.push(transaction) + } + GroupPartials.runtime.remainingToDecrypt.splice(0, 1) + GroupPartials.getDecryptedData() +} + +GroupPartials.checkDecrypted = (success, data) => { + if (!success || !data || !data.decrypted || !data.transaction) { + Logger.writeLog('GRP_002', 'failed to decrypt transaction data', { success }) + GroupPartials.partialFailed(GroupPartials.runtime.remainingToDecrypt[0]) + return + } + if (!data.decrypted.n || !data.decrypted.t || !data.decrypted.p || !data.decrypted.o || !data.decrypted.u) { + Logger.writeLog('GRP_003', 'failed to receive correct encrypted params', { + n: !!data.decrypted.n, + t: data.decrypted.t, + p: data.decrypted.p, + o: data.decrypted.o, + u: data.decrypted.u, + data, + }) + GroupPartials.partialFailed(data.transaction) + return + } + + GroupPartials.runtime.client.validateAddress(data.decrypted.n).then((addressInfo) => { + if (addressInfo.isvalid !== true) { + Logger.writeLog('GRP_003A', 'encrypted address invalid', { success, data }) + GroupPartials.partialFailed(data.transaction) + return + } + GroupPartials.groupPartials(data.decrypted, data.transaction) + }).catch((err) => { + Logger.writeLog('GRP_003B', 'failed to decrypt transaction data', { success, error: err }) + GroupPartials.partialFailed(data.transaction) + return + }) +} + +GroupPartials.groupPartials = (decrypted, transaction) => { + if (!GroupPartials.runtime.partials[decrypted.u]) { + GroupPartials.runtime.partials[decrypted.u] = { + destination: decrypted.n, + unique: decrypted.u, + timeDelay: parseInt(decrypted.t, 10), + parts: parseInt(decrypted.o, 10), + partsSum: 0, + amount: 0, + transactions: {}, + readyToProcess: false, + } + } + + if (GroupPartials.runtime.partials[decrypted.u].readyToProcess === true) { + Logger.writeLog('GRP_006', 'this partial group is already flagged as completed', { + partials: GroupPartials.runtime.partials[decrypted.u], + transaction, + n: !!decrypted.n, + t: decrypted.t, + p: decrypted.p, + o: decrypted.o, + u: decrypted.u, + }) + GroupPartials.partialFailed(transaction) + return + } + + if (GroupPartials.runtime.partials[decrypted.u].destination !== decrypted.n) { + Logger.writeLog('GRP_004', 'decrypted address different from other partials', { + partials: GroupPartials.runtime.partials[decrypted.u], + transaction, + }) + GroupPartials.partialFailed(transaction) + return + } + + if (GroupPartials.runtime.partials[decrypted.u].transactions[transaction.txid]) { + Logger.writeLog('GRP_005', 'txid already processed', { + partials: GroupPartials.runtime.partials[decrypted.u], + transaction, + }) + GroupPartials.partialFailed(transaction) + return + } + + // @TODO investigate rounding error + + const satoshiAmount = Math.round(GroupPartials.runtime.partials[decrypted.u].amount * 100000000) + const satoshiPartialAmount = Math.round(transaction.amount * 100000000) + + const safeTotal = (satoshiAmount + satoshiPartialAmount) / 100000000 + + GroupPartials.runtime.partials[decrypted.u].amount = safeTotal + GroupPartials.runtime.partials[decrypted.u].partsSum += parseInt(decrypted.p, 10) + GroupPartials.runtime.partials[decrypted.u].transactions[transaction.txid] = { + txid: transaction.txid, + amount: transaction.amount, + confirmations: transaction.confirmations, + vout: transaction.vout, + vin: transaction.vin, + part: decrypted.p, + } + + const numParts = GroupPartials.runtime.partials[decrypted.u].parts + const numTransactions = lodash.size(GroupPartials.runtime.partials[decrypted.u].transactions) + + if (numTransactions === numParts + && (numParts * (numParts + 1)) / 2 === GroupPartials.runtime.partials[decrypted.u].partsSum) { + GroupPartials.runtime.partials[decrypted.u].readyToProcess = true + GroupPartials.runtime.readyToProcess[decrypted.u] = GroupPartials.runtime.partials[decrypted.u] + } + GroupPartials.runtime.remainingToDecrypt.splice(0, 1) + GroupPartials.getDecryptedData() +} + +GroupPartials.checkPartials = () => { + lodash.forEach(GroupPartials.runtime.partials, (partial) => { + if (!partial.readyToProcess) { + lodash.forEach(partial.transactions, (partialTx) => { + const returnIndex = lodash.findIndex(GroupPartials.runtime.transactionsToReturn, (tx) => tx.txid === partialTx.txid) + if (returnIndex === -1 && partialTx.confirmations > privateSettings.maxConfs) { // if its not already flagged as returning & too old + GroupPartials.runtime.transactionsToReturn.push(partialTx) + } + }) + } + }) + + GroupPartials.runtime.callback(true, { + readyToProcess: GroupPartials.runtime.readyToProcess, + transactionsToReturn: GroupPartials.runtime.transactionsToReturn, + }) +} + +module.exports = GroupPartials diff --git a/src/lib/Logger.js b/src/lib/Logger.js index 5ccc868..2199406 100644 --- a/src/lib/Logger.js +++ b/src/lib/Logger.js @@ -4,11 +4,14 @@ const nodemailer = require('nodemailer') const config = require('config') let globalSettings = config.get('GLOBAL') // eslint-disable-line - let settings = false if (globalSettings.serverType === 'INCOMING') settings = config.get('INCOMING') if (globalSettings.serverType === 'OUTGOING') settings = config.get('OUTGOING') +const privateSettings = require('../settings/private.settings') + +const version = privateSettings.version.major + '.' + privateSettings.version.minor + '.' + privateSettings.version.patch + const emailAuth = encodeURIComponent(settings.smtp.user) + ':' + encodeURIComponent(settings.smtp.pass) const Logger = {} @@ -17,6 +20,13 @@ Logger.transporter = nodemailer.createTransport('smtps://' + emailAuth + '@' + s // const emailCodes = ['INC_E01'] +const startDate = new Date() +let startUpInfo = '\r\n' +startUpInfo += 'Version: ' + version + '\r\n' +startUpInfo += 'Date: ' + startDate + '\r\n' +startUpInfo += '\r\n-----------------------------------------------------------\r\n' +console.log(startUpInfo) + Logger.writeLog = (errorCode, errorMessage, data, email) => { if (email) { Logger.sendMail(errorCode, errorMessage, data) diff --git a/src/lib/PayoutFee.js b/src/lib/PayoutFee.js index faa08be..8548e40 100644 --- a/src/lib/PayoutFee.js +++ b/src/lib/PayoutFee.js @@ -22,7 +22,13 @@ PayoutFee.run = (options, callback) => { } PayoutFee.send = () => { - PayoutFee.runtime.navClient.getBalance().then((navBalance) => { + PayoutFee.runtime.navClient.listUnspent(20).then((unspent) => { + let navBalanceSat = 0 + const satoshiFactor = 100000000 + for (const pending of unspent) { + navBalanceSat += Math.round(pending.amount * satoshiFactor) + } + const navBalance = navBalanceSat / satoshiFactor if (navBalance < PayoutFee.runtime.settings.navPoolAmount) { Logger.writeLog('PAY_002', 'nav pool balance less than expected', { navPoolAmount: PayoutFee.runtime.settings.navPoolAmount, @@ -41,7 +47,12 @@ PayoutFee.send = () => { client: PayoutFee.runtime.navClient, address: PayoutFee.runtime.settings.anonTxFeeAddress, amount: txFeeAccrued, + transaction: { txid: 'TX_FEE_PAYOUT' }, }, PayoutFee.sent) + }).catch((err) => { + Logger.writeLog('PAY_002A', 'error getting balance', { error: err }) + PayoutFee.runtime.callback(false, { message: 'error getting balance' }) + return }) } diff --git a/src/lib/PrepareIncoming.js b/src/lib/PrepareIncoming.js index fc51647..bb2b228 100644 --- a/src/lib/PrepareIncoming.js +++ b/src/lib/PrepareIncoming.js @@ -2,15 +2,17 @@ const lodash = require('lodash') const config = require('config') const globalSettings = config.get('GLOBAL') +let privateSettings = require('../settings/private.settings.json') // eslint-disable-line let Logger = require('./Logger.js') // eslint-disable-line let NavCoin = require('./NavCoin.js') // eslint-disable-line -const privateSettings = require('../settings/private.settings.json') +let FlattenTransactions = require('./FlattenTransactions.js') // eslint-disable-line +let GroupPartials = require('./GroupPartials.js') // eslint-disable-line const PrepareIncoming = {} PrepareIncoming.run = (options, callback) => { - const required = ['navClient', 'outgoingNavBalance', 'subBalance'] + const required = ['navClient', 'outgoingNavBalance', 'subBalance', 'settings'] if (lodash.intersection(Object.keys(options), required).length !== required.length) { Logger.writeLog('PREPI_001', 'invalid options', { options, required }) callback(false, { message: 'invalid options provided to ReturnAllToSenders.run' }) @@ -21,6 +23,10 @@ PrepareIncoming.run = (options, callback) => { navClient: options.navClient, outgoingNavBalance: options.outgoingNavBalance, subBalance: options.subBalance, + currentFlattened: {}, + currentBatch: [], + numFlattened: 0, + settings: options.settings, } PrepareIncoming.getUnspent() @@ -40,7 +46,7 @@ PrepareIncoming.getUnspent = () => { PrepareIncoming.unspentFiltered) }).catch((err) => { Logger.writeLog('PREPI_002', 'failed to list unspent', err) - PrepareIncoming.runtime.callback(false, { message: 'failed to list unspent' }) + PrepareIncoming.runtime.callback(false, { message: 'failed to list unspent', err }) return }) } @@ -51,10 +57,33 @@ PrepareIncoming.unspentFiltered = (success, data) => { PrepareIncoming.runtime.callback(false, { message: 'no current pending to return' }) return } - PrepareIncoming.runtime.currentPending = data.currentPending + GroupPartials.run({ + currentPending: data.currentPending, + client: PrepareIncoming.runtime.navClient, + }, PrepareIncoming.partialsGrouped) +} + +PrepareIncoming.partialsGrouped = (success, data) => { + if (!success || !data) { + Logger.writeLog('PREPI_003A', 'GroupPartials failed', { success, data }) + PrepareIncoming.runtime.callback(false, { + pendingToReturn: data ? data.transactionsToReturn : null, + }) + } + + if (!data.readyToProcess) { + Logger.writeLog('PREPI_003AA', 'GroupPartials failed to return correct data', { data }) + // @TODO handle this return case + PrepareIncoming.runtime.callback(false, { + pendingToReturn: data.transactionsToReturn ? data.transactionsToReturn : null, + }) + return + } + PrepareIncoming.runtime.transactionsToReturn = data.transactionsToReturn ? data.transactionsToReturn : null + PrepareIncoming.pruneUnspent({ - currentPending: PrepareIncoming.runtime.currentPending, + readyToProcess: data.readyToProcess, client: PrepareIncoming.runtime.navClient, subBalance: PrepareIncoming.runtime.subBalance, maxAmount: PrepareIncoming.runtime.outgoingNavBalance, @@ -62,42 +91,111 @@ PrepareIncoming.unspentFiltered = (success, data) => { } PrepareIncoming.pruneUnspent = (options, callback) => { - if (!options.currentPending || + if (!options.readyToProcess || !parseFloat(options.subBalance) || !parseFloat(options.maxAmount)) { - Logger.writeLog('NAV_006', 'pruneIncomingUnspent invalid params', { options }) + Logger.writeLog('PREPI_003B', 'pruneIncomingUnspent invalid params', { options }) callback(false, { message: 'invalid params' }) return } const currentBatch = [] let hasPruned = false let sumPending = 0 - for (const pending of options.currentPending) { + lodash.forEach(options.readyToProcess, (txGroup) => { if ((currentBatch.length + 1) * (parseFloat(privateSettings.subCoinsPerTx) + parseFloat(privateSettings.subChainTxFee)) <= options.subBalance && - sumPending + pending.amount < parseFloat(options.maxAmount) && - currentBatch.length < privateSettings.maxAddresses) { - sumPending += pending.amount + sumPending + txGroup.amount < parseFloat(options.maxAmount)) { + sumPending += txGroup.amount hasPruned = true - currentBatch.push(pending) + currentBatch.push(txGroup) } - } + }) if (hasPruned) { callback(true, { currentBatch, sumPending }) } else { - callback(false, { message: 'no pruned' }) + callback(true, { message: 'no pruned' }) } } PrepareIncoming.unspentPruned = (success, data) => { if (!success || !data || !data.currentBatch || data.currentBatch.length < 1) { - Logger.writeLog('PREPI_003', 'failed to prune unspent', { success, data }) - PrepareIncoming.runtime.callback(false, { message: 'failed to prune unspent' }) + if (!PrepareIncoming.runtime.transactionsToReturn || PrepareIncoming.runtime.transactionsToReturn < 1) { + Logger.writeLog('PREPI_003D', 'no pruned and none to return', { success, data }) + PrepareIncoming.runtime.callback(false, { + pendingToReturn: PrepareIncoming.runtime.transactionsToReturn, + }) + return + } + Logger.writeLog('PREPI_003C', 'no pruned but some to return', { success, data }) + PrepareIncoming.runtime.callback(true, { + pendingToReturn: PrepareIncoming.runtime.transactionsToReturn, + }) return } - - PrepareIncoming.runtime.callback(true, { currentBatch: data.currentBatch }) + PrepareIncoming.runtime.remainingToFlatten = data.currentBatch.slice(0) + PrepareIncoming.runtime.currentBatch = data.currentBatch.slice(0) + FlattenTransactions.incoming({ + amountToFlatten: PrepareIncoming.runtime.remainingToFlatten[0].amount, + anonFeePercent: PrepareIncoming.runtime.settings.anonFeePercent, + }, PrepareIncoming.flattened) return } +PrepareIncoming.flattened = (success, data) => { + if (!success || !data || !data.flattened) { + Logger.writeLog('PREPI_004', 'failed to flatten transactions', { + success, + data, + runtime: PrepareIncoming.runtime, + }) + + // if it fails, move onto the next transaction + // this will get rejected after the block timeout if it continually fails + PrepareIncoming.runtime.remainingToFlatten.splice(0, 1) + if (PrepareIncoming.runtime.remainingToFlatten.length === 0) { + PrepareIncoming.runtime.callback(true, { + currentBatch: PrepareIncoming.runtime.currentBatch, + currentFlattened: PrepareIncoming.runtime.currentFlattened, + numFlattened: PrepareIncoming.runtime.numFlattened, + pendingToReturn: PrepareIncoming.runtime.transactionsToReturn, + }) + return + } + FlattenTransactions.incoming({ + amountToFlatten: PrepareIncoming.runtime.remainingToFlatten[0].amount, + anonFeePercent: PrepareIncoming.runtime.settings.anonFeePercent, + }, PrepareIncoming.flattened) + return + } + + if (PrepareIncoming.runtime.numFlattened + data.flattened.length >= privateSettings.maxAddresses) { + PrepareIncoming.runtime.callback(true, { + currentBatch: PrepareIncoming.runtime.currentBatch, + currentFlattened: PrepareIncoming.runtime.currentFlattened, + numFlattened: PrepareIncoming.runtime.numFlattened, + pendingToReturn: PrepareIncoming.runtime.transactionsToReturn, + }) + return + } + + PrepareIncoming.runtime.numFlattened += data.flattened.length + PrepareIncoming.runtime.currentFlattened[PrepareIncoming.runtime.remainingToFlatten[0].unique] = data.flattened + PrepareIncoming.runtime.remainingToFlatten.splice(0, 1) + + if (PrepareIncoming.runtime.remainingToFlatten.length === 0) { + PrepareIncoming.runtime.callback(true, { + currentBatch: PrepareIncoming.runtime.currentBatch, + currentFlattened: PrepareIncoming.runtime.currentFlattened, + numFlattened: PrepareIncoming.runtime.numFlattened, + pendingToReturn: PrepareIncoming.runtime.transactionsToReturn, + }) + return + } + + FlattenTransactions.incoming({ + amountToFlatten: PrepareIncoming.runtime.remainingToFlatten[0].amount, + anonFeePercent: PrepareIncoming.runtime.settings.anonFeePercent, + }, PrepareIncoming.flattened) +} + module.exports = PrepareIncoming diff --git a/src/lib/PrepareOutgoing.js b/src/lib/PrepareOutgoing.js index 4433a64..b5ed750 100644 --- a/src/lib/PrepareOutgoing.js +++ b/src/lib/PrepareOutgoing.js @@ -27,7 +27,14 @@ PrepareOutgoing.run = (options, callback) => { currentBatch: [], sumPending: 0, } - PrepareOutgoing.getUnspent() + PrepareOutgoing.runtime.navClient.getBlockCount().then((blockHeight) => { + PrepareOutgoing.runtime.currentBlockHeight = blockHeight + PrepareOutgoing.getUnspent() + }).catch((err) => { + Logger.writeLog('PREPO_001A', 'failed to get the current blockheight', { error: err }) + callback(false, { message: 'failed to get the current blockheight' }) + return + }) } PrepareOutgoing.getUnspent = () => { @@ -87,12 +94,12 @@ PrepareOutgoing.checkDecrypted = (success, data) => { PrepareOutgoing.failedTransaction() return } - if (!data.decrypted.a || !data.decrypted.n || !data.decrypted.s) { + if (!data.decrypted.n || !data.decrypted.v || !data.decrypted.s) { Logger.writeLog('PREPO_005', 'transaction has invalid params', { success }) PrepareOutgoing.failedTransaction() return } - if (parseFloat(data.decrypted.n) > PrepareOutgoing.runtime.settings.maxAmount) { + if (parseFloat(data.decrypted.v) > PrepareOutgoing.runtime.settings.maxAmount) { Logger.writeLog('PREPO_006', 'decrypted amount is larger than maxAmount', { success }) PrepareOutgoing.failedTransaction() return @@ -101,24 +108,33 @@ PrepareOutgoing.checkDecrypted = (success, data) => { Logger.writeLog('PREPO_007', 'secret mismatch', { success }) PrepareOutgoing.failedTransaction() } - PrepareOutgoing.testDecrypted(data.decrypted, data.transaction) + const decrypted = data.decrypted + if (!decrypted.t) decrypted.t = 0 + + PrepareOutgoing.testDecrypted(decrypted, data.transaction) } PrepareOutgoing.testDecrypted = (decrypted, transaction) => { - PrepareOutgoing.runtime.navClient.validateAddress(decrypted.a).then((addressInfo) => { + PrepareOutgoing.runtime.navClient.validateAddress(decrypted.n).then((addressInfo) => { if (addressInfo.isvalid !== true) { Logger.writeLog('PREPO_008', 'recipient address is invalid', { transaction }) PrepareOutgoing.failedTransaction() return } - - if (PrepareOutgoing.runtime.navBalance > PrepareOutgoing.runtime.sumPending + parseFloat(decrypted.n)) { - PrepareOutgoing.runtime.sumPending = PrepareOutgoing.runtime.sumPending + parseFloat(decrypted.n) + if (decrypted.t > PrepareOutgoing.runtime.currentBlockHeight) { + // don't fail it, just move on to the next one + PrepareOutgoing.runtime.currentPending.splice(0, 1) + PrepareOutgoing.processTransaction() + return + } + if (PrepareOutgoing.runtime.navBalance > PrepareOutgoing.runtime.sumPending + parseFloat(decrypted.v)) { + PrepareOutgoing.runtime.sumPending = PrepareOutgoing.runtime.sumPending + parseFloat(decrypted.v) PrepareOutgoing.runtime.currentBatch.push({ decrypted, transaction }) PrepareOutgoing.runtime.currentPending.splice(0, 1) PrepareOutgoing.processTransaction() return } + // max possible nav to send reached // @TODO possibly continue to loop through the rest of the transactions to see if any smaller ones can jump ahead PrepareOutgoing.runtime.callback(true, { diff --git a/src/lib/ProcessIncoming.js b/src/lib/ProcessIncoming.js index ac2a7aa..1e49a36 100644 --- a/src/lib/ProcessIncoming.js +++ b/src/lib/ProcessIncoming.js @@ -2,14 +2,13 @@ const lodash = require('lodash') const ursa = require('ursa') let Logger = require('./Logger.js') // eslint-disable-line -let EncryptedData = require('./EncryptedData.js') // eslint-disable-line -const privateSettings = require('../settings/private.settings.json') +let privateSettings = require('../settings/private.settings.json') // eslint-disable-line let SendToAddress = require('./SendToAddress.js') // eslint-disable-line const ProcessIncoming = {} ProcessIncoming.run = (options, callback) => { - const required = ['currentBatch', 'settings', 'subClient', 'outgoingPubKey', 'subAddresses', 'navClient'] + const required = ['currentBatch', 'settings', 'subClient', 'outgoingPubKey', 'subAddresses', 'navClient', 'currentFlattened'] if (lodash.intersection(Object.keys(options), required).length !== required.length) { Logger.writeLog('PROI_001', 'invalid options', { options, required }) callback(false, { message: 'invalid options provided to ProcessIncoming.run' }) @@ -18,66 +17,80 @@ ProcessIncoming.run = (options, callback) => { ProcessIncoming.runtime = { callback, currentBatch: options.currentBatch, + remainingTxGroups: options.currentBatch, + currentFlattened: options.currentFlattened, settings: options.settings, subClient: options.subClient, navClient: options.navClient, outgoingPubKey: options.outgoingPubKey, subAddresses: options.subAddresses, - transactionsToReturn: [], - successfulSubTransactions: [], + txGroupsToReturn: [], + successfulTxGroups: [], } - ProcessIncoming.runtime.remainingTransactions = options.currentBatch - ProcessIncoming.processPending() + + ProcessIncoming.runtime.navClient.getBlockCount().then((blockHeight) => { + ProcessIncoming.runtime.currentBlockHeight = blockHeight + ProcessIncoming.processPending() + }).catch((err) => { + Logger.writeLog('PROI_001A', 'failed to get the current blockheight', { error: err }) + callback(false, { message: 'failed to get the current blockheight' }) + return + }) } ProcessIncoming.processPending = () => { - if (ProcessIncoming.runtime.remainingTransactions.length < 1) { + if (lodash.size(ProcessIncoming.runtime.remainingTxGroups) < 1) { ProcessIncoming.runtime.callback(true, { - successfulSubTransactions: ProcessIncoming.runtime.successfulSubTransactions, - transactionsToReturn: ProcessIncoming.runtime.transactionsToReturn, + successfulTxGroups: ProcessIncoming.runtime.successfulTxGroups, + txGroupsToReturn: ProcessIncoming.runtime.txGroupsToReturn, }) return } - EncryptedData.getEncrypted({ - transaction: ProcessIncoming.runtime.remainingTransactions[0], - client: ProcessIncoming.runtime.navClient, - }, ProcessIncoming.checkDecrypted) - return -} -ProcessIncoming.transactionFailed = () => { - ProcessIncoming.runtime.transactionsToReturn.push(ProcessIncoming.runtime.remainingTransactions[0]) - ProcessIncoming.runtime.remainingTransactions.splice(0, 1) - ProcessIncoming.processPending() + const currentTxGroup = ProcessIncoming.runtime.remainingTxGroups[0] + + ProcessIncoming.runtime.remainingFlattened = ProcessIncoming.runtime.currentFlattened[currentTxGroup.unique] + ProcessIncoming.runtime.destination = currentTxGroup.destination + ProcessIncoming.runtime.maxDelay = currentTxGroup.timeDelay + ProcessIncoming.processPartial() } -ProcessIncoming.checkDecrypted = (success, data) => { - if (!success || !data || !data.decrypted || !data.transaction) { - Logger.writeLog('PROI_002', 'failed to decrypt transaction data', { success }) - ProcessIncoming.transactionFailed() +ProcessIncoming.processPartial = () => { + if (ProcessIncoming.runtime.remainingFlattened.length < 1) { + ProcessIncoming.runtime.successfulTxGroups.push(ProcessIncoming.runtime.remainingTxGroups[0]) + ProcessIncoming.runtime.remainingTxGroups.splice(0, 1) + ProcessIncoming.processPending() return } - ProcessIncoming.runtime.navClient.validateAddress(data.decrypted).then((addressInfo) => { - if (addressInfo.isvalid !== true) { - Logger.writeLog('PROI_003', 'encrypted address invalid', { success, data }) - ProcessIncoming.transactionFailed() - return - } - ProcessIncoming.reEncryptAddress(data.decrypted, data.transaction, 0) - }).catch((err) => { - Logger.writeLog('PROI_004', 'failed to decrypt transaction data', { success, error: err }) - ProcessIncoming.transactionFailed() + ProcessIncoming.reEncryptAddress( + ProcessIncoming.runtime.destination, + ProcessIncoming.runtime.maxDelay, + ProcessIncoming.runtime.remainingTxGroups[0], + ProcessIncoming.runtime.remainingFlattened[0], + 0 + ) +} + +ProcessIncoming.partialFailed = (txGroup) => { + const currentTxGroup = ProcessIncoming.runtime.remainingTxGroups[0] + if (ProcessIncoming.runtime.currentFlattened[currentTxGroup.unique].length > ProcessIncoming.runtime.remainingFlattened.length) { + Logger.writeLog('PROI_009', 'partial subchain transaction failure', { txGroup, runtime: ProcessIncoming.runtime }) + ProcessIncoming.runtime.callback(false, { partialFailure: true }) return - }) + } + Logger.writeLog('PROI_009A', 'complete group failure', { txGroup, runtime: ProcessIncoming.runtime }) + ProcessIncoming.runtime.txGroupsToReturn.push(currentTxGroup) + ProcessIncoming.runtime.remainingTxGroups.splice(0, 1) + ProcessIncoming.processPending() } -ProcessIncoming.reEncryptAddress = (decryptedAddress, transaction, counter) => { +ProcessIncoming.reEncryptAddress = (destination, maxDelay, txGroup, flattened, counter) => { try { - const newAmount = transaction.amount - (transaction.amount * ProcessIncoming.runtime.settings.anonFeePercent / 100) const dataToEncrypt = { - a: decryptedAddress, - n: newAmount, - s: ProcessIncoming.runtime.settings.secret, + n: destination, // nav + v: flattened, // value + s: ProcessIncoming.runtime.settings.secret, // secret + t: ProcessIncoming.runtime.currentBlockHeight + Math.round(Math.random() * maxDelay), // time } const encrypted = ProcessIncoming.runtime.outgoingPubKey.encrypt( @@ -85,21 +98,21 @@ ProcessIncoming.reEncryptAddress = (decryptedAddress, transaction, counter) => { ) if (encrypted.length !== privateSettings.encryptionOutput.OUTGOING && counter < privateSettings.maxEncryptionAttempts) { - Logger.writeLog('PROI_005', 'public key encryption failed', { transaction, counter, encrypted }) - ProcessIncoming.reEncryptAddress = (decryptedAddress, transaction, counter + 1) + Logger.writeLog('PROI_005', 'public key encryption failed', { maxDelay, txGroup, flattened, counter, encrypted }) + ProcessIncoming.reEncryptAddress = (destination, maxDelay, txGroup, flattened, counter + 1) return } if (encrypted.length !== privateSettings.encryptionOutput.OUTGOING && counter >= privateSettings.maxEncryptionAttempts) { - Logger.writeLog('PROI_006', 'max public key encryption failures', { transaction, counter, encrypted }) - ProcessIncoming.transactionFailed() + Logger.writeLog('PROI_006', 'max public key encryption failures', { txGroup, counter, encrypted }) + ProcessIncoming.partialFailed(txGroup) return } - ProcessIncoming.makeSubchainTx(encrypted, transaction) + ProcessIncoming.makeSubchainTx(encrypted, txGroup) } catch (err) { - Logger.writeLog('PROI_007', 'encrypted address invalid', { transaction, error: err }) - ProcessIncoming.transactionFailed() + Logger.writeLog('PROI_007', 'encrypted address invalid', { txGroup, error: err }) + ProcessIncoming.partialFailed(txGroup) return } } @@ -117,13 +130,12 @@ ProcessIncoming.makeSubchainTx = (encrypted, transaction) => { ProcessIncoming.sentSubToOutgoing = (success, data) => { if (!success || !data || !data.sendOutcome) { Logger.writeLog('PROI_008', 'failed subClient send to address', { transaction: data.transaction, error: data.error }) - ProcessIncoming.transactionFailed() + ProcessIncoming.partialFailed() return } - ProcessIncoming.runtime.successfulSubTransactions.push(data.transaction) ProcessIncoming.runtime.subAddresses.splice(0, 1) - ProcessIncoming.runtime.remainingTransactions.splice(0, 1) - ProcessIncoming.processPending() + ProcessIncoming.runtime.remainingFlattened.splice(0, 1) + ProcessIncoming.processPartial() } module.exports = ProcessIncoming diff --git a/src/lib/ProcessOutgoing.js b/src/lib/ProcessOutgoing.js index 65f9bdc..c27cb3e 100644 --- a/src/lib/ProcessOutgoing.js +++ b/src/lib/ProcessOutgoing.js @@ -33,13 +33,15 @@ ProcessOutgoing.processPending = () => { }) return } - ProcessOutgoing.runtime.partialTransactions = [] - RandomizeTransactions.outgoing({ + + // ProcessOutgoing.mockSend() + + SendToAddress.send({ + client: ProcessOutgoing.runtime.navClient, + address: ProcessOutgoing.runtime.remainingTransactions[0].decrypted.n, + amount: ProcessOutgoing.runtime.remainingTransactions[0].decrypted.v, transaction: ProcessOutgoing.runtime.remainingTransactions[0], - amount: ProcessOutgoing.runtime.remainingTransactions[0].decrypted.n, - address: ProcessOutgoing.runtime.remainingTransactions[0].decrypted.a, - }, ProcessOutgoing.amountsRandomized) - return + }, ProcessOutgoing.sentNav) } ProcessOutgoing.transactionFailed = () => { @@ -48,17 +50,6 @@ ProcessOutgoing.transactionFailed = () => { ProcessOutgoing.processPending() } -ProcessOutgoing.amountsRandomized = (success, data) => { - if (!success || !data) { - Logger.writeLog('PROO_002', 'failed to randomize transaction', { success, data }, true) - ProcessOutgoing.transactionFailed() - return - } - ProcessOutgoing.runtime.partialTransactions = data.partialTransactions - ProcessOutgoing.createNavTransactions() - // ProcessOutgoing.mockSend() -} - ProcessOutgoing.mockSend = () => { Logger.writeLog('PROO_003A', 'mock nav sent', { transaction: ProcessOutgoing.runtime.remainingTransactions[0] }) ProcessOutgoing.runtime.successfulTransactions.push({ @@ -68,39 +59,15 @@ ProcessOutgoing.mockSend = () => { ProcessOutgoing.processPending() } -ProcessOutgoing.createNavTransactions = () => { - if (ProcessOutgoing.runtime.partialTransactions.length < 1) { - Logger.writeLog('PROO_003', 'all partial nav sent', { - transaction: ProcessOutgoing.runtime.remainingTransactions[0].transaction, - }) - ProcessOutgoing.runtime.successfulTransactions.push({ - transaction: ProcessOutgoing.runtime.remainingTransactions[0].transaction, - }) - ProcessOutgoing.runtime.remainingTransactions.splice(0, 1) - ProcessOutgoing.processPending() - return - } - - SendToAddress.send({ - client: ProcessOutgoing.runtime.navClient, - address: ProcessOutgoing.runtime.remainingTransactions[0].decrypted.a, - amount: ProcessOutgoing.runtime.partialTransactions[0], - transaction: ProcessOutgoing.runtime.remainingTransactions[0], - }, ProcessOutgoing.sentPartialNav) -} - -ProcessOutgoing.sentPartialNav = (success, data) => { +ProcessOutgoing.sentNav = (success, data) => { if (!success || !data || !data.sendOutcome) { Logger.writeLog('PROO_004', 'failed nav send to address', data, true) - ProcessOutgoing.runtime.callback(false, { - message: 'failed sending partial transaction to address', - failedTransaction: ProcessOutgoing.runtime.remainingTransactions[0], - remainingPartials: ProcessOutgoing.runtime.partialTransactions, - }) - return + ProcessOutgoing.runtime.failedTransactions.push(ProcessOutgoing.runtime.remainingTransactions[0]) + } else { + ProcessOutgoing.runtime.successfulTransactions.push(ProcessOutgoing.runtime.remainingTransactions[0]) } - ProcessOutgoing.runtime.partialTransactions.splice(0, 1) - ProcessOutgoing.createNavTransactions() + ProcessOutgoing.runtime.remainingTransactions.splice(0, 1) + ProcessOutgoing.processPending() return } diff --git a/src/lib/RefillOutgoing.js b/src/lib/RefillOutgoing.js index 029e1d6..b98ba0f 100644 --- a/src/lib/RefillOutgoing.js +++ b/src/lib/RefillOutgoing.js @@ -88,7 +88,14 @@ RefillOutgoing.holdingDecrypted = (success, data) => { RefillOutgoing.runtime.holdingTransaction = data.transaction - const addresses = JSON.parse(data.decrypted) // @TODO try, catch this + if (data.decrypted.constructor !== Array) { + Logger.writeLog('RFL_007A', 'decrypted data not an array', { currentHolding: RefillOutgoing.runtime.currentHolding }) + RefillOutgoing.runtime.currentHolding.splice(0, 1) + RefillOutgoing.processHolding() + return + } + + const addresses = data.decrypted.slice(0) if (addresses.constructor !== Array) { Logger.writeLog('RFL_007A', 'decrypted data not an array of addresses', { currentHolding: RefillOutgoing.runtime.currentHolding }) diff --git a/src/lib/RetrieveSubchainAddresses.js b/src/lib/RetrieveSubchainAddresses.js index 3374bc6..5943b46 100644 --- a/src/lib/RetrieveSubchainAddresses.js +++ b/src/lib/RetrieveSubchainAddresses.js @@ -7,7 +7,7 @@ let NavCoin = require('./NavCoin.js') //eslint-disable-line const RetrieveSubchainAddresses = {} RetrieveSubchainAddresses.run = (options, callback) => { - const required = ['subClient', 'chosenOutgoing', 'currentBatch'] + const required = ['subClient', 'chosenOutgoing', 'numAddresses'] if (lodash.intersection(Object.keys(options), required).length !== required.length) { Logger.writeLog('RSC_001', 'invalid options', { options, required }) callback(false, { message: 'invalid options provided to RetrieveSubchainAddresses.run' }) @@ -17,7 +17,7 @@ RetrieveSubchainAddresses.run = (options, callback) => { callback, subClient: options.subClient, chosenOutgoing: options.chosenOutgoing, - currentBatch: options.currentBatch, + numAddresses: options.numAddresses, } RetrieveSubchainAddresses.getSubAddresses() @@ -37,7 +37,7 @@ RetrieveSubchainAddresses.getSubAddresses = () => { form: { type: 'SUBCHAIN', account: 'OUTGOING', - num_addresses: RetrieveSubchainAddresses.runtime.currentBatch.length, + num_addresses: RetrieveSubchainAddresses.runtime.numAddresses, }, } @@ -85,10 +85,10 @@ RetrieveSubchainAddresses.checkSubAddresses = (outgoingSubAddresses) => { RetrieveSubchainAddresses.runtime.callback(false, { message: 'outgoing server must provide at least one sub address' }) return } - if (outgoingSubAddresses.length < RetrieveSubchainAddresses.runtime.currentBatch.length) { + if (outgoingSubAddresses.length < RetrieveSubchainAddresses.runtime.numAddresses) { Logger.writeLog('RSC_008', 'outgoing server did not provide enough sub addresses', { subAddressesLength: outgoingSubAddresses.length, - currentBatchLength: RetrieveSubchainAddresses.runtime.currentBatch.length, + numAddresses: RetrieveSubchainAddresses.runtime.numAddresses, }) RetrieveSubchainAddresses.runtime.callback(false, { message: 'outgoing server did not provide enough sub addresses' }) return diff --git a/src/lib/SelectOutgoing.js b/src/lib/SelectOutgoing.js index 9f2b519..93ab8a6 100644 --- a/src/lib/SelectOutgoing.js +++ b/src/lib/SelectOutgoing.js @@ -37,7 +37,7 @@ SelectOutgoing.run = (options, callback) => { SelectOutgoing.pickServer = () => { if (SelectOutgoing.runtime.remoteCluster.length < 1) { Logger.writeLog('SEL_003', 'no valid outgoing servers found') - SelectOutgoing.runtime.callback(false, { returnAllToSenders: true }) + SelectOutgoing.runtime.callback(false, { returnAllToSenders: true, pause: false }) return } diff --git a/src/lib/SendRawTransaction.js b/src/lib/SendRawTransaction.js index e7c5d29..8955040 100644 --- a/src/lib/SendRawTransaction.js +++ b/src/lib/SendRawTransaction.js @@ -6,7 +6,7 @@ const lodash = require('lodash') let Logger = require('./Logger.js') //eslint-disable-line let NavCoin = require('./NavCoin.js') //eslint-disable-line -const globalSettings = config.get('GLOBAL') +let globalSettings = config.get('GLOBAL') //eslint-disable-line let settings = false if (globalSettings.serverType === 'INCOMING') settings = config.get('INCOMING') @@ -22,93 +22,112 @@ SendRawTransaction.createRaw = (options, callback) => { return } - SendRawTransaction.runtime = {} // wipe the runtime variables - if (options.encrypted) { - options.client.createRawTransaction(options.spentTransactions, options.outgoingTransactions, options.encrypted).then((rawTrans) => { - SendRawTransaction.signRaw({ - client: options.client, - rawTrans, - }, callback) + SendRawTransaction.runtime = { + counter: 0, + retryDelay: 6000, + spentTransactions: options.spentTransactions, + outgoingTransactions: options.outgoingTransactions, + client: options.client, + encrypted: options.encrypted || false, + callback, + } // wipe runtime variables + SendRawTransaction.create() +} + +SendRawTransaction.create = () => { + if (SendRawTransaction.runtime.encrypted) { + SendRawTransaction.runtime.client.createRawTransaction( + SendRawTransaction.runtime.spentTransactions, + SendRawTransaction.runtime.outgoingTransactions, + SendRawTransaction.runtime.encrypted).then((rawTrans) => { + SendRawTransaction.signRaw(rawTrans) return }).catch((err) => { Logger.writeLog('RAW_002', 'unable to create raw transaction', { - spentTransactions: options.spentTransactions, - outgoingTransactions: options.outgoingTransactions, - encrypted: options.encrypted, + spentTransactions: SendRawTransaction.runtime.spentTransactions, + outgoingTransactions: SendRawTransaction.runtime.outgoingTransactions, + encrypted: SendRawTransaction.runtime.encrypted, error: err, }) - callback(false, { error: err }) + SendRawTransaction.retry(err) // try again }) } else { - options.client.createRawTransaction(options.spentTransactions, options.outgoingTransactions).then((rawTrans) => { - SendRawTransaction.signRaw({ - client: options.client, - rawTrans, - }, callback) + SendRawTransaction.runtime.client.createRawTransaction( + SendRawTransaction.runtime.spentTransactions, + SendRawTransaction.runtime.outgoingTransactions).then((rawTrans) => { + SendRawTransaction.signRaw(rawTrans) return }).catch((err) => { Logger.writeLog('RAW_003', 'unable to create raw transaction', { - spentTransactions: options.spentTransactions, - outgoingTransactions: options.outgoingTransactions, + spentTransactions: SendRawTransaction.runtime.spentTransactions, + outgoingTransactions: SendRawTransaction.runtime.outgoingTransactions, error: err, }) - callback(false, { error: err }) + SendRawTransaction.retry(err) // try again }) } } -SendRawTransaction.signRaw = (options, callback) => { - options.client.signRawTransaction(options.rawTrans).then((signedRaw) => { - SendRawTransaction.sendRaw({ - client: options.client, - signedRaw, - }, callback) +SendRawTransaction.signRaw = (rawTrans) => { + SendRawTransaction.runtime.client.signRawTransaction(rawTrans).then((signedRaw) => { + SendRawTransaction.sendRaw(signedRaw) return }).catch((err) => { - if (err.code === -13 && !options.triedToUnlock) { - SendRawTransaction.runtime.options = options - SendRawTransaction.runtime.callback = callback - const type = (options.client.port === settings.navCoin.port) ? 'navCoin' : 'subChain' - NavCoin.unlockWallet({ settings, client: options.client, type }, SendRawTransaction.walletUnlocked) + if (err.code === -13 && !SendRawTransaction.runtime.triedToUnlock) { + SendRawTransaction.runtime.rawTrans = rawTrans + const type = (SendRawTransaction.runtime.client.port === settings.navCoin.port) ? 'navCoin' : 'subChain' + NavCoin.unlockWallet({ settings, client: SendRawTransaction.runtime.client, type }, SendRawTransaction.walletUnlocked) return } Logger.writeLog('RAW_004', 'unable to sign raw transaction', { - rawTrans: options.rawTrans, + rawTrans, error: err, }) - callback(false, { error: err }) + SendRawTransaction.retry(err) // try again }) } -SendRawTransaction.walletUnlocked = (success, data) => { +SendRawTransaction.walletUnlocked = (success, data) => { //@TODO if (!success) { Logger.writeLog('RAW_006', 'unable to unlock wallet', { success, data }) SendRawTransaction.runtime.callback(false, data) return } - const options = { - client: SendRawTransaction.runtime.options.client, - rawTrans: SendRawTransaction.runtime.options.rawTrans, - triedToUnlock: true, - } - SendRawTransaction.signRaw(options, SendRawTransaction.runtime.callback) + SendRawTransaction.runtime.triedToUnlock = true + SendRawTransaction.signRaw(SendRawTransaction.runtime.rawTrans) } -SendRawTransaction.sendRaw = (options, callback) => { +SendRawTransaction.sendRaw = (signedRaw) => { if (globalSettings.preventSend) { - Logger.writeLog('RAW_TEST_001', 'preventSend triggered', { options }) - callback(true, { rawOutcome: 'dummy-tx-id' }) + Logger.writeLog('RAW_TEST_001', 'preventSend triggered', { runtime: SendRawTransaction.runtime }) + SendRawTransaction.runtime.callback(true, { rawOutcome: 'dummy-tx-id' }) return } - options.client.sendRawTransaction(options.signedRaw.hex).then((rawOutcome) => { - callback(true, { rawOutcome }) + SendRawTransaction.runtime.client.sendRawTransaction(signedRaw.hex).then((rawOutcome) => { + SendRawTransaction.runtime.callback(true, { rawOutcome }) }).catch((err) => { Logger.writeLog('RAW_005', 'unable to send raw transaction', { - signedRaw: options.signedRaw, + signedRaw: signedRaw, error: err, }) - callback(false, { error: err }) + SendRawTransaction.retry(err) // try again }) } +SendRawTransaction.retry = (err) => { + if (SendRawTransaction.runtime.counter >= 10) { + SendRawTransaction.runtime.callback(false, { error: err }) + return + } else { + Logger.writeLog('RAW_007', 'retrying', { + error: err, + counter: SendRawTransaction.runtime.counter, + }) + setTimeout(() => { + SendRawTransaction.runtime.counter += 1 + SendRawTransaction.create() + }, SendRawTransaction.runtime.retryDelay) //6 second delay & try again + } +} + module.exports = SendRawTransaction diff --git a/src/lib/SendToAddress.js b/src/lib/SendToAddress.js index 4752c9d..f7d680b 100644 --- a/src/lib/SendToAddress.js +++ b/src/lib/SendToAddress.js @@ -6,7 +6,7 @@ const lodash = require('lodash') let Logger = require('./Logger.js') //eslint-disable-line let NavCoin = require('./NavCoin.js') //eslint-disable-line -const globalSettings = config.get('GLOBAL') +let globalSettings = config.get('GLOBAL') //eslint-disable-line let settings = false if (globalSettings.serverType === 'INCOMING') settings = config.get('INCOMING') diff --git a/src/lib/SettingsValidator.js b/src/lib/SettingsValidator.js index 1f428a6..0bb55b0 100644 --- a/src/lib/SettingsValidator.js +++ b/src/lib/SettingsValidator.js @@ -83,8 +83,6 @@ function eachField(value, ignoreList, validation, currentKey) { return } - if (currentKey === 'remote') console.log(currentKey, value) - switch (validation.type) { case 'DOMAIN': validateDomain(value, validation, currentKey) diff --git a/src/lib/SpendToHolding.js b/src/lib/SpendToHolding.js index da11619..cc5d80f 100644 --- a/src/lib/SpendToHolding.js +++ b/src/lib/SpendToHolding.js @@ -1,9 +1,9 @@ const lodash = require('lodash') -const Logger = require('./Logger.js') -const RandomizeTransactions = require('./RandomizeTransactions.js') -const privateSettings = require('../settings/private.settings.json') -const SendRawTransaction = require('./SendRawTransaction.js') +let Logger = require('./Logger.js') //eslint-disable-line +let RandomizeTransactions = require('./RandomizeTransactions.js') //eslint-disable-line +let privateSettings = require('../settings/private.settings.json') //eslint-disable-line +let SendRawTransaction = require('./SendRawTransaction.js') //eslint-disable-line const SpendToHolding = {} diff --git a/src/outgoing.js b/src/outgoing.js index f67460a..2a3ab95 100644 --- a/src/outgoing.js +++ b/src/outgoing.js @@ -2,17 +2,17 @@ const Client = require('bitcoin-core') -const Logger = require('./lib/Logger.js') -const EncryptionKeys = require('./lib/EncryptionKeys.js') -const PreFlight = require('./lib/PreFlight.js') -const PrepareOutgoing = require('./lib/PrepareOutgoing.js') -const ProcessOutgoing = require('./lib/ProcessOutgoing.js') -const PayoutFee = require('./lib/PayoutFee') -const ReturnSubnav = require('./lib/ReturnSubnav') +let Logger = require('./lib/Logger.js') //eslint-disable-line +let EncryptionKeys = require('./lib/EncryptionKeys.js') //eslint-disable-line +let PreFlight = require('./lib/PreFlight.js') //eslint-disable-line +let PrepareOutgoing = require('./lib/PrepareOutgoing.js') //eslint-disable-line +let ProcessOutgoing = require('./lib/ProcessOutgoing.js') //eslint-disable-line +let PayoutFee = require('./lib/PayoutFee') //eslint-disable-line +let ReturnSubnav = require('./lib/ReturnSubnav') //eslint-disable-line const config = require('config') -const settings = config.get('OUTGOING') +let settings = config.get('OUTGOING') //eslint-disable-line // -------------- RUN OUTGOING SERVER ------------------------------------------ @@ -41,7 +41,7 @@ OutgoingServer.init = () => { Logger.writeLog('OUT_000', 'server starting') EncryptionKeys.findKeysToRemove({ type: 'private' }, OutgoingServer.startProcessing) - setInterval(() => { + OutgoingServer.cron = setInterval(() => { if (OutgoingServer.paused === false) { EncryptionKeys.findKeysToRemove({ type: 'private' }, OutgoingServer.startProcessing) } else { @@ -73,16 +73,36 @@ OutgoingServer.preFlightComplete = (success, data) => { } OutgoingServer.runtime.navBalance = data.navBalance OutgoingServer.runtime.subBalance = data.subBalance - PrepareOutgoing.run({ + PayoutFee.run({ navClient: OutgoingServer.navClient, - subClient: OutgoingServer.subClient, - navBalance: data.navBalance, settings, - }, OutgoingServer.currentBatchPrepared) + }, OutgoingServer.feePaid) } -OutgoingServer.currentBatchPrepared = (success, data) => { +OutgoingServer.feePaid = (success, data) => { if (!success) { + Logger.writeLog('OUT_006', 'failed nav send to txfee address', { data, success }) + } + OutgoingServer.navClient.getBalance().then((navBalance) => { + OutgoingServer.runtime.navBalance = navBalance + PrepareOutgoing.run({ + navClient: OutgoingServer.navClient, + subClient: OutgoingServer.subClient, + navBalance, + settings, + }, OutgoingServer.currentBatchPrepared) + }).catch((err) => { + Logger.writeLog('OUT_006A', 'failed nav send to getbalance after sending tx-fee', { data, err }) + OutgoingServer.processing = false + return + }) +} + +OutgoingServer.currentBatchPrepared = (success, data) => { + if (!success || !data || !data.currentBatch || data.currentBatch.length === 0) { + if (data.failedSubTransactions && data.failedSubTransactions.length > 0) { + Logger.writeLog('OUT_003A', 'failed to prepare some subtransactions', { success, data }, true) + } OutgoingServer.processing = false return } @@ -112,22 +132,12 @@ OutgoingServer.transactionsProcessed = (success, data) => { return } - OutgoingServer.runtime.successfulTransactions = data.successfulTransactions - - PayoutFee.run({ - navClient: OutgoingServer.navClient, - settings, - }, OutgoingServer.feePaid) -} - -OutgoingServer.feePaid = (success, data) => { - if (!success) { - Logger.writeLog('OUT_006', 'failed nav send to txfee address', { - transaction: data.transaction, - error: data.error, - }, true) + if (data.failedTransactions && data.failedTransactions.length > 0) { + Logger.writeLog('OUT_005A', 'failed to send send some transactions', { success, data }, true) } + OutgoingServer.runtime.successfulTransactions = data.successfulTransactions + ReturnSubnav.run({ transactions: OutgoingServer.runtime.successfulTransactions, subClient: OutgoingServer.subClient, @@ -135,10 +145,11 @@ OutgoingServer.feePaid = (success, data) => { }, OutgoingServer.subnavReturned) } -OutgoingServer.subnavReturned = (success) => { +OutgoingServer.subnavReturned = (success, data) => { if (!success) { Logger.writeLog('OUT_007', 'unable to return subnav to incoming server', { transactions: OutgoingServer.runtime.successfulTransactions, + data, }, true) OutgoingServer.paused = true OutgoingServer.processing = false diff --git a/src/settings/private.settings.json b/src/settings/private.settings.json index 544431f..a2925b4 100644 --- a/src/settings/private.settings.json +++ b/src/settings/private.settings.json @@ -1,4 +1,9 @@ { + "version": { + "major": 1, + "minor": 2, + "patch": 0 + }, "keyFolders": { "private": { "path": "./keys/private/", @@ -16,11 +21,11 @@ "OUTGOING": "outgoingAccount", "HOLDING": "holdingAccount" }, - "maxAddresses": 250, - "maxHolding": 100, + "maxAddresses": 1000, + "maxHolding": 200, "minNavTransactions": 3, "maxNavTransactions": 8, - "txFee": 0.001, + "txFee": 0.0001, "maxEncryptionAttempts": 10, "encryptionStrength": { "INCOMING": 2048, @@ -30,9 +35,11 @@ "INCOMING": 344, "OUTGOING": 172 }, - "subCoinsPerTx": 10, - "subChainTxFee": 0.001, + "subCoinsPerTx": 1, + "subChainTxFee": 0.0001, "minConfs": 1, + "maxConfs": 60, + "maxQueue": 120, "blockThreshold": { "checking": 5, "processing": 3 diff --git a/src/setup.js b/src/setup.js index 177b40c..17a361d 100644 --- a/src/setup.js +++ b/src/setup.js @@ -236,7 +236,7 @@ const createSecret = (holdingSuccess) => { if (globalSettings.serverType === 'INCOMING') { bcrypt.hash(settings.secretOptions.salt, settings.secretOptions.saltRounds, (err, hash) => { - console.log('STATUS: genearted secret: ', hash.substring(0, 42)) + console.log('STATUS: genearted secret: ', hash.substring(0, 32)) console.log('SUCCESS: everything is configured') }) } else { diff --git a/src/validators/incoming.validation.json b/src/validators/incoming.validation.json index 1a7b9ff..8b8e4ae 100644 --- a/src/validators/incoming.validation.json +++ b/src/validators/incoming.validation.json @@ -31,7 +31,7 @@ "minAmount": { "required": true, "type": "INT", - "min": 10 + "min": 1 }, "maxAmount": { "required": true, @@ -129,6 +129,6 @@ "secret": { "required": true, "type": "STRING", - "length": 42 + "length": 32 } } diff --git a/src/validators/outgoing.validation.json b/src/validators/outgoing.validation.json index fa67a92..615fffc 100644 --- a/src/validators/outgoing.validation.json +++ b/src/validators/outgoing.validation.json @@ -31,7 +31,7 @@ "minAmount": { "required": true, "type": "INT", - "min": 10 + "min": 1 }, "maxAmount": { "required": true, @@ -123,6 +123,6 @@ "secret": { "required": true, "type": "STRING", - "length": 42 + "length": 32 } } diff --git a/test/AddressGenerator.spec.js b/test/AddressGenerator.spec.js index 6b196bb..35736e3 100644 --- a/test/AddressGenerator.spec.js +++ b/test/AddressGenerator.spec.js @@ -83,7 +83,7 @@ describe('[AddressGenerator]', () => { }) }) describe('(runKeypoolRefill)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions AddressGenerator = rewire('../src/lib/AddressGenerator') }) it('should fail to refill the keypool', (done) => { @@ -129,7 +129,7 @@ describe('[AddressGenerator]', () => { }) }) describe('(getAccountAddressesForGeneration)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions AddressGenerator = rewire('../src/lib/AddressGenerator') }) it('fail client.getAddressesByAccount with error 12', (done) => { @@ -217,7 +217,7 @@ describe('[AddressGenerator]', () => { }) }) describe('(generateNewAccountAddresses)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions AddressGenerator = rewire('../src/lib/AddressGenerator') }) it('should fail client.getNewAddress with error 12', (done) => { diff --git a/test/EncryptedData.spec.js b/test/EncryptedData.spec.js index 3e6c9db..e376e17 100644 --- a/test/EncryptedData.spec.js +++ b/test/EncryptedData.spec.js @@ -33,7 +33,7 @@ const ursaMock = { createPrivateKey: () => { return { decrypt: () => { - return 'DECRYPTED_MESSAGE' + return '{"n": "XYZ", "u": "1234", "p": "1", "o": "3", "t": 20}' }, } }, @@ -89,7 +89,7 @@ describe('[EncryptedData]', () => { }) }) describe('(decryptData)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions EncryptedData = rewire('../src/lib/EncryptedData') }) it('should fail on params', (done) => { @@ -104,7 +104,6 @@ describe('[EncryptedData]', () => { const callback = (success, data) => { expect(success).toBe(false) expect(data.message).toBeA('string') - console.log('MESSAGE: ', data.message) done() } EncryptedData.decryptData({ encryptedData: '1234' }, callback) @@ -125,7 +124,6 @@ describe('[EncryptedData]', () => { const callback = (success, data) => { expect(success).toBe(false) expect(data.message).toBeA('string') - console.log('MESSAGE: ', data.message) done() } EncryptedData.decryptData({ encryptedData: '1234' }, callback) @@ -136,7 +134,7 @@ describe('[EncryptedData]', () => { EncryptedData.__set__('ursa', ursaMock) const callback = (success, data) => { expect(success).toBe(true) - expect(data.decrypted).toBe('DECRYPTED_MESSAGE') + expect(data.decrypted).toEqual({ n: 'XYZ', u: '1234', p: '1', o: '3', t: 20 }) done() } EncryptedData.decryptData({ encryptedData: '1234' }, callback) diff --git a/test/EncryptionKeys.spec.js b/test/EncryptionKeys.spec.js index c75a78c..07e018d 100644 --- a/test/EncryptionKeys.spec.js +++ b/test/EncryptionKeys.spec.js @@ -143,14 +143,13 @@ describe('[EncryptionKeys]', () => { EncryptionKeys.__set__('ursa', ursaMock) const callback = (success, data) => { expect(success).toBe(true) - console.log(data) done() } EncryptionKeys.testKeyPair({ privKeyFile, pubKeyFile }, callback) }) }) describe('(getEncryptionKeys)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions EncryptionKeys = rewire('../src/lib/EncryptionKeys') }) it('should find the keys and return them', (done) => { @@ -161,7 +160,6 @@ describe('[EncryptionKeys]', () => { expect(success).toBe(true) expect(data.privKeyFile).toEqual(privKeyFile) expect(data.pubKeyFile).toEqual(pubKeyFile) - console.log(data) done() } EncryptionKeys.getEncryptionKeys({}, callback) @@ -181,7 +179,7 @@ describe('[EncryptionKeys]', () => { }) }) describe('(generateKeys)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions EncryptionKeys = rewire('../src/lib/EncryptionKeys') }) it('should fail on params', (done) => { @@ -225,7 +223,7 @@ describe('[EncryptionKeys]', () => { }) }) describe('(findKeysToRemove)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions EncryptionKeys = rewire('../src/lib/EncryptionKeys') }) it('should fail on params', (done) => { diff --git a/test/FlattenTransactions.spec.js b/test/FlattenTransactions.spec.js new file mode 100644 index 0000000..398cef8 --- /dev/null +++ b/test/FlattenTransactions.spec.js @@ -0,0 +1,195 @@ +'use strict' + +const expect = require('expect') +const rewire = require('rewire') +const sinon = require('sinon') + +const FlattenTransactions = rewire('../src/lib/FlattenTransactions') + +describe('[FlattenTransactions]', () => { + describe('(incoming)', () => { + it('should fail on params', (done) => { + const callback = (success) => { + expect(success).toBe(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'FLT_001') + done() + } + const mockLogger = { + writeLog: sinon.spy(), + } + FlattenTransactions.__set__('Logger', mockLogger) + FlattenTransactions.incoming({ + memes: 'HARAMBE', + }, callback) + }) + it('should flatten transactions 1126.65', (done) => { + const amount = 1126.65 + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.flattened.length).toBe(5) + expect(data.flattened[0]).toBe(1000) + expect(data.flattened[1]).toBe(100) + expect(data.flattened[2]).toBe(10) + expect(data.flattened[3]).toBe(10) + expect(data.flattened[4]).toBe(1.04477612) + const reduced = data.flattened.reduce((acc, x) => x + acc) + const safeReduced = Math.round(reduced * 100000000) / 100000000 + const safeExpected = Math.round((amount / 1.005) * 100000000) / 100000000 + expect(safeReduced).toBe(safeExpected) + done() + } + FlattenTransactions.incoming({ + amountToFlatten: amount, + anonFeePercent: 0.5, + }, callback) + }) + it('should flatten transactions 10', (done) => { + const amount = 10 + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.flattened.length).toBe(9) + expect(data.flattened[0]).toBe(1) + expect(data.flattened[8]).toBe(1.95024876) + const reduced = data.flattened.reduce((acc, x) => x + acc) + const safeReduced = Math.round(reduced * 100000000) / 100000000 + const safeExpected = Math.round((amount / 1.005) * 100000000) / 100000000 + expect(safeReduced).toBe(safeExpected) + done() + } + FlattenTransactions.incoming({ + amountToFlatten: amount, + anonFeePercent: 0.5, + }, callback) + }) + it('should flatten transactions 10000', (done) => { + const amount = 10000 + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.flattened.length).toBe(23) + expect(data.flattened[0]).toBe(1000) + expect(data.flattened[9]).toBe(100) + expect(data.flattened[18]).toBe(10) + expect(data.flattened[22]).toBe(10.24875622) + const reduced = data.flattened.reduce((acc, x) => x + acc) + const safeReduced = Math.round(reduced * 100000000) / 100000000 + const safeExpected = Math.round((amount / 1.005) * 100000000) / 100000000 + expect(safeReduced).toBe(safeExpected) + done() + } + FlattenTransactions.incoming({ + amountToFlatten: amount, + anonFeePercent: 0.5, + }, callback) + }) + it('should flatten transactions 100.99999999', (done) => { + const amount = 100.99999999 + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.flattened.length).toBe(10) + expect(data.flattened[0]).toBe(10) + expect(data.flattened[9]).toBe(10.49751243) + const reduced = data.flattened.reduce((acc, x) => x + acc) + const safeReduced = Math.round(reduced * 100000000) / 100000000 + const safeExpected = Math.round((amount / 1.005) * 100000000) / 100000000 + expect(safeReduced).toBe(safeExpected) + done() + } + FlattenTransactions.incoming({ + amountToFlatten: amount, + anonFeePercent: 0.5, + }, callback) + }) + it('should flatten transactions 9999.99999999', (done) => { + const amount = 9999.99999999 + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.flattened.length).toBe(23) + expect(data.flattened[0]).toBe(1000) + expect(data.flattened[9]).toBe(100) + expect(data.flattened[18]).toBe(10) + expect(data.flattened[22]).toBe(10.24875621) + const reduced = data.flattened.reduce((acc, x) => x + acc) + const safeReduced = Math.round(reduced * 100000000) / 100000000 + const safeExpected = Math.round((amount / 1.005) * 100000000) / 100000000 + expect(safeReduced).toBe(safeExpected) + done() + } + FlattenTransactions.incoming({ + amountToFlatten: amount, + anonFeePercent: 0.5, + }, callback) + }) + it('should flatten transactions 333.33333333', (done) => { + const amount = 333.33333333 + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.flattened.length).toBe(7) + expect(data.flattened[0]).toBe(100) + expect(data.flattened[3]).toBe(10) + expect(data.flattened[6]).toBe(1.67495854) + const reduced = data.flattened.reduce((acc, x) => x + acc) + const safeReduced = Math.round(reduced * 100000000) / 100000000 + const safeExpected = Math.round((amount / 1.005) * 100000000) / 100000000 + expect(safeReduced).toBe(safeExpected) + done() + } + FlattenTransactions.incoming({ + amountToFlatten: amount, + anonFeePercent: 0.5, + }, callback) + }) + it('should flatten transactions 51', (done) => { + const amount = 51 + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.flattened.length).toBe(5) + expect(data.flattened[0]).toBe(10) + expect(data.flattened[4]).toBe(10.74626866) + const reduced = data.flattened.reduce((acc, x) => x + acc) + const safeReduced = Math.round(reduced * 100000000) / 100000000 + const safeExpected = Math.round((amount / 1.005) * 100000000) / 100000000 + expect(safeReduced).toBe(safeExpected) + done() + } + FlattenTransactions.incoming({ + amountToFlatten: amount, + anonFeePercent: 0.5, + }, callback) + }) + it('should flatten transactions 201', (done) => { + const amount = 201 + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.flattened.length).toBe(2) + expect(data.flattened[0]).toBe(100) + expect(data.flattened[1]).toBe(100) + const reduced = data.flattened.reduce((acc, x) => x + acc) + const safeReduced = Math.round(reduced * 100000000) / 100000000 + const safeExpected = Math.round((amount / 1.005) * 100000000) / 100000000 + expect(safeReduced).toBe(safeExpected) + done() + } + FlattenTransactions.incoming({ + amountToFlatten: amount, + anonFeePercent: 0.5, + }, callback) + }) + it('should fail to flatten', (done) => { + const callback = (success) => { + expect(success).toBe(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'FLT_002') + done() + } + const mockLogger = { + writeLog: sinon.spy(), + } + FlattenTransactions.__set__('Logger', mockLogger) + FlattenTransactions.incoming({ + amountToFlatten: 'XYZ', + anonFeePercent: 0.5, + }, callback) + }) + }) +}) diff --git a/test/GroupPartials.spec.js b/test/GroupPartials.spec.js new file mode 100644 index 0000000..f5a3d6f --- /dev/null +++ b/test/GroupPartials.spec.js @@ -0,0 +1,824 @@ +'use strict' + +const expect = require('expect') +const rewire = require('rewire') +const sinon = require('sinon') + +let GroupPartials = rewire('../src/lib/GroupPartials') + +describe('[GroupPartials]', () => { + describe('(run)', () => { + beforeEach(() => { + GroupPartials = rewire('../src/lib/GroupPartials') + }) + it('should fail on params', (done) => { + const callback = (success, data) => { + expect(success).toBe(false) + expect(data.message).toBeA('string') + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'GRP_001') + done() + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.run({ junkParam: 1234 }, callback) + }) + it('should receive correct params and call getDecryptedData', (done) => { + GroupPartials.getDecryptedData = () => { + expect(GroupPartials.runtime.client).toEqual(client) + expect(GroupPartials.runtime.currentPending).toEqual(currentPending) + sinon.assert.notCalled(mockLogger.writeLog) + done() + } + const currentPending = [{ txid: 1 }, { txid: 2 }, { txid: 3 }] + const client = { + getBlockCount: () => 10000, + } + const mockLogger = { + writeLog: sinon.spy(), + } + const callback = () => {} + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.run({ currentPending, client }, callback) + }) + }) + describe('(getDecryptedData)', () => { + beforeEach(() => { + GroupPartials = rewire('../src/lib/GroupPartials') + }) + it('it should call checkPartials if there are no more to process', (done) => { + GroupPartials.checkPartials = () => { + expect(GroupPartials.runtime.remainingToDecrypt.length).toBe(0) + sinon.assert.notCalled(mockLogger.writeLog) + done() + } + const currentPending = [{ txid: 'ABC' }, { txid: 'DEF' }, { txid: 'GHI' }] + GroupPartials.runtime = { + client: { + getBlockCount: () => 10000, + }, + currentPending, + remainingToDecrypt: [], + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.getDecryptedData() + }) + it('it should decrypt the current partial if there are more to be processed', (done) => { + const mockEncryptedData = { + getEncrypted: (options, callback) => { + expect(options.transaction).toEqual({ txid: 'ABC' }) + expect(options.client).toEqual(GroupPartials.runtime.client) + expect(callback).toBe(GroupPartials.checkDecrypted) + sinon.assert.notCalled(mockLogger.writeLog) + done() + }, + } + const currentPending = [{ txid: 'ABC' }, { txid: 'DEF' }, { txid: 'GHI' }] + GroupPartials.runtime = { + client: { + getBlockCount: () => 10000, + }, + currentPending, + remainingToDecrypt: currentPending, + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.__set__('EncryptedData', mockEncryptedData) + GroupPartials.getDecryptedData() + }) + }) + describe('(checkDecrypted)', () => { + beforeEach(() => { + GroupPartials = rewire('../src/lib/GroupPartials') + }) + it('should move to the next transaction if decryption failed (returned false)', (done) => { + const currentPending = [{ txid: 'ABC' }, { txid: 'DEF' }, { txid: 'GHI' }] + GroupPartials.partialFailed = (transaction) => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'GRP_002') + expect(transaction).toEqual(currentPending[0]) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkDecrypted(false) + }) + it('should move to the next transaction if decryption failed (returned bad params)', (done) => { + const currentPending = [{ txid: 'ABC' }, { txid: 'DEF' }, { txid: 'GHI' }] + GroupPartials.partialFailed = (transaction) => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'GRP_002') + expect(transaction).toEqual(currentPending[0]) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkDecrypted(true, { junkParam: true }) + }) + it('should move to the next transaction if decryption failed (returned no transaction data)', (done) => { + const currentPending = [{ txid: 'ABC' }, { txid: 'DEF' }, { txid: 'GHI' }] + GroupPartials.partialFailed = (transaction) => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'GRP_002') + expect(transaction).toEqual(currentPending[0]) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkDecrypted(true, { decrypted: true }) + }) + it('should move to the next transaction if the decrypted data didnt contain the right params', (done) => { + const currentPending = [ + { txid: 'ABC', amount: 100 }, + { txid: 'DEF', amount: 100 }, + { txid: 'GHI', amount: 100 }, + ] + GroupPartials.partialFailed = (transaction) => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'GRP_003') + expect(transaction).toEqual(currentPending[0]) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkDecrypted(true, { + decrypted: { junkParam: true }, + transaction: { txid: 'ABC', amount: 100 }, + }) + }) + it('should move to the next transaction if the decrypted data didnt contain all the needed params', (done) => { + const currentPending = [ + { txid: 'ABC', amount: 100 }, + { txid: 'DEF', amount: 100 }, + { txid: 'GHI', amount: 100 }, + ] + GroupPartials.partialFailed = (transaction) => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'GRP_003') + expect(transaction).toEqual(currentPending[0]) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkDecrypted(true, { + decrypted: { n: 'XYZ', t: 20, p: 1, o: 3, x: 'junk' }, + transaction: { txid: 'ABC', amount: 100 }, + }) + }) + it('should call reject the transaction if the decrypted address doesnt validate', (done) => { + const currentPending = [ + { txid: 'ABC', amount: 100 }, + { txid: 'DEF', amount: 100 }, + { txid: 'GHI', amount: 100 }, + ] + GroupPartials.partialFailed = (transaction) => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'GRP_003A') + expect(transaction).toEqual(currentPending[0]) + done() + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + client: { + validateAddress: () => { + return Promise.resolve({ isvalid: false }) + }, + }, + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkDecrypted(true, { + decrypted: { n: 'XYZ', t: 20, p: 1, o: 3, u: '12345' }, + transaction: { txid: 'ABC', amount: 100 }, + }) + }) + it('should call reject the transaction if the the daemon failed to run the validation', (done) => { + const currentPending = [ + { txid: 'ABC', amount: 100 }, + { txid: 'DEF', amount: 100 }, + { txid: 'GHI', amount: 100 }, + ] + GroupPartials.partialFailed = (transaction) => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'GRP_003B') + expect(transaction).toEqual(currentPending[0]) + done() + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + client: { + validateAddress: () => { + return Promise.reject({ err: -21 }) + }, + }, + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkDecrypted(true, { + decrypted: { n: 'XYZ', t: 20, p: 1, o: 3, u: '12345' }, + transaction: { txid: 'ABC', amount: 100 }, + }) + }) + it('should call groupPartials if everything is correct', (done) => { + const currentPending = [ + { txid: 'ABC', amount: 100 }, + { txid: 'DEF', amount: 100 }, + { txid: 'GHI', amount: 100 }, + ] + GroupPartials.groupPartials = (decrypted, transaction) => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(transaction).toEqual(currentPending[0]) + expect(decrypted).toEqual({ n: 'XYZ', t: 20, p: 1, o: 3, u: '12345' }) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + client: { + validateAddress: () => { + return Promise.resolve({ isvalid: true }) + }, + }, + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkDecrypted(true, { + decrypted: { n: 'XYZ', t: 20, p: 1, o: 3, u: '12345' }, + transaction: { txid: 'ABC', amount: 100 }, + }) + }) + }) + describe('(groupPartials)', () => { + beforeEach(() => { + GroupPartials = rewire('../src/lib/GroupPartials') + }) + it('should reject the partial if the partial group is already marked as complete', (done) => { + const currentPending = [ + { txid: 'ABC', amount: 100 }, + { txid: 'DEF', amount: 100 }, + { txid: 'GHI', amount: 100 }, + ] + GroupPartials.partialFailed = (transaction) => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'GRP_006') + expect(transaction).toEqual(currentPending[0]) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + transactionsToReturn: [], + partials: { + 12345: { + destination: '123', + readyToProcess: true, + }, + }, + } + const decrypted = { n: 'XYZ', t: 20, p: 1, o: 3, u: '12345' } + const transaction = { txid: 'ABC', amount: 100 } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.groupPartials(decrypted, transaction) + }) + it('should reject the partial if the destination doesnt match the existing one', (done) => { + const currentPending = [ + { txid: 'ABC', amount: 100 }, + { txid: 'DEF', amount: 100 }, + { txid: 'GHI', amount: 100 }, + ] + GroupPartials.partialFailed = (transaction) => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'GRP_004') + expect(transaction).toEqual(currentPending[0]) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + transactionsToReturn: [], + partials: { + 12345: { + destination: '123', + }, + }, + } + const decrypted = { n: 'XYZ', t: 20, p: 1, o: 3, u: '12345' } + const transaction = { txid: 'ABC', amount: 100 } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.groupPartials(decrypted, transaction) + }) + it('should reject the partial if the transaction id already exists in the partials list', (done) => { + const currentPending = [ + { txid: 'ABC', amount: 100 }, + { txid: 'DEF', amount: 100 }, + { txid: 'GHI', amount: 100 }, + ] + GroupPartials.partialFailed = (transaction) => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'GRP_005') + expect(transaction).toEqual(currentPending[0]) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + transactionsToReturn: [], + partials: { + 12345: { + destination: 'XYZ', + transactions: { + ABC: { txid: 'ABC', amount: 100 }, + }, + }, + }, + } + const decrypted = { n: 'XYZ', t: 20, p: 1, o: 3, u: '12345' } + const transaction = { txid: 'ABC', amount: 100 } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.groupPartials(decrypted, transaction) + }) + it('should not mark the transaction partials as ready and call getDecryptedData', (done) => { + const currentPending = [ + { txid: 'DEF', amount: 100 }, + { txid: 'GHI', amount: 100 }, + ] + GroupPartials.getDecryptedData = () => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(GroupPartials.runtime.remainingToDecrypt[0]).toEqual({ txid: 'GHI', amount: 100 }) + expect(GroupPartials.runtime.partials[12345].readyToProcess).toBe(false) + expect(GroupPartials.runtime.partials[12345].amount).toBe(200) + expect(GroupPartials.runtime.partials[12345].partsSum).toBe(3) + expect(GroupPartials.runtime.partials[12345].transactions).toEqual({ + ABC: { txid: 'ABC', amount: 100, part: 1, vout: 1, vin: 2, confirmations: 20 }, + DEF: { txid: 'DEF', amount: 100, part: 2, vout: 1, vin: 2, confirmations: 20 }, + }) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + transactionsToReturn: [], + partials: { + 12345: { + destination: 'XYZ', + unique: '12345', + timeDelay: 20, + parts: 3, + partsSum: 1, + amount: 100, + transactions: { + ABC: { txid: 'ABC', amount: 100, part: 1, vout: 1, vin: 2, confirmations: 20 }, + }, + readyToProcess: false, + }, + }, + } + const decrypted = { n: 'XYZ', t: 20, p: 2, o: 3, u: '12345' } + const transaction = { txid: 'DEF', amount: 100, vout: 1, vin: 2, confirmations: 20 } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.groupPartials(decrypted, transaction) + }) + it('should mark the transaction partials as ready and call getDecryptedData', (done) => { + const currentPending = [ + { txid: 'GHI', amount: 100 }, + ] + GroupPartials.getDecryptedData = () => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(GroupPartials.runtime.remainingToDecrypt).toEqual([]) + expect(GroupPartials.runtime.partials[12345].amount).toBe(300) + expect(GroupPartials.runtime.partials[12345].partsSum).toBe(6) + expect(GroupPartials.runtime.partials[12345].transactions).toEqual({ + ABC: { txid: 'ABC', amount: 100, part: 1, vout: 1, vin: 2, confirmations: 20 }, + DEF: { txid: 'DEF', amount: 100, part: 2, vout: 1, vin: 2, confirmations: 20 }, + GHI: { txid: 'GHI', amount: 100, part: 3, vout: 1, vin: 2, confirmations: 20 }, + }) + expect(GroupPartials.runtime.partials[12345].readyToProcess).toBe(true) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + transactionsToReturn: [], + readyToProcess: {}, + partials: { + 12345: { + destination: 'XYZ', + unique: '12345', + timeDelay: 20, + parts: 3, + partsSum: 3, + amount: 200, + transactions: { + ABC: { txid: 'ABC', amount: 100, part: 1, vout: 1, vin: 2, confirmations: 20 }, + DEF: { txid: 'DEF', amount: 100, part: 2, vout: 1, vin: 2, confirmations: 20 }, + }, + readyToProcess: false, + }, + }, + } + const decrypted = { n: 'XYZ', t: 20, p: 3, o: 3, u: '12345' } + const transaction = { txid: 'GHI', amount: 100, vout: 1, vin: 2, confirmations: 20 } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.groupPartials(decrypted, transaction) + }) + it('if this unique has not been seen yet, it should create the obect', (done) => { + const currentPending = [ + { txid: 'ABC', amount: 100, vout: 1, vin: 2, confirmations: 20 }, + { txid: 'DEF', amount: 100, vout: 1, vin: 2, confirmations: 20 }, + { txid: 'GHI', amount: 100, vout: 1, vin: 2, confirmations: 20 }, + ] + GroupPartials.getDecryptedData = () => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(GroupPartials.runtime.remainingToDecrypt).toEqual([ + { txid: 'DEF', amount: 100, vout: 1, vin: 2, confirmations: 20 }, + { txid: 'GHI', amount: 100, vout: 1, vin: 2, confirmations: 20 }, + ]) + expect(GroupPartials.runtime.partials[12345].amount).toBe(100) + expect(GroupPartials.runtime.partials[12345].partsSum).toBe(1) + expect(GroupPartials.runtime.partials[12345].transactions).toEqual({ + ABC: { txid: 'ABC', amount: 100, part: 1, vout: 1, vin: 2, confirmations: 20 }, + }) + expect(GroupPartials.runtime.partials[12345].readyToProcess).toBe(false) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + transactionsToReturn: [], + readyToProcess: {}, + partials: { + }, + } + const decrypted = { n: 'XYZ', t: 20, p: 1, o: 3, u: '12345' } + const transaction = { txid: 'ABC', amount: 100, vout: 1, vin: 2, confirmations: 20 } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.groupPartials(decrypted, transaction) + }) + it('should complete even when in the wrong order', (done) => { + const currentPending = [ + { txid: 'GHI', amount: 100, vout: 1, vin: 2, confirmations: 20 }, + ] + GroupPartials.getDecryptedData = () => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(GroupPartials.runtime.remainingToDecrypt).toEqual([]) + expect(GroupPartials.runtime.partials[12345].amount).toBe(300) + expect(GroupPartials.runtime.partials[12345].partsSum).toBe(6) + expect(GroupPartials.runtime.partials[12345].transactions).toEqual({ + ABC: { txid: 'ABC', amount: 100, part: 1, vout: 1, vin: 2, confirmations: 20 }, + DEF: { txid: 'DEF', amount: 100, part: 3, vout: 1, vin: 2, confirmations: 20 }, + GHI: { txid: 'GHI', amount: 100, part: 2, vout: 1, vin: 2, confirmations: 20 }, + }) + expect(GroupPartials.runtime.partials[12345].readyToProcess).toBe(true) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + transactionsToReturn: [], + readyToProcess: {}, + partials: { + 12345: { + destination: 'XYZ', + unique: '12345', + timeDelay: 20, + parts: 3, + partsSum: 4, + amount: 200, + transactions: { + ABC: { txid: 'ABC', amount: 100, part: 1, vout: 1, vin: 2, confirmations: 20 }, + DEF: { txid: 'DEF', amount: 100, part: 3, vout: 1, vin: 2, confirmations: 20 }, + }, + readyToProcess: false, + }, + }, + } + const decrypted = { n: 'XYZ', t: 20, p: 2, o: 3, u: '12345' } + const transaction = { txid: 'GHI', amount: 100, vout: 1, vin: 2, confirmations: 20 } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.groupPartials(decrypted, transaction) + }) + it('should work with long unsafe amounts', (done) => { + const currentPending = [ + { txid: 'GHI', amount: 100.33333333, vout: 1, vin: 2, confirmations: 20 }, + ] + GroupPartials.getDecryptedData = () => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(GroupPartials.runtime.remainingToDecrypt).toEqual([]) + expect(GroupPartials.runtime.partials[12345].amount).toBe(300.66666666) + expect(GroupPartials.runtime.partials[12345].partsSum).toBe(6) + expect(GroupPartials.runtime.partials[12345].transactions).toEqual({ + ABC: { txid: 'ABC', amount: 100.33333333, part: 1, vout: 1, vin: 2, confirmations: 20 }, + DEF: { txid: 'DEF', amount: 100, part: 3, vout: 1, vin: 2, confirmations: 20 }, + GHI: { txid: 'GHI', amount: 100.33333333, part: 2, vout: 1, vin: 2, confirmations: 20 }, + }) + expect(GroupPartials.runtime.partials[12345].readyToProcess).toBe(true) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + transactionsToReturn: [], + readyToProcess: {}, + partials: { + 12345: { + destination: 'XYZ', + unique: '12345', + timeDelay: 20, + parts: 3, + partsSum: 4, + amount: 200.33333333, + transactions: { + ABC: { txid: 'ABC', amount: 100.33333333, part: 1, vout: 1, vin: 2, confirmations: 20 }, + DEF: { txid: 'DEF', amount: 100, part: 3, vout: 1, vin: 2, confirmations: 20 }, + }, + readyToProcess: false, + }, + }, + } + const decrypted = { n: 'XYZ', t: 20, p: 2, o: 3, u: '12345' } + const transaction = { txid: 'GHI', amount: 100.33333333, vout: 1, vin: 2, confirmations: 20 } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.groupPartials(decrypted, transaction) + }) + }) + describe('(partialFailed)', () => { + beforeEach(() => { + GroupPartials = rewire('../src/lib/GroupPartials') + }) + it('should add the transaction to the return list and move on', (done) => { + const transaction = { txid: 'GHI', amount: 100 } + const currentPending = [{ txid: 'GHI', amount: 100 }, { txid: 'ABC', amount: 100 }, { txid: 'DEF', amount: 100 }] + GroupPartials.getDecryptedData = () => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(GroupPartials.runtime.remainingToDecrypt).toEqual([{ txid: 'ABC', amount: 100 }, { txid: 'DEF', amount: 100 }]) + expect(GroupPartials.runtime.transactionsToReturn).toEqual([{ txid: 'GHI', amount: 100 }]) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + transactionsToReturn: [], + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.partialFailed(transaction) + }) + it('should not add the duplicate transaction to the return list and move on', (done) => { + const transaction = { txid: 'GHI', amount: 100 } + const currentPending = [{ txid: 'GHI', amount: 100 }, { txid: 'ABC', amount: 100 }, { txid: 'DEF', amount: 100 }] + GroupPartials.getDecryptedData = () => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(GroupPartials.runtime.remainingToDecrypt).toEqual([{ txid: 'ABC', amount: 100 }, { txid: 'DEF', amount: 100 }]) + expect(GroupPartials.runtime.transactionsToReturn).toEqual([{ txid: 'GHI', amount: 100 }]) + done() + } + GroupPartials.runtime = { + remainingToDecrypt: currentPending, + transactionsToReturn: [{ txid: 'GHI', amount: 100 }], + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.partialFailed(transaction) + }) + }) + describe('(checkPartials)', () => { + beforeEach(() => { + GroupPartials = rewire('../src/lib/GroupPartials') + }) + it('should have no partials ready to process', (done) => { + const transaction = { txid: 'GHI', amount: 100 } + const currentPending = [{ txid: 'GHI', amount: 100 }, { txid: 'ABC', amount: 100 }, { txid: 'DEF', amount: 100 }] + const partials = { + 11111: { + unique: '11111', + transactions: { + GHI: { txid: 'GHI', amount: 100, confirmations: 10 }, + ABC: { txid: 'ABC', amount: 100, confirmations: 10 }, + }, + readyToProcess: false, + }, + } + GroupPartials.runtime = { + currentPending, + transactionsToReturn: [], + readyToProcess: [], + partials, + callback: (success, data) => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(success).toBe(true) + expect(data.readyToProcess).toEqual([]) + expect(data.transactionsToReturn).toEqual([]) + done() + }, + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkPartials(transaction) + }) + it('should reject partials that were too old to process', (done) => { + const transaction = { txid: 'GHI', amount: 100 } + const currentPending = [{ txid: 'GHI', amount: 100 }, { txid: 'ABC', amount: 100 }, { txid: 'DEF', amount: 100 }] + const partials = { + 11111: { + unique: '11111', + transactions: { + GHI: { txid: 'GHI', amount: 100, confirmations: 1000 }, + ABC: { txid: 'ABC', amount: 100, confirmations: 1000 }, + }, + readyToProcess: false, + }, + } + GroupPartials.runtime = { + currentPending, + transactionsToReturn: [], + readyToProcess: [], + partials, + callback: (success, data) => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(success).toBe(true) + expect(data.readyToProcess).toEqual([]) + expect(data.transactionsToReturn).toEqual([ + { txid: 'GHI', amount: 100, confirmations: 1000 }, + { txid: 'ABC', amount: 100, confirmations: 1000 }, + ]) + done() + }, + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkPartials(transaction) + }) + it('should have partials to process', (done) => { + const transaction = { txid: 'GHI', amount: 100 } + const currentPending = [{ txid: 'GHI', amount: 100 }, { txid: 'ABC', amount: 100 }, { txid: 'DEF', amount: 100 }] + const partials = { + 11111: { + unique: '11111', + transactions: { + GHI: { txid: 'GHI', amount: 100, confirmations: 1000 }, + ABC: { txid: 'ABC', amount: 100, confirmations: 1000 }, + }, + readyToProcess: true, + }, + } + GroupPartials.runtime = { + currentPending, + transactionsToReturn: [], + readyToProcess: partials, + partials, + callback: (success, data) => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(success).toBe(true) + expect(data.readyToProcess).toEqual(partials) + expect(data.transactionsToReturn).toEqual([]) + done() + }, + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkPartials(transaction) + }) + it('should return a mix of ready and return transactions', (done) => { + const transaction = { txid: 'GHI', amount: 100 } + const currentPending = [{ txid: 'GHI', amount: 100 }, { txid: 'ABC', amount: 100 }, { txid: 'DEF', amount: 100 }] + const partials = { + 11111: { + unique: '11111', + transactions: { + GHI: { txid: 'GHI', amount: 100, confirmations: 1000 }, + ABC: { txid: 'ABC', amount: 100, confirmations: 1000 }, + }, + readyToProcess: true, + }, + 22222: { + unique: '22222', + transactions: { + DEF: { txid: 'DEF', amount: 100, confirmations: 1000 }, + }, + readyToProcess: false, + }, + } + GroupPartials.runtime = { + currentPending, + transactionsToReturn: [], + readyToProcess: { 11111: partials[11111] }, + partials, + callback: (success, data) => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(success).toBe(true) + expect(data.readyToProcess).toEqual({ 11111: partials[11111] }) + expect(data.transactionsToReturn).toEqual([partials[22222].transactions.DEF]) + done() + }, + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkPartials(transaction) + }) + it('should return a mix of ready and return transactions', (done) => { + const transaction = { txid: 'GHI', amount: 100 } + const currentPending = [{ txid: 'GHI', amount: 100 }, { txid: 'ABC', amount: 100 }, { txid: 'DEF', amount: 100 }] + const partials = { + 11111: { + unique: '11111', + transactions: { + GHI: { txid: 'GHI', amount: 100, confirmations: 1000 }, + ABC: { txid: 'ABC', amount: 100, confirmations: 1000 }, + }, + readyToProcess: true, + }, + 22222: { + unique: '22222', + transactions: { + DEF: { txid: 'DEF', amount: 100, confirmations: 1000 }, + }, + readyToProcess: false, + }, + } + GroupPartials.runtime = { + currentPending, + transactionsToReturn: [], + readyToProcess: { 11111: partials[11111] }, + partials, + callback: (success, data) => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(success).toBe(true) + expect(data.readyToProcess).toEqual({ 11111: partials[11111] }) + expect(data.transactionsToReturn).toEqual([partials[22222].transactions.DEF]) + done() + }, + } + const mockLogger = { + writeLog: sinon.spy(), + } + GroupPartials.__set__('Logger', mockLogger) + GroupPartials.checkPartials(transaction) + }) + }) +}) diff --git a/test/Logger.spec.js b/test/Logger.spec.js index 9254ca5..f52aec7 100644 --- a/test/Logger.spec.js +++ b/test/Logger.spec.js @@ -24,7 +24,7 @@ describe('[Logger]', () => { }) }) describe('(sendMail)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions Logger = rewire('../src/lib/Logger') }) it('should call the mail transport function', (done) => { diff --git a/test/NavCoin.spec.js b/test/NavCoin.spec.js index 2a05c69..74f13fc 100644 --- a/test/NavCoin.spec.js +++ b/test/NavCoin.spec.js @@ -52,7 +52,7 @@ describe('[NavCoin]', () => { }) }) describe('(lockWallet)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions NavCoin = rewire('../src/lib/NavCoin') }) it('should fail on params', (done) => { diff --git a/test/PayoutFee.spec.js b/test/PayoutFee.spec.js index 041551b..018efa5 100644 --- a/test/PayoutFee.spec.js +++ b/test/PayoutFee.spec.js @@ -36,15 +36,15 @@ describe('[PayoutFee]', () => { it('should fail when navBalance is less than poolAmount', (done) => { const callback = (success, data) => { expect(success).toBe(false) - expect(data.message).toBeA('string') + sinon.assert.calledWith(mockLogger.writeLog, 'PAY_002') sinon.assert.calledOnce(mockLogger.writeLog) done() } const mockClient = { - getBalance: () => { return Promise.resolve(2) }, + listUnspent: () => { return Promise.resolve([{ amount: 100 }, { amount: 200 }, { amount: 500 }]) }, } const mockSettings = { - navPoolAmount: 10, + navPoolAmount: 1000, } PayoutFee.runtime = { callback, @@ -60,15 +60,16 @@ describe('[PayoutFee]', () => { it('should fail when navPayout is less than the minimum specified amount', (done) => { const callback = (success, data) => { expect(success).toBe(false) + sinon.assert.notCalled(mockLogger.writeLog) expect(data.message).toBeA('string') done() } const mockClient = { - getBalance: () => { return Promise.resolve(12) }, + listUnspent: () => { return Promise.resolve([{ amount: 100 }, { amount: 200 }, { amount: 500 }]) }, } const mockSettings = { - navPoolAmount: 10, - txFeePayoutMin: 200, + navPoolAmount: 500, + txFeePayoutMin: 500, } PayoutFee.privateSettings = { txFee: 0.01, @@ -78,12 +79,43 @@ describe('[PayoutFee]', () => { settings: mockSettings, navClient: mockClient, } + const mockLogger = { + writeLog: sinon.spy(), + } + PayoutFee.__set__('Logger', mockLogger) + PayoutFee.send() + }) + it('should fail to get the current balance', (done) => { + const callback = (success, data) => { + expect(success).toBe(false) + expect(data.message).toBeA('string') + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PAY_002A') + done() + } + const mockClient = { + name: 'jeeves', + listUnspent: () => { return Promise.reject({ err: { code: -21 } }) }, + } + const mockSettings = { + navPoolAmount: 100, + txFeePayoutMin: 1, + anonTxFeeAddress: 'abc123', + } + const mockLogger = { + writeLog: sinon.spy(), + } + PayoutFee.__set__('Logger', mockLogger) + PayoutFee.runtime = { + callback, + settings: mockSettings, + navClient: mockClient, + } PayoutFee.send() }) it('should run sendtoaddress if all conditions met', (done) => { const SendToAddress = { send: (options, callback) => { - console.log(options, callback) expect(options.client).toBe(mockClient) expect(options.address).toBe(mockSettings.anonTxFeeAddress) expect(options.amount).toBe(120 - 100 - privateSettings.txFee) @@ -92,16 +124,15 @@ describe('[PayoutFee]', () => { }, } const callback = () => { - console.log('CALLBACK') done() } const mockClient = { name: 'jeeves', - getBalance: () => { return Promise.resolve(120) }, + listUnspent: () => { return Promise.resolve([{ amount: 100 }, { amount: 200 }, { amount: 500 }]) }, } const mockSettings = { - navPoolAmount: 100, - txFeePayoutMin: 1, + navPoolAmount: 500, + txFeePayoutMin: 100, anonTxFeeAddress: 'abc123', } PayoutFee.runtime = { diff --git a/test/PrepareIncoming.spec.js b/test/PrepareIncoming.spec.js index 9e2e08c..a67f48d 100644 --- a/test/PrepareIncoming.spec.js +++ b/test/PrepareIncoming.spec.js @@ -44,11 +44,12 @@ describe('[PrepareIncoming]', () => { navClient: mockClient, outgoingNavBalance: 50000, subBalance: 1000, + settings: { test: true }, }, callback) }) }) describe('(getUnspent)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions PrepareIncoming = rewire('../src/lib/PrepareIncoming') }) it('should fail to list unspent', (done) => { @@ -120,7 +121,7 @@ describe('[PrepareIncoming]', () => { }) }) describe('(unspentFiltered)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions PrepareIncoming = rewire('../src/lib/PrepareIncoming') }) it('should fail to filter the unspent', (done) => { @@ -187,15 +188,14 @@ describe('[PrepareIncoming]', () => { currentPending: [], }) }) - it('should return the right data, set currentPending and call PruneUnspent', (done) => { - PrepareIncoming.pruneUnspent = (options, parsedCallback) => { - expect(parsedCallback).toBe(PrepareIncoming.unspentPruned) - expect(options.currentPending).toBe(currentPending) - expect(options.client).toBe(mockClient) - expect(options.subBalance).toBe(1000) - expect(options.maxAmount).toBe(50000) - sinon.assert.notCalled(mockLogger.writeLog) - done() + it('should return the right data, set currentPending and call GroupPartials.run', (done) => { + const GroupPartials = { + run: (options, parsedCallback) => { + expect(parsedCallback).toBe(PrepareIncoming.partialsGrouped) + expect(options.currentPending).toBe(currentPending) + sinon.assert.notCalled(mockLogger.writeLog) + done() + }, } const mockClient = { listUnspent: () => { return Promise.reject({ code: -17 }) }, @@ -204,6 +204,7 @@ describe('[PrepareIncoming]', () => { writeLog: sinon.spy(), } PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.__set__('GroupPartials', GroupPartials) PrepareIncoming.runtime = { navClient: mockClient, subBalance: 1000, @@ -215,35 +216,125 @@ describe('[PrepareIncoming]', () => { }) }) }) - describe('(pruneUnspent)', () => { - before(() => { // reset the rewired functions + describe('(partialsGrouped)', () => { + beforeEach(() => { // reset the rewired functions PrepareIncoming = rewire('../src/lib/PrepareIncoming') }) - it('should fail no currentPending param', (done) => { + it('should fail on success', (done) => { + const callback = (success) => { + expect(success).toBe(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PREPI_003A') + done() + } + PrepareIncoming.runtime = { + callback, + } + const mockLogger = { + writeLog: sinon.spy(), + } + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.partialsGrouped(false, { + junkParam: 1234, + }) + }) + it('should fail on data', (done) => { + const callback = (success) => { + expect(success).toBe(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PREPI_003A') + done() + } + PrepareIncoming.runtime = { + callback, + } + const mockLogger = { + writeLog: sinon.spy(), + } + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.partialsGrouped(true) + }) + it('should fail with incorrect data and return null pendingToReturn object', (done) => { const callback = (success, data) => { expect(success).toBe(false) - expect(data.message).toBeA('string') sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PREPI_003AA') + expect(data.pendingToReturn).toBe(null) done() } + PrepareIncoming.runtime = { + callback, + } const mockLogger = { writeLog: sinon.spy(), } PrepareIncoming.__set__('Logger', mockLogger) - PrepareIncoming.pruneUnspent({ + PrepareIncoming.partialsGrouped(true, { junkParam: 1234, - }, callback) + }) + }) + it('should fail with no readyToProcess and return a valid pendingToReturn object', (done) => { + const callback = (success, data) => { + expect(success).toBe(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PREPI_003AA') + expect(data.pendingToReturn).toEqual({ 1234: { unique: '1234' }, 2345: { unique: '2345' } }) + done() + } + PrepareIncoming.runtime = { + callback, + } + const mockLogger = { + writeLog: sinon.spy(), + } + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.partialsGrouped(true, { + transactionsToReturn: { 1234: { unique: '1234' }, 2345: { unique: '2345' } }, + }) + }) + it('should set the pendingToReturn object to runtime and call pruneUnspent', (done) => { + PrepareIncoming.pruneUnspent = (options, callback) => { + expect(options.readyToProcess).toEqual({ 1234: { unique: '1234' }, 2345: { unique: '2345' } }) + expect(options.client).toBe(3456) + expect(options.subBalance).toBe(1000) + expect(options.maxAmount).toBe(10000) + sinon.assert.notCalled(mockLogger.writeLog) + expect(callback).toBe(PrepareIncoming.unspentPruned) + done() + } + PrepareIncoming.runtime = { + currentPending: 1234, + navClient: 3456, + subBalance: 1000, + outgoingNavBalance: 10000, + } + const mockLogger = { + writeLog: sinon.spy(), + } + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.partialsGrouped(true, { + readyToProcess: { 1234: { unique: '1234' }, 2345: { unique: '2345' } }, + }) + }) + }) + describe('(pruneUnspent)', () => { + beforeEach(() => { // reset the rewired functions + PrepareIncoming = rewire('../src/lib/PrepareIncoming') }) it('should fail on subBalance is not float', (done) => { const callback = (success, data) => { expect(success).toBe(false) expect(data.message).toBeA('string') sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PREPI_003B') done() } const mockLogger = { writeLog: sinon.spy(), } + PrepareIncoming.runtime = { + transactionsToReturn: 1234, + } const currentPending = [1, 2, 3, 4] PrepareIncoming.__set__('Logger', mockLogger) PrepareIncoming.pruneUnspent({ @@ -256,6 +347,7 @@ describe('[PrepareIncoming]', () => { expect(success).toBe(false) expect(data.message).toBeA('string') sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PREPI_003B') done() } const mockLogger = { @@ -269,10 +361,29 @@ describe('[PrepareIncoming]', () => { maxAmount: 'ABCDE', }, callback) }) - it('should fail to return any pruned', (done) => { + it('should fail on no readyToProcess', (done) => { const callback = (success, data) => { expect(success).toBe(false) expect(data.message).toBeA('string') + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PREPI_003B') + done() + } + const mockLogger = { + writeLog: sinon.spy(), + } + const currentPending = [1, 2, 3, 4] + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.pruneUnspent({ + currentPending, + subBalance: 1000, + maxAmount: 10000, + }, callback) + }) + it('should fail to return any pruned', (done) => { + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.message).toBeA('string') sinon.assert.notCalled(mockLogger.writeLog) done() } @@ -280,23 +391,43 @@ describe('[PrepareIncoming]', () => { writeLog: sinon.spy(), } const currentPending = [ - { amount: 10000 }, - { amount: 10000 }, - { amount: 10000 }, - { amount: 10000 }, + { txid: 'ASDF', amount: 1000 }, + { txid: 'QWER', amount: 3000 }, + { txid: 'ZXCV', amount: 2000 }, ] + const readyToProcess = { + 111: { + amount: 5000, + unique: '111', + transactions: { + ASDF: { txid: 'ASDF', amount: 1000 }, + QWER: { txid: 'QWER', amount: 3000 }, + ZXCV: { txid: 'ZXCV', amount: 2000 }, + }, + }, + } PrepareIncoming.__set__('Logger', mockLogger) PrepareIncoming.pruneUnspent({ currentPending, subBalance: 1000, - maxAmount: 5000, + maxAmount: 4000, + readyToProcess, }, callback) }) - it('should have at least 1 trasaction prepared after pruning', (done) => { + it('should have 1 group ready after pruning', (done) => { const callback = (success, data) => { expect(success).toBe(true) - expect(data.currentBatch.length).toBe(2) - expect(data.sumPending).toBe(200) + expect(data.currentBatch).toEqual([ + { + amount: 1000, + unique: '111', + transactions: { + REWQ: { txid: 'REWQ', amount: 500 }, + FDSA: { txid: 'FDSA', amount: 200 }, + VCXZ: { txid: 'VCXZ', amount: 300 }, + }, + }, + ]) sinon.assert.notCalled(mockLogger.writeLog) done() } @@ -304,40 +435,129 @@ describe('[PrepareIncoming]', () => { writeLog: sinon.spy(), } const currentPending = [ - { amount: 100 }, - { amount: 100 }, - { amount: 10000 }, - { amount: 10000 }, + { txid: 'ASDF', amount: 1000 }, + { txid: 'QWER', amount: 3000 }, + { txid: 'ZXCV', amount: 2000 }, + { txid: 'REWQ', amount: 500 }, + { txid: 'FDSA', amount: 200 }, + { txid: 'VCXZ', amount: 300 }, ] + const readyToProcess = { + 111: { + amount: 1000, + unique: '111', + transactions: { + REWQ: { txid: 'REWQ', amount: 500 }, + FDSA: { txid: 'FDSA', amount: 200 }, + VCXZ: { txid: 'VCXZ', amount: 300 }, + }, + }, + 222: { + amount: 5000, + unique: '2222', + transactions: { + ASDF: { txid: 'ASDF', amount: 1000 }, + QWER: { txid: 'QWER', amount: 3000 }, + ZXCV: { txid: 'ZXCV', amount: 2000 }, + }, + }, + } PrepareIncoming.__set__('Logger', mockLogger) PrepareIncoming.pruneUnspent({ currentPending, subBalance: 1000, maxAmount: 5000, + readyToProcess, + }, callback) + }) + it('should have multiple groups ready after pruning', (done) => { + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.currentBatch).toEqual([ + { + amount: 1000, + unique: '111', + transactions: { + REWQ: { txid: 'REWQ', amount: 500 }, + FDSA: { txid: 'FDSA', amount: 200 }, + VCXZ: { txid: 'VCXZ', amount: 300 }, + }, + }, + { + amount: 1000, + unique: '222', + transactions: { + REWQ: { txid: 'REWQ', amount: 500 }, + FDSA: { txid: 'FDSA', amount: 200 }, + VCXZ: { txid: 'VCXZ', amount: 300 }, + }, + }, + ]) + sinon.assert.notCalled(mockLogger.writeLog) + done() + } + const mockLogger = { + writeLog: sinon.spy(), + } + const readyToProcess = { + 111: { + amount: 1000, + unique: '111', + transactions: { + REWQ: { txid: 'REWQ', amount: 500 }, + FDSA: { txid: 'FDSA', amount: 200 }, + VCXZ: { txid: 'VCXZ', amount: 300 }, + }, + }, + 222: { + amount: 1000, + unique: '222', + transactions: { + REWQ: { txid: 'REWQ', amount: 500 }, + FDSA: { txid: 'FDSA', amount: 200 }, + VCXZ: { txid: 'VCXZ', amount: 300 }, + }, + }, + 333: { + amount: 5000, + unique: '333', + transactions: { + ASDF: { txid: 'ASDF', amount: 1000 }, + QWER: { txid: 'QWER', amount: 3000 }, + ZXCV: { txid: 'ZXCV', amount: 2000 }, + }, + }, + } + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.pruneUnspent({ + subBalance: 1000, + maxAmount: 5000, + readyToProcess, }, callback) }) }) describe('(unspentPruned)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions PrepareIncoming = rewire('../src/lib/PrepareIncoming') }) - it('should fail with false success', (done) => { + it('should have pendingToReturn', (done) => { const mockLogger = { writeLog: sinon.spy(), } PrepareIncoming.__set__('Logger', mockLogger) PrepareIncoming.runtime = { callback: (success, data) => { - expect(success).toBe(false) - expect(data.message).toBeA('string') + expect(success).toBe(true) + expect(data.pendingToReturn).toEqual(1234) sinon.assert.calledOnce(mockLogger.writeLog) done() }, + transactionsToReturn: 1234, } PrepareIncoming.unspentPruned( false, {}) }) - it('should fail with no current batch', (done) => { + it('should fail with no current batch and no transactions to return', (done) => { const mockLogger = { writeLog: sinon.spy(), } @@ -345,7 +565,7 @@ describe('[PrepareIncoming]', () => { PrepareIncoming.runtime = { callback: (success, data) => { expect(success).toBe(false) - expect(data.message).toBeA('string') + expect(data.pendingToReturn).toEqual(null) sinon.assert.calledOnce(mockLogger.writeLog) done() }, @@ -361,7 +581,7 @@ describe('[PrepareIncoming]', () => { PrepareIncoming.runtime = { callback: (success, data) => { expect(success).toBe(false) - expect(data.message).toBeA('string') + expect(data.pendingToReturn).toEqual(null) sinon.assert.calledOnce(mockLogger.writeLog) done() }, @@ -369,25 +589,207 @@ describe('[PrepareIncoming]', () => { PrepareIncoming.unspentPruned( true, { currentBatch: [] }) }) - it('should succeed and run the runtime.callback', (done) => { + it('should succeed to prune the current batch and run FlattenTransactions.incoming', (done) => { const mockLogger = { writeLog: sinon.spy(), } - PrepareIncoming.__set__('Logger', mockLogger) - PrepareIncoming.runtime = { - callback: (success, data) => { - expect(success).toBe(true) - expect(data.currentBatch).toBe(currentBatch) + const mockFlattenTransactions = { + incoming: (options, callback) => { + expect(options.amountToFlatten).toBe(100) + expect(options.anonFeePercent).toBe(0.5) + expect(callback).toBe(PrepareIncoming.flattened) sinon.assert.notCalled(mockLogger.writeLog) done() }, } + PrepareIncoming.runtime = { + settings: { + anonFeePercent: 0.5, + }, + } + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.__set__('FlattenTransactions', mockFlattenTransactions) const currentBatch = [ { amount: 100 }, - { amount: 100 }, + { amount: 500 }, ] PrepareIncoming.unspentPruned( true, { currentBatch }) }) }) + describe('(flattened)', () => { + beforeEach(() => { // reset the rewired functions + PrepareIncoming = rewire('../src/lib/PrepareIncoming') + }) + it('should fail with false success', (done) => { + const mockLogger = { + writeLog: sinon.spy(), + } + const mockFlattenTransactions = { + incoming: (options, callback) => { + expect(options.amountToFlatten).toBe(542) + expect(callback).toBe(PrepareIncoming.flattened) + expect(PrepareIncoming.runtime.remainingToFlatten).toEqual([{ unique: 'ASD', amount: 542, transactions: {} }]) + expect(PrepareIncoming.runtime.numFlattened).toBe(10) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PREPI_004') + done() + }, + } + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.__set__('FlattenTransactions', mockFlattenTransactions) + PrepareIncoming.runtime = { + remainingToFlatten: [ + { unique: 'QWE', amount: 231, transactions: {} }, + { unique: 'ASD', amount: 542, transactions: {} }, + ], + numFlattened: 10, + currentFlattened: {}, + transactionsToReturn: null, + settings: { + anonFeePercent: 0.5, + }, + } + PrepareIncoming.flattened(false, {}) + }) + it('should fail with false success', (done) => { + const mockLogger = { + writeLog: sinon.spy(), + } + const mockFlattenTransactions = { + incoming: (options, callback) => { + expect(options.amountToFlatten).toBe(542) + expect(callback).toBe(PrepareIncoming.flattened) + expect(PrepareIncoming.runtime.remainingToFlatten).toEqual([{ unique: 'ASD', amount: 542, transactions: {} }]) + expect(PrepareIncoming.runtime.numFlattened).toBe(10) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PREPI_004') + done() + }, + } + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.__set__('FlattenTransactions', mockFlattenTransactions) + PrepareIncoming.runtime = { + remainingToFlatten: [ + { unique: 'QWE', amount: 231, transactions: {} }, + { unique: 'ASD', amount: 542, transactions: {} }, + ], + numFlattened: 10, + currentFlattened: {}, + transactionsToReturn: null, + settings: { + anonFeePercent: 0.5, + }, + } + PrepareIncoming.flattened(true, { junkParam: 1234 }) + }) + it('should run out of subaddresses and run the callback', (done) => { + const mockLogger = { + writeLog: sinon.spy(), + } + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.runtime = { + currentBatch: [ + { unique: 'ASD', amount: 546, transactions: {} }, + { unique: 'QWE', amount: 231, transactions: {} }, + ], + remainingToFlatten: [ + { unique: 'ZXC', amount: 345, transactions: {} }, + ], + numFlattened: 15, + currentFlattened: { + ASD: [100, 100, 100, 100, 100, 10, 10, 10, 10, 1, 1, 1, 1, 1, 1], + }, + callback: (success, data) => { + expect(success).toBe(true) + expect(data.numFlattened).toBe(15) + expect(data.currentFlattened).toEqual({ + ASD: [100, 100, 100, 100, 100, 10, 10, 10, 10, 1, 1, 1, 1, 1, 1], + }) + expect(data.currentBatch).toEqual([ + { unique: 'ASD', amount: 546, transactions: {} }, + { unique: 'QWE', amount: 231, transactions: {} }, + ]) + done() + }, + transactionsToReturn: null, + } + const mockPrivateSettings = { + maxAddresses: 20, + } + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.__set__('privateSettings', mockPrivateSettings) + PrepareIncoming.flattened(true, { flattened: [100, 100, 10, 10, 10, 1] }) + }) + it('should get the flattened and move onto the next one', (done) => { + const mockLogger = { + writeLog: sinon.spy(), + } + PrepareIncoming.__set__('Logger', mockLogger) + const mockFlattenTransactions = { + incoming: (options, callback) => { + expect(options.amountToFlatten).toBe(542) + expect(callback).toBe(PrepareIncoming.flattened) + expect(PrepareIncoming.runtime.remainingToFlatten).toEqual([{ unique: 'ASD', amount: 542, transactions: {} }]) + expect(PrepareIncoming.runtime.numFlattened).toBe(16) + expect(PrepareIncoming.runtime.currentFlattened.QWE).toEqual([100, 100, 10, 10, 10, 1]) + sinon.assert.notCalled(mockLogger.writeLog) + done() + }, + } + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.__set__('FlattenTransactions', mockFlattenTransactions) + PrepareIncoming.runtime = { + remainingToFlatten: [ + { unique: 'QWE', amount: 231, transactions: {} }, + { unique: 'ASD', amount: 542, transactions: {} }, + ], + numFlattened: 10, + currentFlattened: {}, + transactionsToReturn: null, + settings: { + anonFeePercent: 0.5, + }, + } + PrepareIncoming.flattened(true, { flattened: [100, 100, 10, 10, 10, 1] }) + }) + it('should flatten the last one and run the callback', (done) => { + const mockLogger = { + writeLog: sinon.spy(), + } + const mockPrivateSettings = { + maxAddresses: 1000, + } + PrepareIncoming.__set__('Logger', mockLogger) + PrepareIncoming.__set__('privateSettings', mockPrivateSettings) + PrepareIncoming.runtime = { + currentBatch: [ + { unique: 'QWE', amount: 231, transactions: {} }, + { unique: 'ASD', amount: 542, transactions: {} }, + ], + remainingToFlatten: [ + { unique: 'ASD', amount: 542, transactions: {} }, + ], + numFlattened: 16, + currentFlattened: { + QWE: [100, 100, 10, 10, 10, 1], + }, + callback: (success, data) => { + expect(success).toBe(true) + expect(data.numFlattened).toBe(27) + expect(data.currentFlattened).toEqual({ + QWE: [100, 100, 10, 10, 10, 1], + ASD: [100, 100, 100, 100, 100, 10, 10, 10, 10, 1, 1], + }) + expect(data.currentBatch).toEqual([ + { unique: 'QWE', amount: 231, transactions: {} }, + { unique: 'ASD', amount: 542, transactions: {} }, + ]) + done() + }, + transactionsToReturn: null, + } + PrepareIncoming.flattened(true, { flattened: [100, 100, 100, 100, 100, 10, 10, 10, 10, 1, 1] }) + }) + }) }) diff --git a/test/PrepareOutgoing.spec.js b/test/PrepareOutgoing.spec.js index 1117352..40e7312 100644 --- a/test/PrepareOutgoing.spec.js +++ b/test/PrepareOutgoing.spec.js @@ -26,6 +26,39 @@ describe('[PrepareOutgoing]', () => { PrepareOutgoing.__set__('Logger', mockLogger) PrepareOutgoing.run({ junkParam: 1234 }, callback) }) + it('should set the runtime variables and fail to get the blockHeight', (done) => { + const callback = () => { + expect(PrepareOutgoing.runtime.callback).toBe(callback) + expect(PrepareOutgoing.runtime.navClient).toBe(mockClient) + expect(PrepareOutgoing.runtime.subClient).toBe(mockClient) + expect(PrepareOutgoing.runtime.navBalance).toBe(50000) + expect(PrepareOutgoing.runtime.settings).toEqual({ test: 1 }) + expect(PrepareOutgoing.runtime.failedSubTransactions).toEqual([]) + expect(PrepareOutgoing.runtime.currentBatch).toEqual([]) + expect(PrepareOutgoing.runtime.sumPending).toBe(0) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PREPO_001A') + done() + } + + const mockLogger = { + writeLog: sinon.spy(), + } + PrepareOutgoing.__set__('Logger', mockLogger) + + const mockClient = { + getBlockCount: () => { + return Promise.reject({ code: -17 }) + }, + } + + PrepareOutgoing.run({ + navClient: mockClient, + subClient: mockClient, + navBalance: 50000, + settings: { test: 1 }, + }, callback) + }) it('should set the runtime variables and call getUnspent', (done) => { PrepareOutgoing.getUnspent = () => { expect(PrepareOutgoing.runtime.callback).toBe(callback) @@ -41,8 +74,15 @@ describe('[PrepareOutgoing]', () => { const callback = () => {} + const mockLogger = { + writeLog: sinon.spy(), + } + PrepareOutgoing.__set__('Logger', mockLogger) + const mockClient = { - getAccountAddress: () => { return Promise.reject({ code: -17 }) }, + getBlockCount: () => { + return Promise.resolve(1000) + }, } PrepareOutgoing.run({ @@ -54,7 +94,7 @@ describe('[PrepareOutgoing]', () => { }) }) describe('(getUnspent)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions PrepareOutgoing = rewire('../src/lib/PrepareOutgoing') }) it('should fail to list unspent', (done) => { @@ -127,7 +167,7 @@ describe('[PrepareOutgoing]', () => { }) }) describe('(unspentFiltered)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions PrepareOutgoing = rewire('../src/lib/PrepareOutgoing') }) it('should fail to filter the unspent', (done) => { @@ -215,7 +255,7 @@ describe('[PrepareOutgoing]', () => { }) }) describe('(processTransaction)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions PrepareOutgoing = rewire('../src/lib/PrepareOutgoing') }) it('should call getEncrypted on the next transaction to process', (done) => { @@ -271,7 +311,7 @@ describe('[PrepareOutgoing]', () => { }) }) describe('(failedTransaction)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions PrepareOutgoing = rewire('../src/lib/PrepareOutgoing') }) it('should successfully exit with no more to process', (done) => { @@ -294,7 +334,7 @@ describe('[PrepareOutgoing]', () => { }) }) describe('(checkDecrypted)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions PrepareOutgoing = rewire('../src/lib/PrepareOutgoing') }) it('should fail to get the encrypted data', (done) => { @@ -365,9 +405,10 @@ describe('[PrepareOutgoing]', () => { } PrepareOutgoing.__set__('Logger', mockLogger) PrepareOutgoing.checkDecrypted(true, { transaction: 1, decrypted: { - a: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', - n: 100000, + n: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', + v: 100000, s: '5rgXsNYQ9y4lArEmWA1Wrh9ztUmoG2vZBx1SB1FnZX', + t: 100, } }) }) it('should get the encrypted data but the secret is wrong', (done) => { @@ -387,16 +428,46 @@ describe('[PrepareOutgoing]', () => { } PrepareOutgoing.__set__('Logger', mockLogger) PrepareOutgoing.checkDecrypted(true, { transaction: 1, decrypted: { - a: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', - n: 1000, + n: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', + v: 1000, s: 'XYZ', } }) }) + it('should get the encrypted data, set the time delay to 0 proceed to test it', (done) => { + PrepareOutgoing.testDecrypted = (parsedDecrypted, parsedTransaction) => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(parsedDecrypted).toBe(decrypted) + expect(parsedTransaction).toBe(transaction) + expect(parsedDecrypted.t).toBe(0) + done() + } + const mockLogger = { + writeLog: sinon.spy(), + } + PrepareOutgoing.runtime = { + settings: { + maxAmount: 10000, + secret: '5rgXsNYQ9y4lArEmWA1Wrh9ztUmoG2vZBx1SB1FnZX', + }, + } + const decrypted = { + n: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', + v: 1000, + s: '5rgXsNYQ9y4lArEmWA1Wrh9ztUmoG2vZBx1SB1FnZX', + } + const transaction = { + to: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', + amount: 100, + } + PrepareOutgoing.__set__('Logger', mockLogger) + PrepareOutgoing.checkDecrypted(true, { transaction, decrypted }) + }) it('should get the encrypted data and proceed to test it', (done) => { PrepareOutgoing.testDecrypted = (parsedDecrypted, parsedTransaction) => { sinon.assert.notCalled(mockLogger.writeLog) expect(parsedDecrypted).toBe(decrypted) expect(parsedTransaction).toBe(transaction) + expect(parsedDecrypted.t).toBe(20) done() } const mockLogger = { @@ -409,9 +480,10 @@ describe('[PrepareOutgoing]', () => { }, } const decrypted = { - a: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', - n: 1000, + n: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', + v: 1000, s: '5rgXsNYQ9y4lArEmWA1Wrh9ztUmoG2vZBx1SB1FnZX', + t: 20, } const transaction = { to: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', @@ -422,7 +494,7 @@ describe('[PrepareOutgoing]', () => { }) }) describe('(testDecrypted)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions PrepareOutgoing = rewire('../src/lib/PrepareOutgoing') }) it('should fail to try and validate the address', (done) => { @@ -479,6 +551,40 @@ describe('[PrepareOutgoing]', () => { PrepareOutgoing.__set__('Logger', mockLogger) PrepareOutgoing.testDecrypted(decrypted, transaction) }) + it('should detect valid address but its not reached the required blockheight', (done) => { + PrepareOutgoing.processTransaction = () => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(PrepareOutgoing.runtime.currentBatch).toEqual([]) + expect(PrepareOutgoing.runtime.currentPending).toEqual([2, 3]) + done() + } + const mockLogger = { + writeLog: sinon.spy(), + } + const mockClient = { + validateAddress: () => { return Promise.resolve({ isvalid: true }) }, + } + const decrypted = { + n: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', + v: 1000, + s: '5rgXsNYQ9y4lArEmWA1Wrh9ztUmoG2vZBx1SB1FnZX', + t: 10000, + } + const transaction = { + to: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', + amount: 100, + } + PrepareOutgoing.runtime = { + navClient: mockClient, + navBalance: 50000, + sumPending: 0, + currentBatch: [], + currentPending: [1, 2, 3], + currentBlockHeight: 1000, + } + PrepareOutgoing.__set__('Logger', mockLogger) + PrepareOutgoing.testDecrypted(decrypted, transaction) + }) it('should detect valid address and add it to the list', (done) => { PrepareOutgoing.processTransaction = () => { sinon.assert.notCalled(mockLogger.writeLog) @@ -495,9 +601,10 @@ describe('[PrepareOutgoing]', () => { validateAddress: () => { return Promise.resolve({ isvalid: true }) }, } const decrypted = { - a: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', - n: 1000, + n: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', + v: 1000, s: '5rgXsNYQ9y4lArEmWA1Wrh9ztUmoG2vZBx1SB1FnZX', + t: 10000, } const transaction = { to: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', @@ -509,6 +616,7 @@ describe('[PrepareOutgoing]', () => { sumPending: 0, currentBatch: [], currentPending: [1, 2, 3], + currentBlockHeight: 10000, } PrepareOutgoing.__set__('Logger', mockLogger) PrepareOutgoing.testDecrypted(decrypted, transaction) @@ -521,8 +629,8 @@ describe('[PrepareOutgoing]', () => { validateAddress: () => { return Promise.resolve({ isvalid: true }) }, } const decrypted = { - a: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', - n: 1000, + n: 'NKxkTEjTLARTUq4tz2i8Gzho8pHDYLLmWj', + v: 1000, s: '5rgXsNYQ9y4lArEmWA1Wrh9ztUmoG2vZBx1SB1FnZX', } const transaction = { diff --git a/test/ProcessIncoming.spec.js b/test/ProcessIncoming.spec.js index dd084b4..001a421 100644 --- a/test/ProcessIncoming.spec.js +++ b/test/ProcessIncoming.spec.js @@ -3,340 +3,387 @@ const expect = require('expect') const rewire = require('rewire') const sinon = require('sinon') -const config = require('config') - -const globalSettings = config.get('GLOBAL') -const privateSettings = require('../src/settings/private.settings.json') let ProcessIncoming = rewire('../src/lib/ProcessIncoming') let mockLogger = { - writeLog: sinon.spy(), -} - -let mockRuntime = { - currentBatch: [], - settings: {setting: true}, - subClient: {test: true}, - navClient: {test: true}, - outgoingPubKey: '123443', - subAddresses: [], - transactionsToReturn: [], - successfulSubTransactions: [], - remainingTransactions: [], + writeLog: sinon.spy(), } beforeEach(() => { - ProcessIncoming = rewire('../src/lib/ProcessIncoming') - mockLogger = { - writeLog: sinon.spy() - } - mockRuntime = {} + ProcessIncoming = rewire('../src/lib/ProcessIncoming') + mockLogger = { + writeLog: sinon.spy(), + } }) describe('[ProcessIncoming]', () => { - describe('(run)', () => { - it('should fail on params', (done) => { - const callback = (success, data) => { - expect(success).toBe(false) - expect(data.message).toBeA('string') - sinon.assert.calledOnce(mockLogger.writeLog) - done() - } + describe('(run)', () => { + it('should fail on params', (done) => { + const callback = (success) => { + expect(success).toBe(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PROI_001') + done() + } - ProcessIncoming.__set__('Logger', mockLogger) - ProcessIncoming.run({junkParam: 1234}, callback) - }) - it('should callback with success when remainingTransactions < 1', (done) => { - const callback = (success, data) => { - expect(success).toBe(true) - expect(data.successfulSubTransactions.length).toBe(0) - expect(data.transactionsToReturn.length).toBe(0) - done() - } - const mockOptions = { - currentBatch: [], - settings: {setting: true}, - subClient: {test: true}, - navClient: {test: true}, - outgoingPubKey: '123443', - subAddresses: [], - } - ProcessIncoming.__set__('Logger', mockLogger) - ProcessIncoming.run(mockOptions, callback) - }) + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.run({ junkParam: 1234 }, callback) }) - describe('(transActionFailed)', () => { - it('should transfer policy from remainingTransactions to TransactionsToReturn when failed', (done) => { - const callback = (success, data) => { - } - mockRuntime = { - callback, - currentBatch: [], - settings: {setting: true}, - subClient: {test: true}, - navClient: {test: true}, - outgoingPubKey: '123443', - subAddresses: [], - transactionsToReturn: [], - successfulSubTransactions: [], - remainingTransactions: [], - } - ProcessIncoming.__set__('Logger', mockLogger) - ProcessIncoming.runtime = mockRuntime - ProcessIncoming.transactionFailed() - expect(ProcessIncoming.runtime.remainingTransactions.length).toBe(0) - expect(ProcessIncoming.runtime.transactionsToReturn.length).toBe(1) - done() - }) + it('should fail to get the current block height', (done) => { + const callback = (success) => { + expect(success).toBe(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PROI_001A') + done() + } + const mockOptions = { + currentBatch: [], + settings: { setting: true }, + subClient: { test: true }, + navClient: { + getBlockCount: () => { + return Promise.reject({ err: { code: -21 } }) + }, + }, + outgoingPubKey: '123443', + subAddresses: [], + currentFlattened: [], + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.run(mockOptions, callback) }) - describe('(checkDecrypted)', () => { - it('should log a message out when success is false', (done) => { - const mockTransactionFailed = () => { - sinon.assert.calledOnce(mockLogger.writeLog) - done() - } - ProcessIncoming.transactionFailed = mockTransactionFailed - ProcessIncoming.__set__('Logger', mockLogger) - ProcessIncoming.checkDecrypted(false, {transaction:true,data:true}) - }) - it('should log a message out when isValid is false', (done) => { - const callback = (success, data) => { - } - const addressInfo = {isvalid:false} - mockRuntime = { - callback, - currentBatch: [], - settings: {setting: true}, - subClient: {test: true}, - navClient: { - validateAddress: () => { - return Promise.resolve(addressInfo) - }, - test: true - }, - outgoingPubKey: '123443', - subAddresses: [], - transactionsToReturn: [], - successfulSubTransactions: [], - remainingTransactions: [], - } - ProcessIncoming.runtime = mockRuntime - ProcessIncoming.transactionFailed = () => { - sinon.assert.calledOnce(mockLogger.writeLog) - done() - } - ProcessIncoming.__set__('Logger', mockLogger) - ProcessIncoming.checkDecrypted(true, {transaction:true,decrypted:true}) - }) - it('should log a message out when validateAddress call comes back false', (done) => { - const callback = (success, data) => { - } - mockRuntime = { - callback, - currentBatch: [], - settings: {setting: true}, - subClient: {test: true}, - navClient: { - validateAddress: () => { - return Promise.reject({message:'mock failure'}) - }, - test: true - }, - outgoingPubKey: '123443', - subAddresses: [], - transactionsToReturn: [], - successfulSubTransactions: [], - remainingTransactions: [], - } - ProcessIncoming.runtime = mockRuntime - ProcessIncoming.transactionFailed = () => { - sinon.assert.calledOnce(mockLogger.writeLog) - done() - } - ProcessIncoming.__set__('Logger', mockLogger) - ProcessIncoming.checkDecrypted(true, {transaction:true,decrypted:true}) - }) - + it('should set the variables into runtime and call processPending', (done) => { + ProcessIncoming.processPending = () => { + expect(ProcessIncoming.runtime.callback).toBe(callback) + expect(ProcessIncoming.runtime.currentBatch).toBe(mockOptions.currentBatch) + expect(ProcessIncoming.runtime.remainingTxGroups).toBe(mockOptions.currentBatch) + expect(ProcessIncoming.runtime.settings).toBe(mockOptions.settings) + expect(ProcessIncoming.runtime.subClient).toBe(mockOptions.subClient) + expect(ProcessIncoming.runtime.navClient).toBe(mockOptions.navClient) + expect(ProcessIncoming.runtime.outgoingPubKey).toBe(mockOptions.outgoingPubKey) + expect(ProcessIncoming.runtime.subAddresses).toBe(mockOptions.subAddresses) + expect(ProcessIncoming.runtime.currentFlattened).toBe(mockOptions.currentFlattened) + expect(ProcessIncoming.runtime.currentBlockHeight).toBe(1000) + done() + } + const callback = () => {} + const mockOptions = { + currentBatch: [], + settings: { setting: true }, + subClient: { test: true }, + navClient: { + getBlockCount: () => { + return Promise.resolve(1000) + }, + }, + outgoingPubKey: '123443', + subAddresses: [], + currentFlattened: [], + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.run(mockOptions, callback) }) - describe('(reEncryptAddress)', ()=> { - it('should log a message when the encryption fails', (done) => { - const callback = (success, data) => { - } - mockRuntime = { - callback, - currentBatch: [], - settings: {setting: true}, - subClient: {test: true}, - navClient: { - validateAddress: () => { - return Promise.reject({message:'mock failure'}) - }, - test: true - }, - outgoingPubKey: { - encrypt: () => {return '12345'} - }, - subAddresses: [], - transactionsToReturn: [], - successfulSubTransactions: [], - remainingTransactions: [], - } - ProcessIncoming.runtime = mockRuntime - ProcessIncoming.__set__('Logger', mockLogger) - ProcessIncoming.reEncryptAddress(true, {transaction:true,decrypted:true}, 0) - sinon.assert.calledOnce(mockLogger.writeLog) - done() - }) - it('should log a message when the counter exceeds the limit', (done) => { - const callback = (success, data) => { - } - mockRuntime = { - callback, - currentBatch: [], - settings: {setting: true}, - subClient: {test: true}, - navClient: { - validateAddress: () => { - return Promise.reject({message:'mock failure'}) - }, - test: true - }, - outgoingPubKey: { - encrypt: () => {return '00000000'} - }, - subAddresses: [], - transactionsToReturn: [], - successfulSubTransactions: [], - remainingTransactions: [], - } - ProcessIncoming.runtime = mockRuntime - ProcessIncoming.__set__('Logger', mockLogger) - ProcessIncoming.reEncryptAddress(true, {transaction:true,decrypted:true}, 11) - sinon.assert.calledOnce(mockLogger.writeLog) - done() - }) - it('should log a message when the counter exceeds the limit', (done) => { - const callback = (success, data) => { - } - mockRuntime = { - callback, - currentBatch: [], - settings: {setting: true}, - subClient: {test: true}, - navClient: { - validateAddress: () => { - return Promise.reject({message:'mock failure'}) - }, - test: true - }, - outgoingPubKey: { - encrypt: () => {return '00000000'} - }, - subAddresses: [], - transactionsToReturn: [], - successfulSubTransactions: [], - remainingTransactions: [], - } - ProcessIncoming.runtime = mockRuntime - ProcessIncoming.__set__('Logger', mockLogger) - ProcessIncoming.reEncryptAddress(true, {transaction:true,decrypted:true}, 11) - sinon.assert.calledOnce(mockLogger.writeLog) - done() - }) + }) + describe('(processPending)', () => { + it('should callback with success when remainingTxGroups < 1', (done) => { + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.successfulTxGroups).toEqual(['1234']) + expect(data.txGroupsToReturn).toEqual('2345') + done() + } + ProcessIncoming.runtime = { + remainingTxGroups: {}, + successfulTxGroups: ['1234'], + txGroupsToReturn: ['2345'], + callback, + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.processPending() }) - describe('(sentSubToOutgoing)', ()=> { - it('should log a message out when success is false', (done) => { - const mockTransactionFailed = () => { - sinon.assert.calledOnce(mockLogger.writeLog) - done() - } - ProcessIncoming.transactionFailed = mockTransactionFailed - ProcessIncoming.__set__('Logger', mockLogger) - ProcessIncoming.sentSubToOutgoing(false, {transaction:true,data:true}) - }) - it('should log a message out when no data is false', (done) => { - const mockTransactionFailed = () => { - sinon.assert.calledOnce(mockLogger.writeLog) - done() - } - ProcessIncoming.transactionFailed = mockTransactionFailed - ProcessIncoming.__set__('Logger', mockLogger) - ProcessIncoming.sentSubToOutgoing(true, {sendOutcome:false,data:true}) - }) - it('should remove data from subAddresses and remainingTransactions and add to successfulSubTransactions', (done) => { - const mockTransactionFailed = () => { - sinon.assert.calledOnce(mockLogger.writeLog) - done() - } - ProcessIncoming.transactionFailed = mockTransactionFailed - ProcessIncoming.__set__('Logger', mockLogger) - const callback = (success, data) => { - } - mockRuntime = { - callback, - currentBatch: [], - settings: {setting: true}, - subClient: {test: true}, - navClient: { - validateAddress: () => { - return Promise.reject({message:'mock failure'}) - }, - test: true - }, - outgoingPubKey: { - encrypt: () => {return '00000000'} - }, - subAddresses: ['1234'], - transactionsToReturn: [], - successfulSubTransactions: [], - remainingTransactions: ['1234'], - } - ProcessIncoming.runtime = mockRuntime - ProcessIncoming.sentSubToOutgoing(true, {sendOutcome:true,transaction:'1234'}) - expect(mockRuntime.subAddresses.length).toBe(0) - expect(mockRuntime.remainingTransactions.length).toBe(0) - expect(mockRuntime.successfulSubTransactions.length).toBe(1) - done() - - }) + it('should call proceed and call processPartial', (done) => { + ProcessIncoming.runtime = { + currentFlattened: { + QWE: [100, 100, 10, 10, 10, 1], + ASD: [100, 100, 100, 10, 10, 1], + ZXC: [1000, 100, 100, 10, 10, 1], + }, + remainingTxGroups: [ + { unique: 'ASD', destination: '234', timeDelay: 20 }, + { unique: 'QWE', destination: '123', timeDelay: 30 }, + { unique: 'ZXC', destination: '345', timeDelay: 40 }, + ], + } + ProcessIncoming.processPartial = () => { + sinon.assert.notCalled(mockLogger.writeLog) + expect(ProcessIncoming.runtime.destination).toBe('234') + expect(ProcessIncoming.runtime.maxDelay).toBe(20) + expect(ProcessIncoming.runtime.remainingFlattened).toEqual([100, 100, 100, 10, 10, 1]) + done() + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.processPending() }) - describe('makeSubchainTx', (done)=> { - it('should call sendToAddress', (done) => { - const callback = (success, data) => { - } - mockRuntime = { - callback, - currentBatch: [], - settings: {setting: true}, - subClient: {test: true}, - navClient: { - validateAddress: () => { - return Promise.reject({message:'mock failure'}) - }, - test: true - }, - outgoingPubKey: { - encrypt: () => {return '00000000'} - }, - subAddresses: ['1234'], - transactionsToReturn: [], - successfulSubTransactions: [], - remainingTransactions: ['1234'], - } - ProcessIncoming.runtime = mockRuntime - const mockSendToAddress = { - send: sinon.spy() - } - ProcessIncoming.__set__('SendToAddress',mockSendToAddress) - ProcessIncoming.__set__('Logger', mockLogger) - ProcessIncoming.makeSubchainTx('test','test') - sinon.assert.calledOnce(mockSendToAddress.send) - done() - }) + }) + describe('(processPartial)', () => { + it('should call processPending when no more partials to process', (done) => { + ProcessIncoming.runtime = { + remainingFlattened: [], + remainingTxGroups: [{ txid: 'QWE', amount: 100 }, { txid: 'ASD', amount: 100 }], + successfulTxGroups: [], + } + ProcessIncoming.processPending = () => { + expect(ProcessIncoming.runtime.remainingTxGroups).toEqual([{ txid: 'ASD', amount: 100 }]) + expect(ProcessIncoming.runtime.successfulTxGroups).toEqual([{ txid: 'QWE', amount: 100 }]) + sinon.assert.notCalled(mockLogger.writeLog) + done() + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.processPartial() }) + it('should call reEncryptAddress when still more partials to process', (done) => { + ProcessIncoming.runtime = { + remainingFlattened: [100, 100], + remainingTxGroups: [{ txid: 'QWE', amount: 100 }, { txid: 'ASD', amount: 100 }], + successfulTxGroups: [], + destination: 'ABC', + maxDelay: 120, + } + ProcessIncoming.reEncryptAddress = (destination, maxDelay, transaction, flattened, counter) => { + expect(destination).toBe('ABC') + expect(maxDelay).toBe(120) + expect(transaction).toEqual({ txid: 'QWE', amount: 100 }) + expect(flattened).toBe(100) + expect(counter).toBe(0) + sinon.assert.notCalled(mockLogger.writeLog) + done() + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.processPartial() + }) + }) + describe('(partialFailed)', () => { + it('should call the logger and a false because it has processed some partials', (done) => { + ProcessIncoming.runtime = { + currentFlattened: { + QWE: [100, 100, 10, 10, 10, 1], + ASD: [100, 100, 100, 10, 10, 1], + ZXC: [1000, 100, 100, 10, 10, 1], + }, + remainingFlattened: [100, 100, 10, 10, 1], + remainingTxGroups: [ + { unique: 'ASD', destination: '234', timeDelay: 20 }, + { unique: 'QWE', destination: '123', timeDelay: 30 }, + { unique: 'ZXC', destination: '345', timeDelay: 40 }, + ], + callback: (success, data) => { + expect(success).toBe(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PROI_009') + expect(data.partialFailure).toBe(true) + done() + }, + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.partialFailed() + }) + it('should call try the next group and push the failed complete group to the return pile', (done) => { + ProcessIncoming.runtime = { + currentFlattened: { + QWE: [100, 100, 10, 10, 10, 1], + ASD: [100, 100, 100, 10, 10, 1], + ZXC: [1000, 100, 100, 10, 10, 1], + }, + remainingFlattened: [100, 100, 100, 10, 10, 1], + remainingTxGroups: [ + { unique: 'ASD', destination: '234', timeDelay: 20 }, + { unique: 'QWE', destination: '123', timeDelay: 30 }, + { unique: 'ZXC', destination: '345', timeDelay: 40 }, + ], + txGroupsToReturn: [ + { unique: 'LKJ', destination: '123', timeDelay: 30 }, + { unique: 'POI', destination: '345', timeDelay: 40 }, + ], + } + ProcessIncoming.processPending = () => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PROI_009A') + expect(ProcessIncoming.runtime.remainingTxGroups).toEqual([ + { unique: 'QWE', destination: '123', timeDelay: 30 }, + { unique: 'ZXC', destination: '345', timeDelay: 40 }, + ]) + expect(ProcessIncoming.runtime.txGroupsToReturn).toEqual([ + { unique: 'LKJ', destination: '123', timeDelay: 30 }, + { unique: 'POI', destination: '345', timeDelay: 40 }, + { unique: 'ASD', destination: '234', timeDelay: 20 }, + ]) + done() + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.partialFailed() + }) + }) + describe('(reEncryptAddress)', () => { + it('should log a message when the encryption fails by exception', (done) => { + const Exception = () => 'manual error' + ProcessIncoming.runtime = { + outgoingPubKey: { + encrypt: () => { + throw new Exception() + }, + }, + } + ProcessIncoming.partialFailed = (transaction) => { + expect(transaction).toEqual({ txid: 'ABC', amount: 1000 }) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PROI_007') + done() + } + const mockPrivateSettings = { + encryptionOutput: { + OUTGOING: 5, + }, + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.__set__('privateSettings', mockPrivateSettings) + ProcessIncoming.reEncryptAddress('XYZ', 120, { txid: 'ABC', amount: 1000 }, 100, 0) + }) + it('should log a message when the encryption is the wrong length', (done) => { + ProcessIncoming.runtime = { + outgoingPubKey: { + encrypt: () => { return '12345' }, + }, + currentBlockHeight: 1000, + settings: { + secret: 'ABCDEFG', + }, + } + const mockPrivateSettings = { + encryptionOutput: { + OUTGOING: 177, + }, + maxEncryptionAttempts: 10, + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.__set__('privateSettings', mockPrivateSettings) + ProcessIncoming.reEncryptAddress('XYZ', 120, { txid: 'ABC', amount: 1000 }, 100, 0) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PROI_005') + done() + }) + it('should log a message when the max attempts is reached', (done) => { + ProcessIncoming.runtime = { + outgoingPubKey: { + encrypt: () => { return '12345' }, + }, + currentBlockHeight: 1000, + settings: { + secret: 'ABCDEFG', + }, + } + const mockPrivateSettings = { + encryptionOutput: { + OUTGOING: 177, + }, + maxEncryptionAttempts: 10, + } + ProcessIncoming.partialFailed = (transaction) => { + expect(transaction).toEqual({ txid: 'ABC', amount: 1000 }) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PROI_006') + done() + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.__set__('privateSettings', mockPrivateSettings) + ProcessIncoming.reEncryptAddress('XYZ', 120, { txid: 'ABC', amount: 1000 }, 100, 10) + }) + it('should call makeSubchainTx when everything okay', (done) => { + ProcessIncoming.runtime = { + outgoingPubKey: { + encrypt: () => { + return '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456' + + '789012345678901234567890123456789012345678901234567890123456789012345678901234567890==' }, + }, + currentBlockHeight: 1000, + settings: { + secret: 'ABCDEFG', + }, + } + const mockPrivateSettings = { + encryptionOutput: { + OUTGOING: 172, + }, + maxEncryptionAttempts: 0, + } + ProcessIncoming.makeSubchainTx = (encrypted, transaction) => { + expect(encrypted).toEqual(ProcessIncoming.runtime.outgoingPubKey.encrypt()) + expect(transaction).toEqual({ txid: 'ABC', amount: 1000 }) + done() + } - - + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.__set__('privateSettings', mockPrivateSettings) + ProcessIncoming.reEncryptAddress('XYZ', 120, { txid: 'ABC', amount: 1000 }, 100, 10) + }) + }) + describe('makeSubchainTx', () => { + it('should call sendToAddress', (done) => { + ProcessIncoming.runtime = { + subClient: { test: true }, + subAddresses: ['1234'], + } + const mockPrivateSettings = { + subCoinsPerTx: 1, + } + const mockSendToAddress = { + send: (options, callback) => { + expect(options.client).toEqual(ProcessIncoming.runtime.subClient) + expect(options.address).toBe('1234') + expect(options.amount).toBe(1) + expect(options.encrypted).toBe('ASD') + expect(options.transaction).toBe('ZXC') + expect(callback).toBe(ProcessIncoming.sentSubToOutgoing) + done() + }, + } + ProcessIncoming.__set__('SendToAddress', mockSendToAddress) + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.__set__('privateSettings', mockPrivateSettings) + ProcessIncoming.makeSubchainTx('ASD', 'ZXC') + }) + }) + describe('(sentSubToOutgoing)', () => { + it('should log a message out when success is false', (done) => { + ProcessIncoming.partialFailed = () => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PROI_008') + done() + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.sentSubToOutgoing(false, { transaction: true, data: true }) + }) + it('should log a message out when no data is false', (done) => { + ProcessIncoming.partialFailed = () => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PROI_008') + done() + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.sentSubToOutgoing(true, { sendOutcome: false, data: true }) + }) + it('should move on to the next partial transaction to send', (done) => { + ProcessIncoming.processPartial = () => { + expect(ProcessIncoming.runtime.subAddresses).toEqual(['2345', '3456']) + expect(ProcessIncoming.runtime.remainingFlattened).toEqual([1, 1.1234]) + done() + } + ProcessIncoming.__set__('Logger', mockLogger) + ProcessIncoming.runtime = { + subAddresses: ['1234', '2345', '3456'], + remainingFlattened: [100, 1, 1.1234], + } + ProcessIncoming.sentSubToOutgoing(true, { sendOutcome: true, transaction: '1234' }) + }) + }) }) - diff --git a/test/ProcessIncoming.spec.test.js b/test/ProcessIncoming.spec.test.js deleted file mode 100644 index e69de29..0000000 diff --git a/test/ProcessOutgoing.spec.js b/test/ProcessOutgoing.spec.js index 496078d..ae8fc6e 100644 --- a/test/ProcessOutgoing.spec.js +++ b/test/ProcessOutgoing.spec.js @@ -52,7 +52,7 @@ describe('[ProcessOutgoing]', () => { }) }) describe('(processPending)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ProcessOutgoing = rewire('../src/lib/ProcessOutgoing') }) it('should run the callback when no more transactions are left to process', (done) => { @@ -74,46 +74,37 @@ describe('[ProcessOutgoing]', () => { ProcessOutgoing.__set__('Logger', mockLogger) ProcessOutgoing.processPending() }) - it('should reset the partial transaction array and create random transactions', (done) => { - const remainingTransactions = [ - { - decrypted: { - n: 10, - a: 'XYZ', - }, - }, - { - decrypted: { - n: 100, - a: 'XYZ', - }, - }, - ] + it('should run the SendToAddress.send when it has remaining transactions to process', (done) => { ProcessOutgoing.runtime = { - remainingTransactions, - successfulTransactions: [], - failedTransactions: [], - callback: () => {}, + remainingTransactions: [ + { txid: 1234, decrypted: { n: 'ASDF', v: 333 } }, + { txid: 5678, decrypted: { n: 'QWER', v: 222 } }, + ], + successfulTransactions: [1, 2, 3], + failedTransactions: [4, 5, 6], + navClient: { getInfo: true }, } - const RandomizeTransactions = { - outgoing: (settings, callback) => { - expect(settings.transaction).toBe(remainingTransactions[0]) - expect(settings.amount).toBe(10) - expect(settings.address).toBe('XYZ') - expect(callback).toBe(ProcessOutgoing.amountsRandomized) + const SendToAddress = { + send: (options, callback) => { + expect(callback).toBe(ProcessOutgoing.sentNav) + expect(options.client).toBe(ProcessOutgoing.runtime.navClient) + expect(options.address).toBe('ASDF') + expect(options.amount).toBe(333) + expect(options.transaction).toEqual({ txid: 1234, decrypted: { n: 'ASDF', v: 333 } }) done() }, } + const mockLogger = { writeLog: sinon.spy(), } ProcessOutgoing.__set__('Logger', mockLogger) - ProcessOutgoing.__set__('RandomizeTransactions', RandomizeTransactions) + ProcessOutgoing.__set__('SendToAddress', SendToAddress) ProcessOutgoing.processPending() }) }) describe('(transactionFailed)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ProcessOutgoing = rewire('../src/lib/ProcessOutgoing') }) it('should trim the failed transaction and move onto the next one', (done) => { @@ -138,43 +129,8 @@ describe('[ProcessOutgoing]', () => { ProcessOutgoing.transactionFailed() }) }) - describe('(amountsRandomized)', () => { - before(() => { // reset the rewired functions - ProcessOutgoing = rewire('../src/lib/ProcessOutgoing') - }) - it('should fail to randomize the transactions', (done) => { - const mockLogger = { - writeLog: sinon.spy(), - } - - ProcessOutgoing.transactionFailed = () => { - sinon.assert.calledOnce(mockLogger.writeLog) - sinon.assert.calledWith(mockLogger.writeLog, 'PROO_002') - done() - } - - ProcessOutgoing.__set__('Logger', mockLogger) - ProcessOutgoing.amountsRandomized(false, { junkParam: 1234 }) - }) - it('should randomize the transactions and call createNavTransactions', (done) => { - const mockLogger = { - writeLog: sinon.spy(), - } - - ProcessOutgoing.runtime = {} - - ProcessOutgoing.createNavTransactions = () => { - expect(ProcessOutgoing.runtime.partialTransactions).toEqual([1, 2, 3]) - sinon.assert.notCalled(mockLogger.writeLog) - done() - } - - ProcessOutgoing.__set__('Logger', mockLogger) - ProcessOutgoing.amountsRandomized(true, { partialTransactions: [1, 2, 3] }) - }) - }) describe('(mockSend)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ProcessOutgoing = rewire('../src/lib/ProcessOutgoing') }) it('should pretend a successful send happened for testing purposes', (done) => { @@ -184,15 +140,15 @@ describe('[ProcessOutgoing]', () => { const tx1 = { transaction: { txid: '1234' }, decrypted: { - n: 10, - a: 'XYZ', + v: 10, + n: 'XYZ', }, } const tx2 = { transaction: { txid: '4321' }, decrypted: { - n: 100, - a: 'ZYX', + v: 100, + n: 'ZYX', }, } const remainingTransactions = [ @@ -218,188 +174,146 @@ describe('[ProcessOutgoing]', () => { ProcessOutgoing.mockSend() }) }) - describe('(createNavTransactions)', () => { - before(() => { // reset the rewired functions + describe('(sentNav)', () => { + beforeEach(() => { // reset the rewired functions ProcessOutgoing = rewire('../src/lib/ProcessOutgoing') }) - it('should still have pending partials and call the send function', (done) => { - const mockLogger = { - writeLog: sinon.spy(), - } - const tx1 = { - transaction: { txid: '1234' }, - decrypted: { - n: 10, - a: 'XYZ', - }, - } - const remainingTransactions = [ - tx1, - ] - ProcessOutgoing.runtime = { - remainingTransactions, - partialTransactions: [1, 2, 3], - successfulTransactions: [], - failedTransactions: [], - navClient: () => {}, - } - - const SendToAddress = { - send: (options, callback) => { - expect(options.client).toBe(ProcessOutgoing.runtime.navClient) - expect(options.address).toBe('XYZ') - expect(options.amount).toBe(1) - expect(options.transaction).toBe(tx1) - expect(callback).toBe(ProcessOutgoing.sentPartialNav) - sinon.assert.notCalled(mockLogger.writeLog) - done() - }, - } - - ProcessOutgoing.__set__('Logger', mockLogger) - ProcessOutgoing.__set__('SendToAddress', SendToAddress) - ProcessOutgoing.createNavTransactions(false, { junkParam: 1234 }) - }) - it('should recognize that all the partials have been sent and move onto the next record', (done) => { + it('should fail to send partial nav (returned false) and try the next one', (done) => { const mockLogger = { writeLog: sinon.spy(), } const tx1 = { transaction: { txid: '1234' }, decrypted: { - n: 10, - a: 'XYZ', + v: 10, + n: 'XYZ', }, } const tx2 = { transaction: { txid: '4321' }, decrypted: { - n: 100, - a: 'ZYX', + v: 100, + n: 'ZYX', }, } const remainingTransactions = [ tx1, tx2, ] - ProcessOutgoing.runtime = { - remainingTransactions, - partialTransactions: [], - successfulTransactions: [], - failedTransactions: [], - callback: () => {}, - } - ProcessOutgoing.processPending = () => { - expect(ProcessOutgoing.runtime.successfulTransactions).toEqual([{ transaction: tx1.transaction }]) + expect(ProcessOutgoing.runtime.failedTransactions).toEqual([tx1]) expect(ProcessOutgoing.runtime.remainingTransactions).toEqual([tx2]) + expect(ProcessOutgoing.runtime.successfulTransactions).toEqual([]) sinon.assert.calledOnce(mockLogger.writeLog) - sinon.assert.calledWith(mockLogger.writeLog, 'PROO_003') + sinon.assert.calledWith(mockLogger.writeLog, 'PROO_004') done() } + ProcessOutgoing.runtime = { + remainingTransactions, + failedTransactions: [], + successfulTransactions: [], + callback: () => {}, + } ProcessOutgoing.__set__('Logger', mockLogger) - ProcessOutgoing.createNavTransactions(false, { junkParam: 1234 }) + ProcessOutgoing.sentNav(false, { junkParam: 1234 }) }) - }) - describe('(sentPartialNav)', () => { - before(() => { // reset the rewired functions - ProcessOutgoing = rewire('../src/lib/ProcessOutgoing') - }) - it('should fail to send partial nav (returned false) and exit the current process', (done) => { + it('should fail to send partial nav (bad data) and try the next one', (done) => { const mockLogger = { writeLog: sinon.spy(), } const tx1 = { transaction: { txid: '1234' }, decrypted: { - n: 10, - a: 'XYZ', + v: 10, + n: 'XYZ', }, } const tx2 = { transaction: { txid: '4321' }, decrypted: { - n: 100, - a: 'ZYX', + v: 100, + n: 'ZYX', }, } const remainingTransactions = [ tx1, tx2, ] + ProcessOutgoing.processPending = () => { + expect(ProcessOutgoing.runtime.failedTransactions).toEqual([tx1]) + expect(ProcessOutgoing.runtime.remainingTransactions).toEqual([tx2]) + expect(ProcessOutgoing.runtime.successfulTransactions).toEqual([]) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PROO_004') + done() + } ProcessOutgoing.runtime = { remainingTransactions, - partialTransactions: [2, 3, 4], failedTransactions: [], - callback: (success, data) => { - expect(success).toBe(false) - expect(data.failedTransaction).toBe(tx1) - expect(data.remainingPartials).toEqual([2, 3, 4]) - sinon.assert.calledOnce(mockLogger.writeLog) - sinon.assert.calledWith(mockLogger.writeLog, 'PROO_004') - done() - }, + successfulTransactions: [], + callback: () => {}, } ProcessOutgoing.__set__('Logger', mockLogger) - ProcessOutgoing.sentPartialNav(false, { junkParam: 1234 }) + ProcessOutgoing.sentNav(true, { junkParam: 1234 }) }) - it('should fail to send partial nav (bad data) and exit the current process', (done) => { + it('should fail to send partial (no data) nav and try the next one', (done) => { const mockLogger = { writeLog: sinon.spy(), } const tx1 = { transaction: { txid: '1234' }, decrypted: { - n: 10, - a: 'XYZ', + v: 10, + n: 'XYZ', }, } const tx2 = { transaction: { txid: '4321' }, decrypted: { - n: 100, - a: 'ZYX', + v: 100, + n: 'ZYX', }, } const remainingTransactions = [ tx1, tx2, ] + ProcessOutgoing.processPending = () => { + expect(ProcessOutgoing.runtime.failedTransactions).toEqual([tx1]) + expect(ProcessOutgoing.runtime.remainingTransactions).toEqual([tx2]) + expect(ProcessOutgoing.runtime.successfulTransactions).toEqual([]) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'PROO_004') + done() + } ProcessOutgoing.runtime = { remainingTransactions, - partialTransactions: [2, 3, 4], failedTransactions: [], - callback: (success, data) => { - expect(success).toBe(false) - expect(data.failedTransaction).toBe(tx1) - expect(data.remainingPartials).toEqual([2, 3, 4]) - sinon.assert.calledOnce(mockLogger.writeLog) - sinon.assert.calledWith(mockLogger.writeLog, 'PROO_004') - done() - }, + successfulTransactions: [], + callback: () => {}, } ProcessOutgoing.__set__('Logger', mockLogger) - ProcessOutgoing.sentPartialNav(true, { junkParam: 1234 }) + ProcessOutgoing.sentNav(true) }) - it('should fail to send partial (no data) nav and exit the current process', (done) => { + it('should successfully send the partial nav and try the next partial', (done) => { const mockLogger = { writeLog: sinon.spy(), } const tx1 = { transaction: { txid: '1234' }, decrypted: { - n: 10, - a: 'XYZ', + v: 10, + n: 'XYZ', }, } const tx2 = { transaction: { txid: '4321' }, decrypted: { - n: 100, - a: 'ZYX', + v: 100, + n: 'ZYX', }, } const remainingTransactions = [ @@ -408,37 +322,20 @@ describe('[ProcessOutgoing]', () => { ] ProcessOutgoing.runtime = { remainingTransactions, - partialTransactions: [2, 3, 4], failedTransactions: [], - callback: (success, data) => { - expect(success).toBe(false) - expect(data.failedTransaction).toBe(tx1) - expect(data.remainingPartials).toEqual([2, 3, 4]) - sinon.assert.calledOnce(mockLogger.writeLog) - sinon.assert.calledWith(mockLogger.writeLog, 'PROO_004') - done() - }, - } - - ProcessOutgoing.__set__('Logger', mockLogger) - ProcessOutgoing.sentPartialNav(true) - }) - it('should successfully send the partial nav and try the next partial', (done) => { - const mockLogger = { - writeLog: sinon.spy(), - } - ProcessOutgoing.runtime = { - partialTransactions: [2, 3, 4], + successfulTransactions: [], } - ProcessOutgoing.createNavTransactions = () => { - expect(ProcessOutgoing.runtime.partialTransactions).toEqual([3, 4]) + ProcessOutgoing.processPending = () => { + expect(ProcessOutgoing.runtime.failedTransactions).toEqual([]) + expect(ProcessOutgoing.runtime.successfulTransactions).toEqual([tx1]) + expect(ProcessOutgoing.runtime.remainingTransactions).toEqual([tx2]) sinon.assert.notCalled(mockLogger.writeLog) done() } ProcessOutgoing.__set__('Logger', mockLogger) - ProcessOutgoing.sentPartialNav(true, { sendOutcome: '1234' }) + ProcessOutgoing.sentNav(true, { sendOutcome: '1234' }) }) }) }) diff --git a/test/RandomizeTransactions.spec.js b/test/RandomizeTransactions.spec.js index 7e77c4a..988a2ed 100644 --- a/test/RandomizeTransactions.spec.js +++ b/test/RandomizeTransactions.spec.js @@ -92,7 +92,7 @@ describe('[RandomizeTransactions]', () => { }) }) describe('(chooseRandomAddresses)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RandomizeTransactions = rewire('../src/lib/RandomizeTransactions') }) it('should fail on params', (done) => { diff --git a/test/RefillOutgoing.spec.js b/test/RefillOutgoing.spec.js index 831b9b5..a7ce97e 100644 --- a/test/RefillOutgoing.spec.js +++ b/test/RefillOutgoing.spec.js @@ -40,7 +40,7 @@ describe('[RefillOutgoing]', () => { }) }) describe('(getUnspent)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RefillOutgoing = rewire('../src/lib/RefillOutgoing') }) it('should fail to get unspent', (done) => { @@ -109,7 +109,7 @@ describe('[RefillOutgoing]', () => { }) }) describe('(holdingFiltered)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RefillOutgoing = rewire('../src/lib/RefillOutgoing') }) it('should have no holding to process', (done) => { @@ -148,6 +148,7 @@ describe('[RefillOutgoing]', () => { sinon.assert.notCalled(mockLogger.writeLog) done() } + RefillOutgoing.runtime = {} const mockLogger = { writeLog: sinon.spy(), } @@ -156,7 +157,7 @@ describe('[RefillOutgoing]', () => { }) }) describe('(processHolding)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RefillOutgoing = rewire('../src/lib/RefillOutgoing') }) it('should run the callback when all holding are processed', (done) => { @@ -194,7 +195,7 @@ describe('[RefillOutgoing]', () => { }) }) describe('(checkIfHoldingIsSpendable)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RefillOutgoing = rewire('../src/lib/RefillOutgoing') }) it('should not process holding transaction because its below the confirmation threshold', (done) => { @@ -240,7 +241,7 @@ describe('[RefillOutgoing]', () => { }) }) describe('(holdingDecrypted)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RefillOutgoing = rewire('../src/lib/RefillOutgoing') }) it('should not process the holding because the encyptedData is bad (returned false)', (done) => { @@ -319,7 +320,7 @@ describe('[RefillOutgoing]', () => { }) it('should figure out how many random transactions to make', (done) => { const transaction = { confirmations: 10, amount: 500, txid: 'ABC' } - const decrypted = '["QWER", "ASDF", "ZXCV", "POIU", "LKJH", "MNBV"]' + const decrypted = ["QWER", "ASDF", "ZXCV", "POIU", "LKJH", "MNBV"] let i = 0 const RandomizeTransactions = { incoming: (options, callback) => { @@ -331,6 +332,11 @@ describe('[RefillOutgoing]', () => { if (i === 100) done() }, } + const hld1 = { confirmations: 0, amount: 100, txid: 'XYZ' } + const hld2 = { confirmations: 10, amount: 500, txid: 'ABC' } + RefillOutgoing.runtime = { + currentHolding: [hld1, hld2], + } const mockLogger = { writeLog: sinon.spy(), } @@ -342,7 +348,7 @@ describe('[RefillOutgoing]', () => { }) }) describe('(checkRandomTransactions)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RefillOutgoing = rewire('../src/lib/RefillOutgoing') }) it('should not receive bad randomized transactions (returned false)', (done) => { @@ -418,7 +424,7 @@ describe('[RefillOutgoing]', () => { }) }) describe('(sendRawRefillTransaction)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RefillOutgoing = rewire('../src/lib/RefillOutgoing') }) it('should create the spent transactions array and call the rawTransaction function', (done) => { @@ -450,7 +456,7 @@ describe('[RefillOutgoing]', () => { }) }) describe('(refillSent)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RefillOutgoing = rewire('../src/lib/RefillOutgoing') }) it('should not fail to send the raw holding transaction (returned false)', (done) => { diff --git a/test/RetrieveSubchainAddresses.spec.js b/test/RetrieveSubchainAddresses.spec.js index 353072c..1e6ad0f 100644 --- a/test/RetrieveSubchainAddresses.spec.js +++ b/test/RetrieveSubchainAddresses.spec.js @@ -26,12 +26,12 @@ describe('[RetrieveSubchainAddresses]', () => { const callback = () => {} const subClient = { getinfo: () => {} } const chosenOutgoing = { ipAddress: '123.123.123.123' } - const currentBatch = [1, 2, 3] + const numAddresses = 10 RetrieveSubchainAddresses.getSubAddresses = () => { expect(RetrieveSubchainAddresses.runtime.callback).toBe(callback) expect(RetrieveSubchainAddresses.runtime.subClient).toBe(subClient) expect(RetrieveSubchainAddresses.runtime.chosenOutgoing).toBe(chosenOutgoing) - expect(RetrieveSubchainAddresses.runtime.currentBatch).toBe(currentBatch) + expect(RetrieveSubchainAddresses.runtime.numAddresses).toBe(numAddresses) sinon.assert.notCalled(mockLogger.writeLog) done() } @@ -39,19 +39,19 @@ describe('[RetrieveSubchainAddresses]', () => { writeLog: sinon.spy(), } RetrieveSubchainAddresses.__set__('Logger', mockLogger) - RetrieveSubchainAddresses.run({ subClient, chosenOutgoing, currentBatch }, callback) + RetrieveSubchainAddresses.run({ subClient, chosenOutgoing, numAddresses }, callback) }) }) describe('(getSubAddresses)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RetrieveSubchainAddresses = rewire('../src/lib/RetrieveSubchainAddresses') }) it('should build the request and send it to the outgoing server', (done) => { const chosenOutgoing = { ipAddress: '123.123.123.123', port: '3000' } - const currentBatch = [1, 2, 3] + const numAddresses = 10 RetrieveSubchainAddresses.runtime = { chosenOutgoing, - currentBatch, + numAddresses, } const mockLogger = { writeLog: sinon.spy(), @@ -60,7 +60,7 @@ describe('[RetrieveSubchainAddresses]', () => { expect(RetrieveSubchainAddresses.runtime.outgoingAddress).toBe(chosenOutgoing.ipAddress + ':' + chosenOutgoing.port) expect(options.form.type).toBe('SUBCHAIN') expect(options.form.account).toBe('OUTGOING') - expect(options.form.num_addresses).toBe(3) + expect(options.form.num_addresses).toBe(10) expect(callback).toBe(RetrieveSubchainAddresses.requestResponse) sinon.assert.notCalled(mockLogger.writeLog) done() @@ -72,10 +72,10 @@ describe('[RetrieveSubchainAddresses]', () => { }) it('should build the request and send it to the outgoing server without port', (done) => { const chosenOutgoing = { ipAddress: '123.123.123.123' } - const currentBatch = [1, 2, 3] + const numAddresses = 50 RetrieveSubchainAddresses.runtime = { chosenOutgoing, - currentBatch, + numAddresses, } const mockLogger = { writeLog: sinon.spy(), @@ -84,7 +84,7 @@ describe('[RetrieveSubchainAddresses]', () => { expect(RetrieveSubchainAddresses.runtime.outgoingAddress).toBe(chosenOutgoing.ipAddress) expect(options.form.type).toBe('SUBCHAIN') expect(options.form.account).toBe('OUTGOING') - expect(options.form.num_addresses).toBe(3) + expect(options.form.num_addresses).toBe(50) expect(callback).toBe(RetrieveSubchainAddresses.requestResponse) sinon.assert.notCalled(mockLogger.writeLog) done() @@ -94,15 +94,15 @@ describe('[RetrieveSubchainAddresses]', () => { RetrieveSubchainAddresses.getSubAddresses() }) describe('(requestResponse)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RetrieveSubchainAddresses = rewire('../src/lib/RetrieveSubchainAddresses') }) it('should get an error from the outgoing server', (done) => { const chosenOutgoing = { ipAddress: '123.123.123.123', port: '3000' } - const currentBatch = [1, 2, 3] + const numAddresses = 55 RetrieveSubchainAddresses.runtime = { chosenOutgoing, - currentBatch, + numAddresses, callback: (success, data) => { expect(success).toBe(false) expect(data.message).toBeA('string') @@ -119,10 +119,10 @@ describe('[RetrieveSubchainAddresses]', () => { }) it('should get no error and continue', (done) => { const chosenOutgoing = { ipAddress: '123.123.123.123', port: '3000' } - const currentBatch = [1, 2, 3] + const numAddresses = 5 RetrieveSubchainAddresses.runtime = { outgoingAddress: chosenOutgoing.ipAddress + ':' + chosenOutgoing.port, - currentBatch, + numAddresses, callback: () => {}, } RetrieveSubchainAddresses.checkOutgoingCanTransact = (body, outgoingAddress) => { @@ -139,7 +139,7 @@ describe('[RetrieveSubchainAddresses]', () => { }) }) describe('(checkOutgoingCanTransact)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RetrieveSubchainAddresses = rewire('../src/lib/RetrieveSubchainAddresses') }) it('should get a non json response from the server and call false callback', (done) => { @@ -242,7 +242,7 @@ describe('[RetrieveSubchainAddresses]', () => { }) }) describe('(checkSubAddresses)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RetrieveSubchainAddresses = rewire('../src/lib/RetrieveSubchainAddresses') }) it('should fail because 0 addresses received from server', (done) => { @@ -263,7 +263,7 @@ describe('[RetrieveSubchainAddresses]', () => { }) it('should fail because less addressses than needed were received from server', (done) => { RetrieveSubchainAddresses.runtime = { - currentBatch: [1, 2, 3], + numAddresses: 10, callback: (success, data) => { expect(success).toBe(false) expect(data.message).toBeA('string') @@ -280,7 +280,7 @@ describe('[RetrieveSubchainAddresses]', () => { }) it('should receive the correct number of addresses and continue to the validator', (done) => { RetrieveSubchainAddresses.runtime = { - currentBatch: [1, 2, 3], + numAddresses: 3, } const NavCoin = { validateAddresses: (options, callback) => { @@ -298,7 +298,7 @@ describe('[RetrieveSubchainAddresses]', () => { }) }) describe('(subAddressesValid)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions RetrieveSubchainAddresses = rewire('../src/lib/RetrieveSubchainAddresses') }) it('should fail from error within the address validator', (done) => { diff --git a/test/ReturnAllToSenders.spec.js b/test/ReturnAllToSenders.spec.js index 7d06a21..6842310 100644 --- a/test/ReturnAllToSenders.spec.js +++ b/test/ReturnAllToSenders.spec.js @@ -40,7 +40,7 @@ describe('[ReturnAllToSenders]', () => { }) }) describe('(fromList)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ReturnAllToSenders = rewire('../src/lib/ReturnAllToSenders') }) it('should fail on params', (done) => { @@ -76,7 +76,7 @@ describe('[ReturnAllToSenders]', () => { }) }) describe('(getUnspent)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ReturnAllToSenders = rewire('../src/lib/ReturnAllToSenders') }) it('should fail to get the unspent', (done) => { @@ -141,7 +141,7 @@ describe('[ReturnAllToSenders]', () => { }) }) describe('(unspentFiltered)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ReturnAllToSenders = rewire('../src/lib/ReturnAllToSenders') }) it('should fail to filter the unspent (returned false)', (done) => { @@ -220,7 +220,7 @@ describe('[ReturnAllToSenders]', () => { }) }) describe('(returnToSender)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ReturnAllToSenders = rewire('../src/lib/ReturnAllToSenders') }) it('should fail because the filtered wasnt an array', (done) => { @@ -287,7 +287,7 @@ describe('[ReturnAllToSenders]', () => { }) }) describe('(returnToSender)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ReturnAllToSenders = rewire('../src/lib/ReturnAllToSenders') }) it('should log because it send returned false and skip the transaction', (done) => { diff --git a/test/ReturnSubnav.spec.js b/test/ReturnSubnav.spec.js index 80ed7f8..522e4b0 100644 --- a/test/ReturnSubnav.spec.js +++ b/test/ReturnSubnav.spec.js @@ -42,7 +42,7 @@ describe('[ReturnSubnav]', () => { }) }) describe('(sendToIncoming)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ReturnSubnav = rewire('../src/lib/ReturnSubnav') }) it('should call the callback when theres no transactions left', (done) => { @@ -84,7 +84,7 @@ describe('[ReturnSubnav]', () => { }) }) describe('(sent)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ReturnSubnav = rewire('../src/lib/ReturnSubnav') }) it('should fail because it couldnt return subnav (returned false)', (done) => { diff --git a/test/ReturnToSender.spec.js b/test/ReturnToSender.spec.js index efa67c2..719deb5 100644 --- a/test/ReturnToSender.spec.js +++ b/test/ReturnToSender.spec.js @@ -68,7 +68,7 @@ describe('[ReturnSubnav]', () => { }) }) describe('(decodeOriginRaw)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ReturnToSender = rewire('../src/lib/ReturnToSender') }) it('should fail to decode the raw transaction', (done) => { @@ -117,7 +117,7 @@ describe('[ReturnSubnav]', () => { }) }) describe('(decodeOriginRaw)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ReturnToSender = rewire('../src/lib/ReturnToSender') }) it('should fail to decode the raw transaction', (done) => { @@ -171,7 +171,7 @@ describe('[ReturnSubnav]', () => { }) }) describe('(decodeOriginInputRaw)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ReturnToSender = rewire('../src/lib/ReturnToSender') }) it('should fail to decode the raw transaction', (done) => { @@ -233,7 +233,7 @@ describe('[ReturnSubnav]', () => { }) }) describe('(buildTransaction)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions ReturnToSender = rewire('../src/lib/ReturnToSender') }) it('should build the transaction and call createRaw', (done) => { diff --git a/test/SelectOutgoing.spec.js b/test/SelectOutgoing.spec.js index db917a0..5344048 100644 --- a/test/SelectOutgoing.spec.js +++ b/test/SelectOutgoing.spec.js @@ -58,7 +58,7 @@ describe('[SelectOutgoing]', () => { }) }) describe('(pickServer)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions SelectOutgoing = rewire('../src/lib/SelectOutgoing') }) it('should fail when it runs out of remotes to try', (done) => { @@ -100,7 +100,7 @@ describe('[SelectOutgoing]', () => { }) }) describe('(testOutgoing)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions SelectOutgoing = rewire('../src/lib/SelectOutgoing') }) it('should make the request to the server', (done) => { @@ -130,7 +130,7 @@ describe('[SelectOutgoing]', () => { }) }) describe('(gotServerResponse)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions SelectOutgoing = rewire('../src/lib/SelectOutgoing') }) it('should fail due to error from the server and try the next one', (done) => { @@ -184,7 +184,7 @@ describe('[SelectOutgoing]', () => { }) }) describe('(gotServerResponse)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions SelectOutgoing = rewire('../src/lib/SelectOutgoing') }) it('should fail due to non json response from the server', (done) => { diff --git a/test/SendRawTransaction.spec.js b/test/SendRawTransaction.spec.js index de3ac48..6009bb7 100644 --- a/test/SendRawTransaction.spec.js +++ b/test/SendRawTransaction.spec.js @@ -8,6 +8,9 @@ let SendRawTransaction = rewire('../src/lib/SendRawTransaction') describe('[SendRawTransaction]', () => { describe('(run)', () => { + beforeEach(() => { // reset the rewired functions + SendRawTransaction = rewire('../src/lib/SendRawTransaction') + }) it('should fail on params', (done) => { const callback = (success, data) => { expect(success).toBe(false) @@ -22,121 +25,187 @@ describe('[SendRawTransaction]', () => { SendRawTransaction.__set__('Logger', mockLogger) SendRawTransaction.createRaw({ junkParam: 1234 }, callback) }) - it('should get the right params and fail to create the transaction without encrypted', (done) => { - const callback = (success, data) => { - expect(success).toBe(false) - expect(data.error.code).toBe(-4) - expect(SendRawTransaction.runtime).toEqual({}) - sinon.assert.calledOnce(mockLogger.writeLog) - sinon.assert.calledWith(mockLogger.writeLog, 'RAW_003') - done() - } + it('should get the right params and run create without encrypted', (done) => { + const callback = () => {} const spentTransactions = [] const outgoingTransactions = {} const client = { createRawTransaction: () => { return Promise.reject({ code: -4 }) }, } - const mockLogger = { writeLog: sinon.spy(), } + SendRawTransaction.create = () => { + expect(SendRawTransaction.runtime.spentTransactions).toBe(spentTransactions) + expect(SendRawTransaction.runtime.outgoingTransactions).toBe(outgoingTransactions) + expect(SendRawTransaction.runtime.client).toBe(client) + expect(SendRawTransaction.runtime.callback).toBe(callback) + expect(SendRawTransaction.runtime.counter).toBe(0) + expect(SendRawTransaction.runtime.encrypted).toBe(false) + sinon.assert.notCalled(mockLogger.writeLog) + done() + } SendRawTransaction.__set__('Logger', mockLogger) SendRawTransaction.createRaw({ spentTransactions, outgoingTransactions, client }, callback) }) - it('should get the right params and fail to create the transaction with encrypted', (done) => { - const callback = (success, data) => { - expect(success).toBe(false) - expect(data.error.code).toBe(-4) - sinon.assert.calledOnce(mockLogger.writeLog) - sinon.assert.calledWith(mockLogger.writeLog, 'RAW_002') - done() - } + it('should get the right params and run create with encrypted', (done) => { + const callback = () => {} const spentTransactions = [] const outgoingTransactions = {} const client = { createRawTransaction: () => { return Promise.reject({ code: -4 }) }, } - const mockLogger = { writeLog: sinon.spy(), } + SendRawTransaction.create = () => { + expect(SendRawTransaction.runtime.spentTransactions).toBe(spentTransactions) + expect(SendRawTransaction.runtime.outgoingTransactions).toBe(outgoingTransactions) + expect(SendRawTransaction.runtime.client).toBe(client) + expect(SendRawTransaction.runtime.callback).toBe(callback) + expect(SendRawTransaction.runtime.counter).toBe(0) + expect(SendRawTransaction.runtime.encrypted).toBe('1234') + sinon.assert.notCalled(mockLogger.writeLog) + done() + } SendRawTransaction.__set__('Logger', mockLogger) SendRawTransaction.createRaw({ spentTransactions, outgoingTransactions, client, encrypted: '1234' }, callback) }) - it('should get the right params and call signRaw (no encrypted)', (done) => { - const callback = () => {} - const spentTransactions = [] - const outgoingTransactions = {} - const client = { - createRawTransaction: () => { return Promise.resolve('RAW_TRANSACTION') }, + }) + describe('(create)', () => { + beforeEach(() => { // reset the rewired functions + SendRawTransaction = rewire('../src/lib/SendRawTransaction') + }) + it('should get the right params and fail to create the transaction without encrypted', (done) => { + const mockLogger = { + writeLog: sinon.spy(), } - SendRawTransaction.signRaw = (options, parsedCallback) => { - expect(parsedCallback).toBe(callback) - expect(options.client).toBe(client) - expect(options.rawTrans).toBe('RAW_TRANSACTION') - sinon.assert.notCalled(mockLogger.writeLog) + SendRawTransaction.runtime = { + spentTransactions: [1, 2, 3], + outgoingTransactions: { x: 1, y: 2, z: 3 }, + client: { + createRawTransaction: () => { return Promise.reject({ code: -4 }) }, + }, + counter: 0, + callback: () => {} + } + + SendRawTransaction.retry = (error) => { + expect(error.code).toBe(-4) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'RAW_003') done() } + SendRawTransaction.__set__('Logger', mockLogger) + SendRawTransaction.create() + }) + it('should get the right params and fail to create the transaction with encrypted', (done) => { const mockLogger = { writeLog: sinon.spy(), } + SendRawTransaction.runtime = { + spentTransactions: [1, 2, 3], + outgoingTransactions: { x: 1, y: 2, z: 3 }, + client: { + createRawTransaction: () => { return Promise.reject({ code: -4 }) }, + }, + counter: 0, + encrypted: '1234', + callback: () => {} + } + SendRawTransaction.retry = (error) => { + expect(error.code).toBe(-4) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'RAW_002') + done() + } + SendRawTransaction.__set__('Logger', mockLogger) - SendRawTransaction.createRaw({ spentTransactions, outgoingTransactions, client }, callback) + SendRawTransaction.create() }) - it('should get the right params and call signRaw (encrypted)', (done) => { - const callback = () => {} - const spentTransactions = [] - const outgoingTransactions = {} - const client = { - createRawTransaction: () => { return Promise.resolve('RAW_TRANSACTION') }, + it('should get the right params and call signRaw (no encrypted)', (done) => { + const mockLogger = { + writeLog: sinon.spy(), + } + + SendRawTransaction.runtime = { + spentTransactions: [1, 2, 3], + outgoingTransactions: { x: 1, y: 2, z: 3 }, + client: { + createRawTransaction: () => { return Promise.resolve('RAW_TRANSACTION') }, + }, + counter: 0, + callback: () => {} } - SendRawTransaction.signRaw = (options, parsedCallback) => { - expect(parsedCallback).toBe(callback) - expect(options.client).toBe(client) - expect(options.rawTrans).toBe('RAW_TRANSACTION') + SendRawTransaction.signRaw = (rawTrans) => { + expect(rawTrans).toBe('RAW_TRANSACTION') sinon.assert.notCalled(mockLogger.writeLog) done() } + SendRawTransaction.__set__('Logger', mockLogger) + SendRawTransaction.create() + }) + it('should get the right params and call signRaw (encrypted)', (done) => { const mockLogger = { writeLog: sinon.spy(), } + SendRawTransaction.runtime = { + spentTransactions: [1, 2, 3], + outgoingTransactions: { x: 1, y: 2, z: 3 }, + client: { + createRawTransaction: () => { return Promise.resolve('RAW_TRANSACTION') }, + }, + counter: 0, + encrypted: 'ENCRYPTED_DATA', + callback: () => {} + } + + SendRawTransaction.signRaw = (rawTrans) => { + expect(rawTrans).toBe('RAW_TRANSACTION') + sinon.assert.notCalled(mockLogger.writeLog) + done() + } + SendRawTransaction.__set__('Logger', mockLogger) - SendRawTransaction.createRaw({ spentTransactions, outgoingTransactions, client, encrypted: 'ENCRYPTED_DATA' }, callback) + SendRawTransaction.create() }) }) describe('(signRaw)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions SendRawTransaction = rewire('../src/lib/SendRawTransaction') }) it('should fail to sign the raw transaction', (done) => { - const callback = (success, data) => { - expect(success).toBe(false) - expect(data.error.code).toBe(-23) + SendRawTransaction.runtime = { + spentTransactions: [1, 2, 3], + outgoingTransactions: { x: 1, y: 2, z: 3 }, + client: { + signRawTransaction: () => { return Promise.reject({ code: -23 }) }, + }, + counter: 0, + callback: () => {} + } + + SendRawTransaction.retry = (error) => { + expect(error.code).toBe(-23) sinon.assert.calledOnce(mockLogger.writeLog) sinon.assert.calledWith(mockLogger.writeLog, 'RAW_004') done() } - const client = { - signRawTransaction: () => { return Promise.reject({ code: -23 }) }, - } const mockLogger = { writeLog: sinon.spy(), } SendRawTransaction.__set__('Logger', mockLogger) - SendRawTransaction.signRaw({ - rawTrans: 'RAW_TRANSACTION', - client, - }, callback) + SendRawTransaction.signRaw('RAW_TRANSACTION') }) it('should fail to sign the raw transaction and attempt to unlock the wallet (navCoin)', (done) => { const callback = () => {} @@ -145,7 +214,10 @@ describe('[SendRawTransaction]', () => { port: '44444', } - SendRawTransaction.runtime = {} + SendRawTransaction.runtime = { + callback, + client, + } const NavCoin = { unlockWallet: (options, parsedCallback) => { @@ -163,10 +235,7 @@ describe('[SendRawTransaction]', () => { SendRawTransaction.__set__('NavCoin', NavCoin) SendRawTransaction.__set__('Logger', mockLogger) - SendRawTransaction.signRaw({ - rawTrans: 'RAW_TRANSACTION', - client, - }, callback) + SendRawTransaction.signRaw('RAW_TRANSACTION') }) it('should fail to sign the raw transaction and attempt to unlock the wallet (subChain)', (done) => { const callback = () => {} @@ -175,7 +244,10 @@ describe('[SendRawTransaction]', () => { port: '33333', } - SendRawTransaction.runtime = {} + SendRawTransaction.runtime = { + callback, + client, + } const NavCoin = { unlockWallet: (options, parsedCallback) => { @@ -193,10 +265,7 @@ describe('[SendRawTransaction]', () => { SendRawTransaction.__set__('NavCoin', NavCoin) SendRawTransaction.__set__('Logger', mockLogger) - SendRawTransaction.signRaw({ - rawTrans: 'RAW_TRANSACTION', - client, - }, callback) + SendRawTransaction.signRaw('RAW_TRANSACTION') }) it('should sign the raw transaction and call sendRaw', (done) => { const callback = () => {} @@ -204,12 +273,13 @@ describe('[SendRawTransaction]', () => { signRawTransaction: () => { return Promise.resolve('SIGNED_RAW_TRANSACTION') }, } - SendRawTransaction.runtime = {} + SendRawTransaction.runtime = { + callback, + client, + } - SendRawTransaction.sendRaw = (options, parsedCallback) => { - expect(parsedCallback).toBe(callback) - expect(options.client).toBe(client) - expect(options.signedRaw).toBe('SIGNED_RAW_TRANSACTION') + SendRawTransaction.sendRaw = (signedRaw) => { + expect(signedRaw).toBe('SIGNED_RAW_TRANSACTION') sinon.assert.notCalled(mockLogger.writeLog) done() } @@ -219,14 +289,11 @@ describe('[SendRawTransaction]', () => { } SendRawTransaction.__set__('Logger', mockLogger) - SendRawTransaction.signRaw({ - rawTrans: 'RAW_TRANSACTION', - client, - }, callback) + SendRawTransaction.signRaw('RAW_TRANSACTION') }) }) describe('(walletUnlocked)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions SendRawTransaction = rewire('../src/lib/SendRawTransaction') }) it('should fail to unlock the wallet', (done) => { @@ -249,17 +316,11 @@ describe('[SendRawTransaction]', () => { }) it('should unlock the wallet and call signRaw', (done) => { SendRawTransaction.runtime = { - options: { - callback: () => {}, - client: () => {}, - rawTrans: 'RAW_TRANSACTION', - }, + rawTrans: 'RAW_TRANSACTION', } - SendRawTransaction.signRaw = (options, parsedCallback) => { - expect(parsedCallback).toBe(SendRawTransaction.runtime.callback) - expect(options.client).toBe(SendRawTransaction.runtime.options.client) - expect(options.rawTrans).toBe('RAW_TRANSACTION') + SendRawTransaction.signRaw = (rawTrans) => { + expect(rawTrans).toBe('RAW_TRANSACTION') sinon.assert.notCalled(mockLogger.writeLog) done() } @@ -273,32 +334,57 @@ describe('[SendRawTransaction]', () => { }) }) describe('(sendRaw)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions SendRawTransaction = rewire('../src/lib/SendRawTransaction') }) it('should fail to create the raw transaction', (done) => { - const callback = (success, data) => { - expect(success).toBe(false) - expect(data.error.code).toBe(-13) + SendRawTransaction.retry = (error) => { + expect(error.code).toBe(-13) sinon.assert.calledOnce(mockLogger.writeLog) sinon.assert.calledWith(mockLogger.writeLog, 'RAW_005') done() } - const client = { - sendRawTransaction: () => { return Promise.reject({ code: -13 }) }, - } - SendRawTransaction.runtime = {} + SendRawTransaction.runtime = { + client: { + sendRawTransaction: () => { return Promise.reject({ code: -13 }) }, + } + } const mockLogger = { writeLog: sinon.spy(), } SendRawTransaction.__set__('Logger', mockLogger) - SendRawTransaction.sendRaw({ - signedRaw: { hex: '1234' }, - client, - }, callback) + SendRawTransaction.sendRaw({ hex: '1234' }) + }) + it('should create the mock raw transaction', (done) => { + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.rawOutcome).toBe('dummy-tx-id') + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'RAW_TEST_001') + done() + } + + SendRawTransaction.runtime = { + client: { + sendRawTransaction: () => { return Promise.resolve('TXID') }, + }, + callback, + } + + const mockLogger = { + writeLog: sinon.spy(), + } + const globalSettings = { + serverType: 'INCOMING', + preventSend: true, + } + SendRawTransaction.__set__('globalSettings', globalSettings) + SendRawTransaction.__set__('Logger', mockLogger) + SendRawTransaction.sendRaw({ hex: '1234' }) + }) it('should create the raw transaction', (done) => { const callback = (success, data) => { @@ -311,17 +397,59 @@ describe('[SendRawTransaction]', () => { sendRawTransaction: () => { return Promise.resolve('TXID') }, } - SendRawTransaction.runtime = {} + SendRawTransaction.runtime = { client, callback } const mockLogger = { writeLog: sinon.spy(), } SendRawTransaction.__set__('Logger', mockLogger) - SendRawTransaction.sendRaw({ - signedRaw: { hex: '1234' }, - client, - }, callback) + SendRawTransaction.sendRaw({ hex: '1234' }) + }) + }) + describe('(retry)', () => { + beforeEach(() => { // reset the rewired functions + SendRawTransaction = rewire('../src/lib/SendRawTransaction') + }) + it('should call create', (done) => { + SendRawTransaction.create = () => { + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'RAW_007') + done() + } + + SendRawTransaction.runtime = { + counter: 0, + retryDelay: 100, + callback: () => {} + } + + const mockLogger = { + writeLog: sinon.spy(), + } + SendRawTransaction.__set__('Logger', mockLogger) + SendRawTransaction.retry({ code: -22 }) + }) + it('should max out retry attempts and run the callback', (done) => { + SendRawTransaction.create = () => { + SendRawTransaction.retry({ code: -22 }) + } + + SendRawTransaction.runtime = { + counter: 0, + retryDelay: 100, + callback: (success, data) => { + expect(success).toBe(false) + expect(data).toEqual({ error: { code: -22 } }) + done() + } + } + + const mockLogger = { + writeLog: sinon.spy(), + } + SendRawTransaction.__set__('Logger', mockLogger) + SendRawTransaction.retry({ code: -22 }) }) }) }) diff --git a/test/SendToAddress.spec.js b/test/SendToAddress.spec.js index ad559fb..9775046 100644 --- a/test/SendToAddress.spec.js +++ b/test/SendToAddress.spec.js @@ -8,6 +8,9 @@ let SendToAddress = rewire('../src/lib/SendToAddress') describe('[SendToAddress]', () => { describe('(send)', () => { + beforeEach(() => { // reset the rewired functions + SendToAddress = rewire('../src/lib/SendToAddress') + }) it('should fail on params', (done) => { const callback = (success, data) => { expect(success).toBe(false) @@ -67,6 +70,33 @@ describe('[SendToAddress]', () => { SendToAddress.__set__('Logger', mockLogger) SendToAddress.send({ address, amount, client, transaction, counter: 1 }, callback) }) + it('should have the right params and fake the send', (done) => { + const callback = (success, data) => { + expect(success).toBe(true) + expect(data.transaction).toBe(transaction) + expect(data.sendOutcome).toBe('dummy-tx-id') + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'STA_TEST_001') + done() + } + const client = { + sendToAddress: () => { return Promise.resolve('TXID') }, + port: '44444', + } + const address = 'ADDR' + const amount = 10 + const transaction = { txid: '1234' } + const mockLogger = { + writeLog: sinon.spy(), + } + const globalSettings = { + serverType: 'INCOMING', + preventSend: true, + } + SendToAddress.__set__('globalSettings', globalSettings) + SendToAddress.__set__('Logger', mockLogger) + SendToAddress.send({ address, amount, client, transaction, counter: 1 }, callback) + }) it('should have the right params and succeed', (done) => { const callback = (success, data) => { expect(success).toBe(true) @@ -111,7 +141,7 @@ describe('[SendToAddress]', () => { }) }) describe('(walletUnlocked)', () => { - before(() => { // reset the rewired functions + beforeEach(() => { // reset the rewired functions SendToAddress = rewire('../src/lib/SendToAddress') }) it('should fail to unlock the wallet', (done) => { diff --git a/test/SpendToHolding.spec.js b/test/SpendToHolding.spec.js new file mode 100644 index 0000000..42159a8 --- /dev/null +++ b/test/SpendToHolding.spec.js @@ -0,0 +1,155 @@ +'use strict' + +const expect = require('expect') +const rewire = require('rewire') +const sinon = require('sinon') + +let SpendToHolding = rewire('../src/lib/SpendToHolding') + +describe('[SpendToHolding]', () => { + describe('(send)', () => { + beforeEach(() => { // reset the rewired functions + SpendToHolding = rewire('../src/lib/SpendToHolding') + }) + it('should fail on params', (done) => { + const callback = (success, data) => { + expect(success).toBe(false) + expect(data.message).toBeA('string') + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'STH_001') + done() + } + const mockLogger = { + writeLog: sinon.spy(), + } + SpendToHolding.__set__('Logger', mockLogger) + SpendToHolding.run({ junkParam: 1234 }, callback) + }) + it('should have correct params and call getRandomAccountAddresses', (done) => { + const callback = () => {} + const navClient = { + getInfo: () => {}, + } + const successfulSubTransactions = [1, 2, 3] + const holdingEncrypted = 'QWER==' + const RandomizeTransactions = { + getRandomAccountAddresses: (options, parsedCallback) => { + expect(parsedCallback).toBe(SpendToHolding.createHoldingTransactions) + expect(options.accountName).toBe('HOLDING_ACCOUNT') + expect(options.client).toBe(navClient) + expect(options.numAddresses).toBe(1) + sinon.assert.notCalled(mockLogger.writeLog) + done() + }, + } + const mockLogger = { + writeLog: sinon.spy(), + } + const privateSettings = { + account: { HOLDING: 'HOLDING_ACCOUNT' }, + } + SpendToHolding.__set__('Logger', mockLogger) + SpendToHolding.__set__('RandomizeTransactions', RandomizeTransactions) + SpendToHolding.__set__('privateSettings', privateSettings) + SpendToHolding.run({ navClient, successfulSubTransactions, holdingEncrypted }, callback) + }) + }) + describe('(createHoldingTransactions)', () => { + beforeEach(() => { // reset the rewired functions + SpendToHolding = rewire('../src/lib/SpendToHolding') + }) + it('should fail to get the random holding address', (done) => { + SpendToHolding.runtime = { + callback: (success, data) => { + expect(success).toBe(false) + expect(data.message).toBeA('string') + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'STH_002') + done() + }, + } + const mockLogger = { + writeLog: sinon.spy(), + } + SpendToHolding.__set__('Logger', mockLogger) + SpendToHolding.createHoldingTransactions(false) + }) + it('should build the holding transactions and create call createRaw', (done) => { + const navClient = { + getInfo: () => {}, + } + const privateSettings = { + txFee: 0.0001, + } + SpendToHolding.runtime = { + successfulSubTransactions: [ + { txid: '1234', amount: 100, vout: 0 }, + { txid: '2345', amount: 200, vout: 1 }, + { txid: '6789', amount: 400, vout: 2 }, + ], + holdingEncrypted: 'QWER==', + navClient, + } + const SendRawTransaction = { + createRaw: (options, parsedCallback) => { + expect(options.outgoingTransactions).toEqual({ + ZXCVB: 700 - 0.0003, + }) + expect(options.spentTransactions).toEqual([ + { txid: '1234', vout: 0 }, + { txid: '2345', vout: 1 }, + { txid: '6789', vout: 2 }, + ]) + expect(options.encrypted).toBe('QWER==') + expect(options.client).toBe(navClient) + expect(parsedCallback).toBe(SpendToHolding.sentToHolding) + sinon.assert.notCalled(mockLogger.writeLog) + done() + }, + } + const mockLogger = { + writeLog: sinon.spy(), + } + SpendToHolding.__set__('Logger', mockLogger) + SpendToHolding.__set__('privateSettings', privateSettings) + SpendToHolding.__set__('SendRawTransaction', SendRawTransaction) + SpendToHolding.createHoldingTransactions(true, { pickedAddresses: ['ZXCVB'] }) + }) + }) + describe('(sentToHolding)', () => { + beforeEach(() => { // reset the rewired functions + SpendToHolding = rewire('../src/lib/SpendToHolding') + }) + it('should fail to spend to holding', (done) => { + SpendToHolding.runtime = { + callback: (success, data) => { + expect(success).toBe(false) + expect(data.message).toBeA('string') + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'STH_003') + done() + }, + } + const mockLogger = { + writeLog: sinon.spy(), + } + SpendToHolding.__set__('Logger', mockLogger) + SpendToHolding.sentToHolding(false) + }) + it('should successfully spend to holding', (done) => { + SpendToHolding.runtime = { + callback: (success, data) => { + expect(success).toBe(true) + expect(data).toEqual({ txid: '1234' }) + sinon.assert.notCalled(mockLogger.writeLog) + done() + }, + } + const mockLogger = { + writeLog: sinon.spy(), + } + SpendToHolding.__set__('Logger', mockLogger) + SpendToHolding.sentToHolding(true, { txid: '1234' }) + }) + }) +}) diff --git a/test/incoming.spec.js b/test/incoming.spec.js new file mode 100644 index 0000000..0f61d77 --- /dev/null +++ b/test/incoming.spec.js @@ -0,0 +1,843 @@ +'use strict' + +const expect = require('expect') +const rewire = require('rewire') +const sinon = require('sinon') +const config = require('config') +const settings = config.get('INCOMING') + +let IncomingServer = rewire('../src/incoming') + +describe('[IncomingServer]', () => { + describe('(init)', () => { + beforeEach(() => { // reset the rewired functions + IncomingServer = rewire('../src/incoming') + }) + it('should start the daemons and call findKeysToRemove', (done) => { + const EncryptionKeys = { + findKeysToRemove: (options, callback) => { + expect(callback).toBe(IncomingServer.startProcessing) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_000') + expect(IncomingServer.navClient).toBeA('object') + expect(IncomingServer.subClient).toBeA('object') + done() + }, + } + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('EncryptionKeys', EncryptionKeys) + IncomingServer.init() + }) + it('should start the daemons and the timer', (done) => { + const EncryptionKeys = { + findKeysToRemove: () => {}, + } + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('EncryptionKeys', EncryptionKeys) + IncomingServer.init() + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_000') + expect(IncomingServer.navClient).toBeA('object') + expect(IncomingServer.subClient).toBeA('object') + expect(IncomingServer.cron._repeat).toBe(settings.scriptInterval) + done() + }) + }) + describe('(startProcessing)', () => { + beforeEach(() => { // reset the rewired functions + IncomingServer = rewire('../src/incoming') + }) + it('should not proceed as the script is still processing', (done) => { + IncomingServer.processing = true + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.startProcessing() + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_002') + done() + }) + it('should proceed to run preflight checks', (done) => { + IncomingServer.navClient = { getInfo: () => true } + IncomingServer.subClient = { getInfo: () => true } + const now = new Date() + const clock = sinon.useFakeTimers(now.getTime()) + const PreFlight = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(options.subClient).toBe(IncomingServer.subClient) + expect(callback).toBe(IncomingServer.preFlightComplete) + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.runtime).toEqual({ cycleStart: now }) + clock.restore() + done() + }, + } + IncomingServer.processing = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('PreFlight', PreFlight) + IncomingServer.__set__('Date', Date) + IncomingServer.startProcessing() + }) + }) + describe('(preFlightComplete)', () => { + beforeEach(() => { // reset the rewired functions + IncomingServer = rewire('../src/incoming') + }) + it('should fail preflight and log error', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.preFlightComplete(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_003') + expect(IncomingServer.processing).toBe(false) + expect(IncomingServer.paused).toBe(false) + done() + }) + it('should pass preflight, set balances and call the refill function', (done) => { + IncomingServer.navClient = { getInfo: () => true } + IncomingServer.subClient = { getInfo: () => true } + const RefillOutgoing = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(callback).toBe(IncomingServer.holdingProcessed) + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.runtime).toEqual({ + navBalance: 1000, + subBalance: 100, + }) + done() + }, + } + IncomingServer.processing = true + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('RefillOutgoing', RefillOutgoing) + IncomingServer.preFlightComplete(true, { + navBalance: 1000, + subBalance: 100, + }) + }) + }) + describe('(holdingProcessed)', () => { + beforeEach(() => { // reset the rewired functions + IncomingServer = rewire('../src/incoming') + }) + it('should fail to process holding transactions', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.holdingProcessed(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_004') + expect(IncomingServer.processing).toBe(false) + expect(IncomingServer.paused).toBe(true) + done() + }) + it('should process the holding, set balances and call the refill function', (done) => { + IncomingServer.navClient = { getInfo: () => true } + IncomingServer.subClient = { getInfo: () => true } + const SelectOutgoing = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(options.settings).toBe(settings) + expect(callback).toBe(IncomingServer.outgoingSelected) + expect(IncomingServer.processing).toBe(true) + done() + }, + } + IncomingServer.processing = true + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('SelectOutgoing', SelectOutgoing) + IncomingServer.holdingProcessed(true, { + navClient: IncomingServer.navClient, + settings, + }) + }) + }) + describe('(outgoingSelected)', () => { + beforeEach(() => { // reset the rewired functions + IncomingServer = rewire('../src/incoming') + }) + it('should fail to select outgoing server', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.outgoingSelected(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_005') + expect(IncomingServer.processing).toBe(false) + expect(IncomingServer.paused).toBe(false) + done() + }) + it('should return all to senders if failed to locate outgoing server', (done) => { + IncomingServer.navClient = { getInfo: () => true } + IncomingServer.subClient = { getInfo: () => true } + const ReturnAllToSenders = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(callback).toBe(IncomingServer.allPendingReturned) + expect(IncomingServer.processing).toBe(true) + done() + }, + } + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('ReturnAllToSenders', ReturnAllToSenders) + IncomingServer.outgoingSelected(true, { + returnAllToSenders: true, + }) + }) + it('should pause transactions if the issue was on the incoming server', (done) => { + IncomingServer.navClient = { getInfo: () => true } + IncomingServer.subClient = { getInfo: () => true } + const ReturnAllToSenders = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(callback).toBe(IncomingServer.allPendingReturned) + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.paused).toBe(true) + done() + }, + } + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('ReturnAllToSenders', ReturnAllToSenders) + IncomingServer.outgoingSelected(true, { + returnAllToSenders: true, + pause: true, + }) + }) + it('should not pause transactions if the issue was on the outgoing server', (done) => { + IncomingServer.navClient = { getInfo: () => true } + IncomingServer.subClient = { getInfo: () => true } + const ReturnAllToSenders = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(callback).toBe(IncomingServer.allPendingReturned) + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.paused).toBe(false) + done() + }, + } + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('ReturnAllToSenders', ReturnAllToSenders) + IncomingServer.outgoingSelected(true, { + returnAllToSenders: true, + }) + }) + it('should run prepareIncoming if all params correct', (done) => { + IncomingServer.navClient = { getInfo: () => true } + IncomingServer.subClient = { getInfo: () => true } + const PrepareIncoming = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(options.outgoingNavBalance).toBe(1000) + expect(options.navClient).toBe(IncomingServer.navClient) + expect(callback).toBe(IncomingServer.currentBatchPrepared) + expect(options.settings).toBe(settings) + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.runtime).toEqual({ + chosenOutgoing: 'QWER', + outgoingNavBalance: 1000, + holdingEncrypted: 'ZXCV', + outgoingPubKey: 'ASDF', + }) + done() + }, + } + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('PrepareIncoming', PrepareIncoming) + IncomingServer.outgoingSelected(true, { + returnAllToSenders: false, + chosenOutgoing: 'QWER', + outgoingNavBalance: 1000, + holdingEncrypted: 'ZXCV', + outgoingPubKey: 'ASDF', + }) + }) + }) + describe('(allPendingReturned)', () => { + beforeEach(() => { // reset the rewired functions + IncomingServer = rewire('../src/incoming') + }) + it('should fail to return all pending and pause', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.allPendingReturned(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_006') + expect(IncomingServer.processing).toBe(false) + expect(IncomingServer.paused).toBe(true) + done() + }) + it('should return the pending to sender and reset', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + + IncomingServer.allPendingReturned(true) + + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_007') + expect(IncomingServer.processing).toBe(false) + expect(IncomingServer.processing).toBe(false) + done() + }) + }) + describe('(currentBatchPrepared)', () => { + beforeEach(() => { // reset the rewired functions + IncomingServer = rewire('../src/incoming') + }) + it('should fail to get the currentBatch with false success', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + + IncomingServer.currentBatchPrepared(false) + + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_011D') + expect(IncomingServer.processing).toBe(false) + expect(IncomingServer.paused).toBe(false) + done() + }) + it('should fail to get the currentBatch with bad params', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + + IncomingServer.currentBatchPrepared(true, { + junkParam: true, + }) + + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_011D') + expect(IncomingServer.processing).toBe(false) + expect(IncomingServer.paused).toBe(false) + done() + }) + it('should get the currentBatch but have some to return', (done) => { + IncomingServer.navClient = { getInfo: () => true } + const ReturnAllToSenders = { + fromList: (options, callback) => { + expect(callback).toBe(IncomingServer.pendingFailedReturned) + expect(options.navClient).toBe(IncomingServer.navClient) + expect(options.transactionsToReturn).toEqual([{ txid: '1234', amount: 100 }, { txid: 'QWER', amount: 100 }]) + done() + }, + } + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('ReturnAllToSenders', ReturnAllToSenders) + + IncomingServer.currentBatchPrepared(true, { + currentBatch: [{ unique: 1234, transactions: {} }, { unique: 5678, transactions: {} }], + currentFlattened: {}, + numFlattened: 0, + pendingToReturn: [{ txid: '1234', amount: 100 }, { txid: 'QWER', amount: 100 }], + }) + }) + it('should get the currentBatch and have none to return', (done) => { + IncomingServer.subClient = { getInfo: () => true } + IncomingServer.runtime = { + chosenOutgoing: { + ipAddress: '10.10.10.1', + }, + } + const RetrieveSubchainAddresses = { + run: (options, callback) => { + expect(callback).toBe(IncomingServer.retrievedSubchainAddresses) + expect(options.subClient).toBe(IncomingServer.subClient) + expect(options.numAddresses).toBe(5) + done() + }, + } + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('RetrieveSubchainAddresses', RetrieveSubchainAddresses) + + IncomingServer.currentBatchPrepared(true, { + currentBatch: [{ unique: 'ABC', amount: 30 }, { unique: 'XYZ', amount: 200 }], + currentFlattened: { ABC: [10, 10, 10], XYZ: [100, 100] }, + numFlattened: 5, + pendingToReturn: [], + }) + }) + it('should get the currentBatch and have none to return or process', (done) => { + IncomingServer.subClient = { getInfo: () => true } + IncomingServer.runtime = { + chosenOutgoing: { + ipAddress: '10.10.10.1', + }, + } + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + + IncomingServer.currentBatchPrepared(true, { + currentBatch: [], + currentFlattened: {}, + numFlattened: 0, + pendingToReturn: [], + }) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_011B') + expect(IncomingServer.processing).toBe(false) + expect(IncomingServer.paused).toBe(false) + done() + }) + }) + describe('(pendingFailedReturned)', () => { + beforeEach(() => { // reset the rewired functions + IncomingServer = rewire('../src/incoming') + }) + it('should fail to return the pending that are marked as expired', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + + const ReturnAllToSenders = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(callback).toBe(IncomingServer.allPendingReturned) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_011A') + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.paused).toBe(true) + done() + }, + } + + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('ReturnAllToSenders', ReturnAllToSenders) + IncomingServer.pendingFailedReturned(false) + }) + it('should have returned all expired to sender and have no currentBatch', done => { + IncomingServer.subClient = { getInfo: () => true } + IncomingServer.runtime = { + chosenOutgoing: { + ipAddress: '10.10.10.1', + }, + numFlattened: 5, + currentBatch: [], + } + const RetrieveSubchainAddresses = { + run: (options, callback) => { + expect(callback).toBe(IncomingServer.retrievedSubchainAddresses) + expect(options.subClient).toBe(IncomingServer.subClient) + expect(options.numAddresses).toBe(5) + done() + }, + } + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('RetrieveSubchainAddresses', RetrieveSubchainAddresses) + + IncomingServer.pendingFailedReturned(true, {}) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_011C') + expect(IncomingServer.processing).toBe(false) + expect(IncomingServer.paused).toBe(false) + done() + }) + it('should have returned all expired to sender and get the subchain addresses', done => { + IncomingServer.subClient = { getInfo: () => true } + IncomingServer.runtime = { + chosenOutgoing: { + ipAddress: '10.10.10.1', + }, + numFlattened: 5, + currentBatch: [{ unique: 1234 }], + } + const RetrieveSubchainAddresses = { + run: (options, callback) => { + expect(callback).toBe(IncomingServer.retrievedSubchainAddresses) + expect(options.subClient).toBe(IncomingServer.subClient) + expect(options.numAddresses).toBe(5) + done() + }, + } + IncomingServer.processing = true + IncomingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('RetrieveSubchainAddresses', RetrieveSubchainAddresses) + + IncomingServer.pendingFailedReturned(true, {}) + }) + }) + describe('(retrievedSubchainAddresses)', () => { + beforeEach(() => { // reset the rewired functions + IncomingServer = rewire('../src/incoming') + }) + it('should fail to retrieve subchain addresses with success false', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + + const ReturnAllToSenders = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(callback).toBe(IncomingServer.allPendingReturned) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_009') + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.paused).toBe(false) + done() + }, + } + + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('ReturnAllToSenders', ReturnAllToSenders) + IncomingServer.retrievedSubchainAddresses(false) + }) + it('should fail to retrieve subchain addresses with bad params', (done) => { + IncomingServer.navClient = { getInfo: () => true } + IncomingServer.processing = true + IncomingServer.paused = false + + const ReturnAllToSenders = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(callback).toBe(IncomingServer.allPendingReturned) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_009') + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.paused).toBe(false) + done() + }, + } + + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('ReturnAllToSenders', ReturnAllToSenders) + IncomingServer.retrievedSubchainAddresses(true, { junkParam: true }) + }) + it('should have everything it needs to process the batch', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + IncomingServer.subClient = { getInfo: () => true } + IncomingServer.navClient = { getInfo: () => true } + IncomingServer.runtime = { + currentBatch: [{ unique: 'ABC', amount: 30 }, { unique: 'XYZ', amount: 200 }], + currentFlattened: { ABC: [10, 10, 10], XYZ: [100, 100] }, + outgoingPubKey: 'ZXCV', + } + const ProcessIncoming = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(options.subClient).toBe(IncomingServer.subClient) + expect(options.currentBatch).toEqual(IncomingServer.runtime.currentBatch) + expect(options.currentFlattened).toEqual(IncomingServer.runtime.currentFlattened) + expect(options.outgoingPubKey).toBe('ZXCV') + expect(options.subAddresses).toEqual(['QWER', 'ASDF', 'ZXCV']) + expect(options.settings).toBe(settings) + expect(callback).toBe(IncomingServer.transactionsProcessed) + sinon.assert.notCalled(mockLogger.writeLog) + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.paused).toBe(false) + done() + }, + } + + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('ProcessIncoming', ProcessIncoming) + IncomingServer.retrievedSubchainAddresses(true, { subAddresses: ['QWER', 'ASDF', 'ZXCV'] }) + }) + }) + describe('(transactionsProcessed)', () => { + beforeEach(() => { // reset the rewired functions + IncomingServer = rewire('../src/incoming') + }) + it('should fail to process all transactions and return all to senders', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + IncomingServer.navClient = { getInfo: () => true } + + const ReturnAllToSenders = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(callback).toBe(IncomingServer.allPendingReturned) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_010') + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.paused).toBe(true) + done() + }, + } + + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('ReturnAllToSenders', ReturnAllToSenders) + IncomingServer.transactionsProcessed(false) + }) + it('should process some and fail a partial send', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + IncomingServer.navClient = { getInfo: () => true } + + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + + IncomingServer.transactionsProcessed(false, { partialFailure: true }) + + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_010A') + expect(IncomingServer.processing).toBe(false) + expect(IncomingServer.paused).toBe(true) + done() + }) + it('should succeed with some failed and return the failed', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + IncomingServer.navClient = { getInfo: () => true } + + const ReturnAllToSenders = { + fromList: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(callback).toBe(IncomingServer.failedTransactionsReturned) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_011') + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.paused).toBe(false) + expect(options.transactionsToReturn).toEqual([ + { txid: 'QQQQ' }, { txid: 'WWWW' }, { txid: 'EEEE' }, + { txid: 'AAAA' }, { txid: 'SSSS' }, { txid: 'DDDD' }, + ]) + done() + }, + } + + const successfulTxGroups = [ + { + unique: 'QWER', + transactions: [{ txid: '1111' }, { txid: '2222' }, { txid: '3333' }], + }, + { + unique: 'ASDF', + transactions: [{ txid: '4444' }, { txid: '5555' }, { txid: '6666' }], + }, + { + unique: 'ZXCV', + transactions: [{ txid: '7777' }, { txid: '8888' }, { txid: '9999' }], + }, + ] + + const txGroupsToReturn = [ + { + unique: '1234', + transactions: [{ txid: 'QQQQ' }, { txid: 'WWWW' }, { txid: 'EEEE' }], + }, + { + unique: '6789', + transactions: [{ txid: 'AAAA' }, { txid: 'SSSS' }, { txid: 'DDDD' }], + }, + ] + + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('ReturnAllToSenders', ReturnAllToSenders) + IncomingServer.transactionsProcessed(true, { successfulTxGroups, txGroupsToReturn }) + }) + it('should succeed with no failed and skip trying to return any', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + IncomingServer.navClient = { getInfo: () => true } + + IncomingServer.failedTransactionsReturned = (success) => { + expect(success).toBe(true) + sinon.assert.notCalled(mockLogger.writeLog) + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.paused).toBe(false) + expect(IncomingServer.runtime.successfulTxGroups).toEqual(successfulTxGroups) + done() + } + + const successfulTxGroups = [ + { + unique: 'QWER', + transactions: [{ txid: '1111' }, { txid: '2222' }, { txid: '3333' }], + }, + { + unique: 'ASDF', + transactions: [{ txid: '4444' }, { txid: '5555' }, { txid: '6666' }], + }, + { + unique: 'ZXCV', + transactions: [{ txid: '7777' }, { txid: '8888' }, { txid: '9999' }], + }, + ] + + const txGroupsToReturn = [] + + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.transactionsProcessed(true, { successfulTxGroups, txGroupsToReturn }) + }) + }) + describe('(failedTransactionsReturned)', () => { + beforeEach(() => { // reset the rewired functions + IncomingServer = rewire('../src/incoming') + }) + it('should fail to return the failed to sender and continue to try to spend to holding', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + IncomingServer.navClient = { getInfo: () => true } + + const successfulTxGroups = [ + { + unique: 'QWER', + transactions: [{ txid: '1111' }, { txid: '2222' }, { txid: '3333' }], + }, + { + unique: 'ASDF', + transactions: [{ txid: '4444' }, { txid: '5555' }, { txid: '6666' }], + }, + { + unique: 'ZXCV', + transactions: [{ txid: '7777' }, { txid: '8888' }, { txid: '9999' }], + }, + ] + + const SpendToHolding = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(callback).toBe(IncomingServer.spentToHolding) + expect(options.holdingEncrypted).toBe('QWER==') + expect(options.successfulSubTransactions).toEqual([ + { txid: '1111' }, { txid: '2222' }, { txid: '3333' }, + { txid: '4444' }, { txid: '5555' }, { txid: '6666' }, + { txid: '7777' }, { txid: '8888' }, { txid: '9999' }, + ]) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_012') + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.paused).toBe(true) + done() + }, + } + + IncomingServer.runtime = { + successfulTxGroups, + holdingEncrypted: 'QWER==', + } + + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('SpendToHolding', SpendToHolding) + IncomingServer.failedTransactionsReturned(false) + }) + it('should return the failed to sender and continue to spend to holding', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + IncomingServer.navClient = { getInfo: () => true } + + const successfulTxGroups = [ + { + unique: 'QWER', + transactions: [{ txid: '1111' }, { txid: '2222' }, { txid: '3333' }], + }, + { + unique: 'ASDF', + transactions: [{ txid: '4444' }, { txid: '5555' }, { txid: '6666' }], + }, + { + unique: 'ZXCV', + transactions: [{ txid: '7777' }, { txid: '8888' }, { txid: '9999' }], + }, + ] + + const SpendToHolding = { + run: (options, callback) => { + expect(options.navClient).toBe(IncomingServer.navClient) + expect(callback).toBe(IncomingServer.spentToHolding) + expect(options.holdingEncrypted).toBe('QWER==') + expect(options.successfulSubTransactions).toEqual([ + { txid: '1111' }, { txid: '2222' }, { txid: '3333' }, + { txid: '4444' }, { txid: '5555' }, { txid: '6666' }, + { txid: '7777' }, { txid: '8888' }, { txid: '9999' }, + ]) + sinon.assert.notCalled(mockLogger.writeLog) + expect(IncomingServer.processing).toBe(true) + expect(IncomingServer.paused).toBe(false) + done() + }, + } + + IncomingServer.runtime = { + successfulTxGroups, + holdingEncrypted: 'QWER==', + } + + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + IncomingServer.__set__('SpendToHolding', SpendToHolding) + IncomingServer.failedTransactionsReturned(true) + }) + }) + describe('(spentToHolding)', () => { + beforeEach(() => { // reset the rewired functions + IncomingServer = rewire('../src/incoming') + }) + it('should fail to spend the processed to holding and pause', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + + IncomingServer.spentToHolding(false) + + expect(IncomingServer.processing).toBe(false) + expect(IncomingServer.paused).toBe(true) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'INC_013') + done() + }) + it('should fail to spend the processed to holding and pause', (done) => { + IncomingServer.processing = true + IncomingServer.paused = false + + const mockLogger = { writeLog: sinon.spy() } + IncomingServer.__set__('Logger', mockLogger) + + IncomingServer.spentToHolding(true) + + expect(IncomingServer.processing).toBe(false) + expect(IncomingServer.paused).toBe(false) + sinon.assert.notCalled(mockLogger.writeLog) + done() + }) + }) +}) diff --git a/test/outgoing.spec.js b/test/outgoing.spec.js new file mode 100644 index 0000000..d14afb2 --- /dev/null +++ b/test/outgoing.spec.js @@ -0,0 +1,370 @@ +'use strict' + +const expect = require('expect') +const rewire = require('rewire') +const sinon = require('sinon') +const config = require('../config/example-outgoing.default') +const settings = config.OUTGOING + +let OutgoingServer = rewire('../src/outgoing') + +describe('[OutgoingServer]', () => { + describe('(init)', () => { + beforeEach(() => { // reset the rewired functions + OutgoingServer = rewire('../src/outgoing') + }) + it('should start the daemons and call findKeysToRemove', (done) => { + const EncryptionKeys = { + findKeysToRemove: (options, callback) => { + expect(callback).toBe(OutgoingServer.startProcessing) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'OUT_000') + expect(OutgoingServer.navClient).toBeA('object') + expect(OutgoingServer.subClient).toBeA('object') + done() + }, + } + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.__set__('EncryptionKeys', EncryptionKeys) + OutgoingServer.__set__('settings', settings) + OutgoingServer.init() + }) + it('should start the daemons and the timer', (done) => { + const EncryptionKeys = { + findKeysToRemove: () => {}, + } + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.__set__('EncryptionKeys', EncryptionKeys) + OutgoingServer.__set__('settings', settings) + OutgoingServer.init() + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'OUT_000') + expect(OutgoingServer.navClient).toBeA('object') + expect(OutgoingServer.subClient).toBeA('object') + expect(OutgoingServer.cron._repeat).toBe(settings.scriptInterval) + done() + }) + }) + describe('(startProcessing)', () => { + beforeEach(() => { // reset the rewired functions + OutgoingServer = rewire('../src/outgoing') + }) + it('should not proceed as the script is still processing', (done) => { + OutgoingServer.processing = true + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.startProcessing() + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'OUT_002') + done() + }) + it('should proceed to run preflight checks', (done) => { + OutgoingServer.navClient = { getInfo: () => true } + OutgoingServer.subClient = { getInfo: () => true } + const now = new Date() + const clock = sinon.useFakeTimers(now.getTime()) + const PreFlight = { + run: (options, callback) => { + expect(options.navClient).toBe(OutgoingServer.navClient) + expect(options.subClient).toBe(OutgoingServer.subClient) + expect(callback).toBe(OutgoingServer.preFlightComplete) + expect(OutgoingServer.processing).toBe(true) + expect(OutgoingServer.runtime).toEqual({ cycleStart: now }) + clock.restore() + done() + }, + } + OutgoingServer.processing = false + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.__set__('PreFlight', PreFlight) + OutgoingServer.__set__('Date', Date) + OutgoingServer.startProcessing() + }) + }) + describe('(preFlightComplete)', () => { + beforeEach(() => { // reset the rewired functions + OutgoingServer = rewire('../src/outgoing') + }) + it('should fail preflight and log error', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.preFlightComplete(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'OUT_003') + expect(OutgoingServer.processing).toBe(false) + expect(OutgoingServer.paused).toBe(false) + done() + }) + it('should pass preflight, set balances and call the refill function', (done) => { + OutgoingServer.navClient = { listUnspent: () => true } + OutgoingServer.subClient = { listUnspent: () => true } + const PayoutFee = { + run: (options, callback) => { + expect(options.navClient).toBe(OutgoingServer.navClient) + expect(OutgoingServer.runtime).toEqual({ + navBalance: 1000, + subBalance: 100, + }) + done() + }, + } + OutgoingServer.processing = true + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.__set__('PayoutFee', PayoutFee) + OutgoingServer.preFlightComplete(true, { + navBalance: 1000, + subBalance: 100, + }) + }) + }) + describe('(currentBatchPrepared)', () => { + beforeEach(() => { // reset the rewired functions + OutgoingServer = rewire('../src/outgoing') + }) + it('should fail to get the currentBatch with false success', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + + OutgoingServer.currentBatchPrepared(false, {}) + + sinon.assert.notCalled(mockLogger.writeLog) + expect(OutgoingServer.processing).toBe(false) + expect(OutgoingServer.paused).toBe(false) + done() + }) + it('should get the current batch, log failed sub and process', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + + const ProcessOutgoing = { + run: (options, callback) => { + expect(options.navClient).toBe(OutgoingServer.navClient) + expect(callback).toBe(OutgoingServer.transactionsProcessed) + expect(OutgoingServer.processing).toBe(true) + expect(OutgoingServer.paused).toBe(false) + expect(OutgoingServer.runtime.failedSubTransactions).toEqual([1, 2, 3]) + expect(OutgoingServer.runtime.currentBatch).toEqual([4, 5, 6]) + done() + }, + } + + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.__set__('ProcessOutgoing', ProcessOutgoing) + OutgoingServer.currentBatchPrepared(true, { failedSubTransactions: [1, 2, 3], currentBatch: [4, 5, 6] }) + }) + it('should get the current batch and process', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + + const ProcessOutgoing = { + run: (options, callback) => { + expect(options.navClient).toBe(OutgoingServer.navClient) + expect(callback).toBe(OutgoingServer.transactionsProcessed) + expect(OutgoingServer.processing).toBe(true) + expect(OutgoingServer.paused).toBe(false) + expect(OutgoingServer.runtime.failedSubTransactions).toEqual(undefined) + expect(OutgoingServer.runtime.currentBatch).toEqual([4, 5, 6]) + done() + }, + } + + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.__set__('ProcessOutgoing', ProcessOutgoing) + OutgoingServer.currentBatchPrepared(true, { currentBatch: [4, 5, 6] }) + }) + }) + describe('(transactionsProcessed)', () => { + beforeEach(() => { // reset the rewired functions + OutgoingServer = rewire('../src/outgoing') + }) + it('should fail to get the process transactions with false success', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + + OutgoingServer.transactionsProcessed(false) + + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'OUT_004') + expect(OutgoingServer.processing).toBe(false) + expect(OutgoingServer.paused).toBe(true) + done() + }) + it('should get the current batch and log any failed', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + + const ReturnSubnav = { + run: (options, callback) => { + expect(options.subClient).toBe(OutgoingServer.subClient) + expect(callback).toBe(OutgoingServer.subnavReturned) + expect(OutgoingServer.processing).toBe(true) + expect(OutgoingServer.paused).toBe(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'OUT_005A') + done() + }, + } + + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.__set__('ReturnSubnav', ReturnSubnav) + OutgoingServer.transactionsProcessed(true, { successfulTransactions: [1, 2, 3], failedTransactions: [4, 5, 6] }) + }) + it('should fail to send any transactions and pause', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + + OutgoingServer.runtime = {} + + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.transactionsProcessed(true, { successfulTransactions: [], failedTransactions: [4, 5, 6] }) + + expect(OutgoingServer.processing).toBe(false) + expect(OutgoingServer.paused).toBe(true) + expect(OutgoingServer.runtime.successfulTransactions).toEqual(undefined) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'OUT_005') + done() + }) + it('should get the current batch and return the subnav', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + + const ReturnSubnav = { + run: (options, callback) => { + expect(options.subClient).toBe(OutgoingServer.subClient) + expect(callback).toBe(OutgoingServer.subnavReturned) + expect(OutgoingServer.processing).toBe(true) + expect(OutgoingServer.paused).toBe(false) + expect(OutgoingServer.runtime.successfulTransactions).toEqual([1, 2, 3]) + done() + }, + } + + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.__set__('ReturnSubnav', ReturnSubnav) + OutgoingServer.transactionsProcessed(true, { successfulTransactions: [1, 2, 3] }) + }) + }) + describe('(feePaid)', () => { + beforeEach(() => { // reset the rewired functions + OutgoingServer = rewire('../src/outgoing') + }) + it('should continue to return the subnav if it failed to pay the fee', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + + const mockClient = { + getBalance: () => { return Promise.resolve(50000) }, + } + + const PrepareOutgoing = { + run: (options, callback) => { + expect(options.subClient).toBe(OutgoingServer.subClient) + expect(options.navClient).toBe(OutgoingServer.navClient) + expect(callback).toBe(OutgoingServer.currentBatchPrepared) + expect(options.navBalance).toBe(50000) + expect(OutgoingServer.processing).toBe(true) + expect(OutgoingServer.paused).toBe(false) + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'OUT_006') + done() + }, + } + + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.__set__('PrepareOutgoing', PrepareOutgoing) + OutgoingServer.navClient = mockClient + OutgoingServer.feePaid(false, { transaction: 123, error: 'failed' }) + }) + it('should continue to return the subnav if it paid the fee', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + + const mockClient = { + getBalance: () => { return Promise.resolve(50000) }, + } + + const PrepareOutgoing = { + run: (options, callback) => { + expect(options.subClient).toBe(OutgoingServer.subClient) + expect(options.navClient).toBe(OutgoingServer.navClient) + expect(options.navBalance).toBe(50000) + expect(callback).toBe(OutgoingServer.currentBatchPrepared) + expect(OutgoingServer.processing).toBe(true) + expect(OutgoingServer.paused).toBe(false) + sinon.assert.notCalled(mockLogger.writeLog) + done() + }, + } + + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.__set__('PrepareOutgoing', PrepareOutgoing) + OutgoingServer.navClient = mockClient + OutgoingServer.feePaid(true, { transaction: 123, error: 'failed' }) + }) + it('should stop if it couldnt get the balance after sending the fee', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + + const mockClient = { + getBalance: () => { return Promise.reject({ err: { code: -22 } }) }, + } + + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + OutgoingServer.navClient = mockClient + OutgoingServer.feePaid(true, { transaction: 123, error: 'failed' }) + done() + + }) + }) + describe('(subnavReturned)', () => { + beforeEach(() => { // reset the rewired functions + OutgoingServer = rewire('../src/outgoing') + }) + it('should fail to return the subnav and pause', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + + OutgoingServer.subnavReturned(false) + + sinon.assert.calledOnce(mockLogger.writeLog) + sinon.assert.calledWith(mockLogger.writeLog, 'OUT_007') + expect(OutgoingServer.processing).toBe(false) + expect(OutgoingServer.paused).toBe(true) + done() + }) + it('should fail to return the subnav and end', (done) => { + OutgoingServer.processing = true + OutgoingServer.paused = false + const mockLogger = { writeLog: sinon.spy() } + OutgoingServer.__set__('Logger', mockLogger) + + OutgoingServer.subnavReturned(true) + + sinon.assert.notCalled(mockLogger.writeLog) + expect(OutgoingServer.processing).toBe(false) + expect(OutgoingServer.paused).toBe(false) + done() + }) + }) +})