diff --git a/payments/README.md b/payments/README.md index af79504..80ba575 100644 --- a/payments/README.md +++ b/payments/README.md @@ -19,8 +19,216 @@ In order to run test successfully, you will need to have test wallet for which t Safex payments module can be used as REST service and/or nodeJS module. - node REST_service.js -# Current steps -- Write tests and be thorough -- Specify API with easy use on FE part -- Implement SSL/TLS on REST service. +Config json is main configuration file for SfxPaymentModule: +Field name | Description +------------ | ------------- + nodeRPCPort | RPC Port for local node. + walletRPCPort | RPC port for local safex-wallet-rpc + scanningSpan | Number of blocks to be scanned from the end of blockchain. + listeningPeriod | Time period on which scan is performed + port | Listening port of REST Service + +Every REST Service response has error and timestamp field by default. If error field is set to false, there will be result field with targeted data. Otherwise it will be error_msg indicating what was error which happened. +REST Service has next exposed endpoints + +>@IMPORTANT: Confirmations are calculated from last block which contains tx linked with given paymentId!!! + +### **POST** /getpaymentinfo + +##### Description: +*Getting paymentinfo from internal book keeping of payment module. This is roughly around scanningSpan number of blocks.* + +##### Request Data: +```json +{ "paymentId" : "a1b2c5d4e5e61236" } +``` +##### Response: +```json +{ + "error": false, + "result": { + "paymentId": "a1b2c5d4e5e61236", + "confirmations": 178, + "totalAmount": 1500000000000 + }, + "timestamp": "2018-11-17T22:27:49.000Z" +} +``` + +### **POST** /getpaymentinfowholebc + +##### Description: +*Getting payment info connected to paymentId scaning entire blockchain. Its intended for dispute solving and debugging.* + +##### Request Data: + +```json{ "paymentId" : "a1b2c5d4e5e61236" }``` + +##### Response: + +```json +{ + "error": false, + "result": { + "paymentId": "a1b2c5d4e5e61236", + "confirmations": 179, + "totalAmount": 1500000000000 + }, + "timestamp": "2018-11-17T22:33:05.506Z" +} +``` + +### **POST** /getintegratedaddress + +##### Description: +*Get integrated address based on given paymentId* + +##### Request Data: + +```json +{ "paymentId" : "a1b2c5d4e5e61236" } +``` + +##### Response: + +```json +{ + "error": false, + "result": { + "integrated_address": "SFXti9o1apCRhgUEU4FVJjBkNS9sarNpbexP6YfZgDYv3bcSVwCZtm9PWnpkoRiifC3uMQJS9ihFmNTbUXr2eWgY7LUMiRvnD3RfTJin1xguGC" + }, + "timestamp": "2018-11-17T22:34:40.828Z" +} +``` + +### **POST** /splitintegratedaddress + +##### Description: +*For given integrated address returns paymentId and payment address.* + +##### Request Data: + +```json +{ "integratedAddress" : "SFXti9o1apCRhgUEU4FVJjBkNS9sarNpbexP6YfZgDYv3bcSVwCZtm9PWnpkoRiifC3uMQJS9ihFmNTbUXr2eWgY7LUMiRvnD3RfTJin1xguGC" +} +``` + +##### Response: + +```json +{ + "error": false, + "result": { + "paymentId": "a1b2c5d4e5e61236", + "address": "SFXtzWd5wWCRhgUEU4FVJjBkNS9sarNpbexP6YfZgDYv3bcSVwCZtm9PWnpkoRiifC3uMQJS9ihFmNTbUXr2eWgY7LUMiPBHFgq" + }, + "timestamp": "2018-11-17T22:44:41.770Z" +} +``` + +### **POST** /getpaymentaddress +##### Description: + +##### Request Data: +```json {} ``` + +##### Response: + +```json +{ + "error": false, + "result": { + "payment_address": "SFXtzWd5wWCRhgUEU4FVJjBkNS9sarNpbexP6YfZgDYv3bcSVwCZtm9PWnpkoRiifC3uMQJS9ihFmNTbUXr2eWgY7LUMiPBHFgq" + }, + "timestamp": "2018-11-17T22:35:22.061Z" +} +``` + + +### **POST** /hardforkinfo + +##### Description: +*Getting some relevant information regarding hard fork. For more info see https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#hard_fork_info* + +##### Request Data: + +```json +{} +``` + +##### Response: + +```json +{ + "error": false, + "result": { + "enabled": true, + "state": 2, + "status": "OK", + "version": 2 + }, + "timestamp": "2018-11-17T22:37:14.282Z" +} +``` + +### **POST** /nodeinfo + +##### Description: +*Getting some relevant (filtered) information regarding node. https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_info* + +##### Request Data: + +```json +{} +``` + +##### Request Data: + +```json +{ + "error": false, + "result": { + "height": 45237, + "free_space": 15625752576, + "mainnet": false, + "stagenet": false, + "testnet": true, + "status": "OK", + "start_time": 1542493587 + }, + "timestamp": "2018-11-17T22:38:00.522Z" +} +``` + +### **POST** /openwallet + +##### Description: +*Wallet file should be located in dir specified when starting walletRPC. Its intended for debugging purposes and for remote activating wallet file. Empty result means success.* + +##### Request Data: + +```json +{ "filename" : "test.bin", "password" : "cicko" } +``` + + +##### Response: + +```json +{ + "error": false, + "result": {}, + "timestamp": "2018-11-17T22:51:37.838Z" +} +``` + +### Error example: + +```json +{ + "error": true, + "error_msg": "API Error: Failed to open wallet", + "timestamp": "2018-11-17T22:56:34.735Z" +} +``` diff --git a/payments/REST_service.js b/payments/REST_service.js index a66ad50..0705621 100644 --- a/payments/REST_service.js +++ b/payments/REST_service.js @@ -26,36 +26,41 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -var express = require("express"); -var bodyParser = require('body-parser') -const colors = require('colors'); -var router = express.Router() -var cookieParser = require('cookie-parser') -var errorHandler = require('errorhandler') +let express = require("express"); +let bodyParser = require('body-parser'); +let colors = require('colors'); +let cookieParser = require('cookie-parser'); +let errorHandler = require('errorhandler'); const https = require("https"); const fs = require("fs"); const helmet = require("helmet"); -const config = require('./config'); - const options = { - key: fs.readFileSync("./certs/host1.key"), - cert: fs.readFileSync("./certs/host1.cert") + key: fs.readFileSync("./certs/key.pem"), + cert: fs.readFileSync("./certs/cert.pem"), + + passphrase : '1234', + requestCert: true, + rejectUnauthorized: true, + + ca : [fs.readFileSync('./certs/client-cert.pem')] + }; const sfx_pay = require('./index'); -let sfxPayment = new sfx_pay.SafexPayments(config.walletRPCPort, config.nodeRPCPort); +let sfxPayment = new sfx_pay.Payments(); +sfxPayment.listenForPayments(); var app = express(); -app.use(helmet()); app.use(bodyParser.json()); app.use(cookieParser()); app.use(errorHandler()); const port = 3000; +// Forming REST request. let form_rest_response = function(result) { return { error: false, @@ -75,8 +80,12 @@ let form_rest_error = function(err) { app.post("/getpaymentinfo", function(req,res,next){ if(req.body.paymentId) { - sfxPayment.getPaymentStatusOne(req.body.paymentId).then((result) => { - res.json(form_rest_response(result)); + sfxPayment.getPaymentInfo(req.body.paymentId).then((result) => { + res.json(form_rest_response({ + paymentId : req.body.paymentId, + confirmations: sfxPayment.bcHeight - result.block_height, + totalAmount: result.total_amount + })); }).catch((e) => { res.json(form_rest_error(e)); }); @@ -90,10 +99,14 @@ app.post("/getpaymentinfo", function(req,res,next){ } }); -app.post("/getpaymentsinfo", function(req,res,next){ - if(req.body.paymentIds && req.body.startBlockHeight) { - sfxPayment.getPaymentStatusBulk(req.body.paymentIds, req.body.startBlockHeight).then((result) => { - res.json(form_rest_response(result)); +app.post("/getpaymentinfowholebc", function(req,res,next){ + if(req.body.paymentId) { + sfxPayment.getPaymentInfoWholeBC(req.body.paymentId).then((result) => { + res.json(form_rest_response({ + paymentId : req.body.paymentId, + confirmations: sfxPayment.bcHeight - result[0].block_height, + totalAmount: result[0].total_amount + })); }).catch((e) => { res.json(form_rest_error(e)); }); @@ -110,7 +123,24 @@ app.post("/getpaymentsinfo", function(req,res,next){ app.post("/getintegratedaddress", function(req,res,next){ if(req.body.paymentId) { sfxPayment.getIntegratedAddress(req.body.paymentId).then((result) => { - res.json(form_rest_response({ integrated_address: result})); + res.json(form_rest_response(result)); + }).catch((e) => { + res.json(form_rest_error(e)); + }); + } + else { + res.json({ + error : true, + error_msg: 'Invalid request!', + timestamp: new Date() + }) + } +}); + +app.post("/splitintegratedaddress", function(req,res,next){ + if(req.body.integratedAddress) { + sfxPayment.splitIntegratedAddress(req.body.integratedAddress).then((result) => { + res.json(form_rest_response(result)); }).catch((e) => { res.json(form_rest_error(e)); }); @@ -136,7 +166,7 @@ app.post("/getpaymentaddress", function(req, res, next){ app.post("/hardforkinfo", function(req, res, next){ sfxPayment.getHardForkInfo().then((result) => { - res.json(form_rest_response({ integrated_address: result})); + res.json(form_rest_response(result)); }).catch((e) => { res.json(form_rest_error(e)); }); @@ -144,7 +174,7 @@ app.post("/hardforkinfo", function(req, res, next){ app.post("/nodeinfo", function(req, res, next){ sfxPayment.getInfo().then((result) => { - res.json(form_rest_response({ integrated_address: result})); + res.json(form_rest_response(result)); }).catch((e) => { res.json(form_rest_error(e)); }); @@ -172,4 +202,5 @@ app.use(function(req, res){ res.json({ error: true, error_msg : 'Not found', timestamp: new Date() }); }); +app.listen(port); https.createServer(options, app).listen(port+1); diff --git a/payments/certs/generate_certs.sh b/payments/certs/generate_certs.sh old mode 100644 new mode 100755 index 3ec02ab..057f07e --- a/payments/certs/generate_certs.sh +++ b/payments/certs/generate_certs.sh @@ -1,7 +1,9 @@ #!/bin/bash -# Sample script to create cert/key pair for SSL/TLS communication. +# Sample script to create cert/key pair for SSL-TLS -openssl genrsa 1024 > host.key -chmod 400 host.key -openssl req -new -x509 -nodes -sha1 -days 365 -key host.key -out host.cert +# Creating key.pem cert.pem +openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 + +# Client keys +openssl req -x509 -newkey rsa:2048 -keyout client-key.pem -out client-cert.pem -days 365 \ No newline at end of file diff --git a/payments/certs/host1.cert b/payments/certs/host1.cert deleted file mode 100644 index 00b7931..0000000 --- a/payments/certs/host1.cert +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC3zCCAkigAwIBAgIJAOy/DxjSJK86MA0GCSqGSIb3DQEBBQUAMIGGMQswCQYD -VQQGEwJTUjEQMA4GA1UECAwHdGVzdGVzdDESMBAGA1UEBwwJdGVyc3R0ZXN0MRAw -DgYDVQQKDAd0ZXN0ZXRzMRAwDgYDVQQLDAd0ZXN0ZXRzMQ8wDQYDVQQDDAZzdHN0 -c3QxHDAaBgkqhkiG9w0BCQEWDWV0YXRAYXNkYS5jb20wHhcNMTgxMTE0MTEzMzI2 -WhcNMTkxMTE0MTEzMzI2WjCBhjELMAkGA1UEBhMCU1IxEDAOBgNVBAgMB3Rlc3Rl -c3QxEjAQBgNVBAcMCXRlcnN0dGVzdDEQMA4GA1UECgwHdGVzdGV0czEQMA4GA1UE -CwwHdGVzdGV0czEPMA0GA1UEAwwGc3RzdHN0MRwwGgYJKoZIhvcNAQkBFg1ldGF0 -QGFzZGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq96kPfXVQ/mSR -6BUaUmHVmvxCwnnHXR27/pkXCvCw995D25lILGp33tIHubmcajzItQW9XD7bSqkM -+cdfoByK9uRYxPJBu3ksFGLmT3lwv/ogAeo1WEwFqp8elB2hmhnOVIPwv0GLocX6 -w9/SUPnHwaJV5SWPlhjJLwuRIHX91wIDAQABo1MwUTAdBgNVHQ4EFgQUZGxqPyqf -rSYW1N0AF2pvJHAOc4kwHwYDVR0jBBgwFoAUZGxqPyqfrSYW1N0AF2pvJHAOc4kw -DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQB3izUv01Ka5xvm4sE6 -VuBu2kJUQDCq1KWDgVPePiHc6CCxIi0UaK2ItKn8R/tBeqHoWca+Fe7E2aYm8WAS -m0V63lGZQR4ivxqhDcIVNHSvRj0Hfo2rglswt3JI+M5U3umng4FoYEK/fcpi/ccu -CsOanZX7AvTJdXj0PPToa/C+Gg== ------END CERTIFICATE----- diff --git a/payments/certs/host1.key b/payments/certs/host1.key deleted file mode 100644 index 2c9ef35..0000000 --- a/payments/certs/host1.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQCq96kPfXVQ/mSR6BUaUmHVmvxCwnnHXR27/pkXCvCw995D25lI -LGp33tIHubmcajzItQW9XD7bSqkM+cdfoByK9uRYxPJBu3ksFGLmT3lwv/ogAeo1 -WEwFqp8elB2hmhnOVIPwv0GLocX6w9/SUPnHwaJV5SWPlhjJLwuRIHX91wIDAQAB -AoGAdlgq0YshiygdC5agDPFbwZWf6sJds6/0Ji7NX9L12zoow8Ydq4T7SfCWaDya -i/hKDRCpYXOaGwfqTw4TgVD+j91FelOwpwPWctNSPdPF8Ks7ZipG3I5ugUKpeijW -ypg9pM4EV4Uk61vtPQxdRulQ9v7UAFSZ6/GQ7vzByigA4nECQQDSnfRXVfQDiJVW -vAt8MqAZSlk+2D3L/lwHz3tIu9R37eYpR4oVHWux6o7SV4IPb8aIK6AoIyP3auDZ -fGZBwGh/AkEAz86OrV9MKfTUJojMeBiw/JMRUKZzGjo328ejDkAcBLf/PWfsYZRV -1dD/u7qqXKkdEClo29Yc+vHqT6Es1b3+qQJBAIMGtxJ6K5LjLpzF4ZNDX2w8X6cE -GfXFaPWG81h9WaG+g3hcxB98rRoDJRTgUc5OqEDgewNdUSvD9tv2UxtGmvsCQQCt -xWnUGrOjBiN90blB2Evv+b9p9LadwP9C3u9AFGYZoA9lNwkZFCSu9uVTA6ZAc/5k -rGwOIT78rd0lF39ZgVZ5AkAb+/qEfBDrciBlypqP2KtRwO0NsKnQmk9jiICfdb80 -+RPZFlhYNe3gMAYhXgdcp4zf7s1Kzfchi3YoSL0JBP/r ------END RSA PRIVATE KEY----- diff --git a/payments/certs/localhost.crt b/payments/certs/localhost.crt new file mode 100644 index 0000000..56fa868 --- /dev/null +++ b/payments/certs/localhost.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC5TCCAc2gAwIBAgIJAMy1vjAwNQwjMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAeFw0xODExMTcyMjAyMzZaFw0xODEyMTcyMjAyMzZaMBQx +EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALJ5a6kr5PsIr1Ug7wseLza4V7QBlk6pWO0dnyYkQvcEyuRymXf3t9GG6ZRa +RS4bSjif6UQ4dQN3IUo/XP4Ciz7iKonmC6by5vafk2quTUHllSQBKVbyjCPAXtKq +6WPdwTn8/VN7nNVSk/8+x1JOd+voQAzydmMNf3nbb9PnG0qgyOv3QwDa+lgsxKPR +hdwVAiKi+OhCaiurQ1mcVrwIs9oqjugnEKvyODwTRoZKoWxksSaEAjGQR4cA+NsX +JM9tHYEDLoP7dDKtXWUm0VgoLXzxMWdMnh/wTnB8zpXsYvQNh8wfj0U2euDFeNmS +OR2WoAskDbmao/vAZMLv/DfcdyUCAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo +b3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B +AQsFAAOCAQEAQLrqzi8NMEniMpUQ2uN/NiAERvMFWIhuu8QtAZz/bzNbXReQtGqf +LE8hsJR8AnpHJyhwcHDNVAsVXJN0Iysg+U7afVAX7DlYPURb8eOtzFl4f0NYxYqU +5bhBBwVOQgZ7LPaMyiDA6Nql3vEkb5k2pV8+7+pHs+zKUHum6YrZUQmNEBaBXLk2 +Z/M8FVlRBP9Ig2rHuf/sksQJSCrt72N7zxMLb1er7dhjFYIJ0r3+9dBiOPQjjObO +6w4UgjdRGoplXDJVH3rUQ5vF8N3oVw9bXDyFwNyQLYFi4FCJBB7HIJ6h+bjoAnva +e8U3ISllvwNvbHJgrPdqvdXXousLfX5e/g== +-----END CERTIFICATE----- diff --git a/payments/certs/localhost.key b/payments/certs/localhost.key new file mode 100644 index 0000000..fdb1432 --- /dev/null +++ b/payments/certs/localhost.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCyeWupK+T7CK9V +IO8LHi82uFe0AZZOqVjtHZ8mJEL3BMrkcpl397fRhumUWkUuG0o4n+lEOHUDdyFK +P1z+Aos+4iqJ5gum8ub2n5Nqrk1B5ZUkASlW8owjwF7Squlj3cE5/P1Te5zVUpP/ +PsdSTnfr6EAM8nZjDX9522/T5xtKoMjr90MA2vpYLMSj0YXcFQIiovjoQmorq0NZ +nFa8CLPaKo7oJxCr8jg8E0aGSqFsZLEmhAIxkEeHAPjbFyTPbR2BAy6D+3QyrV1l +JtFYKC188TFnTJ4f8E5wfM6V7GL0DYfMH49FNnrgxXjZkjkdlqALJA25mqP7wGTC +7/w33HclAgMBAAECggEBALCxe0ASH/HUaFl8cIzuRkoDeXjR9XBNQqgGIpENZ2rN +SN2hA4V9L3R1//yK3mDGTdX/o16SEm7q2395eeRUumz4VqsNoM2ZP8Y8uCLvC7fI +xr3NlJLrMIkYpAgng0cGvY1ehaLIB74+sh0KlyVKYHmjBv6KcWChdWo8MNt2u1NG +cjPTTTCz+SkV5LXYQ/A1KTXnrGVBKwTiDmvyusyPU5x6mKIOFJY8dUNiijUsLYjI +2Ic4p0xM26i6pXtghP4f7Pq8StvOn8kIL2jMlf2trPPvdmTXr6m26pHZ4VBxlQta +za5INnl/7W0bzLkr0ISalqMU0o0yZ78s4hIl6MKgLUECgYEA2dCwEZcfYuPVAiio +QgIGq7A/+VdLQ8U45F0eXVJ3y5nGAKwcL5CFME7d+P51SvOAQm7MFb0/Jml4MWJ6 +LIaCaX54ka8y/5AfGewcGChhwGNQxJyEOUgNBr8azvJ/0Xd8JGu5M8iqC3WQIxkn +x0cvG5eoN2wmCAnM0zpNPLoL5DUCgYEA0cMn6Jj9+ex1F4FM8xeVLfsZ16k/hr4k +/k7h+8RILZBjZiwzwr6pXVMypy3LSB1Ob8xohGLokXF4k+8hR2gYUD9omXoguzbu +xTL193NVFVszRgqiLt7QXirxn9S3Z+SIvEUPusLcYm0HwaixZSCQk/JW9ZpGmqcS +8VDsqR41xTECgYEA0WnLrc3O1kPXyrCOPJA4G59jknV0Hl8iuKQyimS8WlqovIK8 +wpo/A1gAH+F9oacE/FoMCyac7XBfp5NXhTF5drWqc96uuHi2/jQ8OpmwiciyoaUd +lvkH1SGtQ6RwcVJgX5/WOsxFeKj6AFSyIuz0hKHFkCuBUR8i9z5JfgYdYrUCgYAr +pPN0zgBqM+zZCyo7mjiL7Kcf3DM/keiYkUkr3Vc3Q8eqxp+ucYVDyb/MURbu1kov +sNQ+V2YUfQudnC/Mb7g/WjIlXOfajD96AHR1HCYj32n3tNax8eJg9YZ4vwW6NC7P +HX98WHcKc60dekAOzovGfLHjQEpYe1LRqwjS11pQwQKBgQDPzAPGekeCq9o/K9vn +DIGQCbH5GQ/F27Ye9NfwquoAcu5jHiqefihdXZ1HUvU/BofUJssjGdXT/cAlVpFv +vXW5kYCOLEM7plS3lMmiiCVigNveY9d8QXj5jJXRMQVehxRcXs2P/ngRR1A4CwRU +ApeF/uzj/oroNatLegYaVlYENA== +-----END PRIVATE KEY----- diff --git a/payments/config.json b/payments/config.json index e8517c8..7e99e25 100644 --- a/payments/config.json +++ b/payments/config.json @@ -1,4 +1,7 @@ { "nodeRPCPort": 29393, - "walletRPCPort" : 10032 + "walletRPCPort" : 10032, + "scanningSpan" : 40, + "acceptancePeriod" : 10, + "listeningPeriod" : 120000 } \ No newline at end of file diff --git a/payments/index.js b/payments/index.js index caba427..04f6582 100644 --- a/payments/index.js +++ b/payments/index.js @@ -26,117 +26,184 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const color = require('colors'); -const bigInt = require('big-integer'); -const walletRpc = require('./src/wallet-rpc').WalletRPC; -const nodeRpc = require('./src/node-rpc').NodeRPC; +let sfx_pay = require('./src/sfx-payments'); +const config = require('./config.json'); +// Final layer of payments module. +class Payments { + + constructor() { + this.ledger = new Map; + this.lastBlockHeightScanned = 0; + this.scanningSpan = config.scanningSpan; + this.sfxPayments = new sfx_pay.SafexPayments(config.walletRPCPort, config.nodeRPCPort); + this.bcHeight = 0; + + // Leaving as data field for possible future use. + this.handlerListening = setInterval(this.listenForPayments.bind(this), config.listeningPeriod); + this.handlerClear = setInterval(this.clearLedger.bind(this), config.listeningPeriod * 5); -class SafexPayments { - constructor(walletRPCPort, nodeRPCPort) { - this.walletRPC = new walletRpc(walletRPCPort); - this.nodeRpc = new nodeRpc(nodeRPCPort); } +// Saving next information +// +// paymentId -> { +// total_amount => Total amount recorded for paymentId +// block_height => Highest block with tx recorded +// tx_hashes[] => Tx hashes associated with paymentId. +// } +// + // Summarize payment data to be more friendly to payments checking. + async summarizeByPamentId(payments) { + var returnVal = []; + var processPaymentId = function(paymentId) { + var total_sum = 0; + var block_height = 0; + var tx_hashes = []; + paymentId.txs.forEach((tx) => { + total_sum += tx.amount; + if(block_height < tx.block_height) + block_height = tx.block_height; + tx_hashes.push(tx.tx_hash); + }); - /** - * Returning TX statuses for given paymentId. Returned values are from the beginning of blockchain. - * @param paymentId - * @returns {Promise { - returnVals.push({ - amount : input.amount, - block_height : input.block_height, - tx_hash : input.tx_hash, - unlock_time: input.unlock_time - }); + returnVal.push({ + paymentId : paymentId.paymentId, + total_amount : total_sum, + block_height : block_height, + tx_hashes : tx_hashes }); - } + }; - return {paymentId: paymentId, txs: returnVals}; + processPaymentId(payments); + + return returnVal; } - async getPaymentStatusBulk(paymentIds, start_block_height) { - let results = await this.walletRPC.checkForPayments(paymentIds, start_block_height); - - // PostProcessing - var returnVals = new Object(); - - if(results.result.payments){ - // Refer test/test_data/getPaymentMultiple.json to see how input looks like for multiple tx for same - // paymentId. - results.result.payments.forEach((input) => { - if(! returnVals.hasOwnProperty(input.payment_id)) { - returnVals[input.payment_id] = { - paymentId: input.payment_id, - txs: [] - }; + /* + * Getting info for given paymentId for whole bc + * */ + async getPaymentInfoWholeBC(paymentId) { + return new Promise((resolve, reject) => { + this.sfxPayments.getPaymentStatusOne(paymentId).then((vals) => { + resolve(this.summarizeByPamentId(vals)); + }).catch((err)=> { + reject(err); + }); + }); + } + + /* + * Getting paymentId from intern ledger for book keeping of txs. + * */ + async getPaymentInfo(paymentId) { + return new Promise((resolve, reject) => { + if(this.ledger.has(paymentId)){ + resolve(this.ledger.get(paymentId)); + } + resolve({block_height: 0, total_amount: 0}); + }); + } + + /* + * Clearing paymentIds which are not in given time span + * */ + clearLedger() { + this.ledger.forEach((value, key) => { + if (this.lastBlockHeightScanned - value.block_height > this.scanningSpan) { + this.ledger.delete(key); + } + }); + } + + /* + * Function for updating inner ledger from batch of paymentIds from wallet. + * */ + updateLedger(payments) { + let process = (payment) => { + // Check if tx exists already recorded in our ledger and process it if its not. + payment.txs.forEach((tx) => { + // Recognize short paymentIds + var pid = payment.paymentId.replace(/0*$/,""); + if(payment.paymentId.length - pid.length == 48) { + payment.paymentId = pid; + } + if (!this.ledger.has(payment.paymentId)) { + this.ledger.set(payment.paymentId, {total_amount: 0, block_height: 0, tx_hashes: []}); + } + + if (!this.ledger.get(payment.paymentId).tx_hashes.includes(tx.tx_hash)) { + this.ledger.get(payment.paymentId).total_amount += tx.amount; + this.ledger.get(payment.paymentId).tx_hashes.push(tx.tx_hash); + if (tx.block_height > this.ledger.get(payment.paymentId).block_height) { + this.ledger.get(payment.paymentId).block_height = tx.block_height; + } } - returnVals[input.payment_id].txs.push({ - amount : input.amount, - block_height : input.block_height, - tx_hash : input.tx_hash, - unlock_time: input.unlock_time - }); }); + }; + + if (payments instanceof Array) { + payments.forEach(process); + } else { + process(payments); } + } + + listenForPayments() { + // Get payment info from node + + this.sfxPayments.getLastBlockHeight().then((height) => { + if (height > this.lastBlockHeightScanned) { + this.sfxPayments.getPaymentStatusBulk([], height - this.scanningSpan) + .then((payments) => { + this.updateLedger(payments, height); + + }); + this.bcHeight = height; + this.lastBlockHeightScanned = height - this.scanningSpan; + } + - return Object.values(returnVals); + }); } + /* + * Get integrated address based on given paymentId + * */ async getIntegratedAddress(paymentId) { - let results = await this.walletRPC.makeIntegratedAddress(paymentId); - return { integrated_address : results.result.integrated_address}; + return this.sfxPayments.getIntegratedAddress(paymentId); } - async getInfo() { - let results = await this.nodeRpc.getInfo(); - return { - height: results.result.height, - free_space: results.result.free_space, - mainnet: results.result.mainnet, - stagenet: results.result.stagenet, - testnet: results.result.testnet, - status: results.result.status, - start_time: results.result.start_time - }; + async splitIntegratedAddress(integratedAddress) { + return this.sfxPayments.splitIntegratedAddress(integratedAddress); + } + /* + * Get address of connected wallet + * */ async getAddress() { - let results = await this.walletRPC.getAddress(); - return { payment_address : results.result.address}; + return this.sfxPayments.getAddress(); } + /* + * Get hardfork info from node. + * */ async getHardForkInfo() { - let results = await this.nodeRpc.getHardForkInfo(); - return { - enabled : results.result.enabled, - state : results.result.state, - status : results.result.status, - version : results.result.version - }; + return this.sfxPayments.getHardForkInfo(); } - async openWallet(filepath, password) { - let results = await this.walletRPC.open(filepath, password); - return results.result; - }; + /* + * Blockchain info from connected node. + * */ + async getInfo() { + return this.sfxPayments.getInfo(); + } + /* + * Opening wallet + * */ + async openWallet(file, pass) { + return this.sfxPayments.openWallet(file,pass); + } } - -module.exports.SafexPayments = SafexPayments; \ No newline at end of file + module.exports.Payments = Payments; \ No newline at end of file diff --git a/payments/package.json b/payments/package.json index 1cc5ab8..52aa5fb 100644 --- a/payments/package.json +++ b/payments/package.json @@ -2,7 +2,7 @@ "name": "safex-payments-module", "version": "0.0.1", "description": "Module for communication with safex-wallet-rpc.", - "main": "index.js", + "main": "src/sfx-payments.js", "scripts": { "test": "mocha" }, diff --git a/payments/playground.js b/payments/playground.js new file mode 100644 index 0000000..0ad3cd6 --- /dev/null +++ b/payments/playground.js @@ -0,0 +1,34 @@ +const utils = require('./index'); +const colors = require('colors'); + +console.log("Loading data".yellow); + +let test_data = {"paymentId":"52cf9717af42a7002a40e9ed09c383248fce78da9c6652fc9f28a5ddac9c0069","txs":[{"amount":250000000000,"block_height":41104,"tx_hash":"3db35a20cdbc215af901087258a7f14d361731ace99d5f927e89fdf87b96c8a2","unlock_time":0},{"amount":500000000000,"block_height":41104,"tx_hash":"f387b9de42801795061113e072cc9be2c41af3f88141588b432c9473ab70617f","unlock_time":0}]}; +let test_data2 = [{"paymentId":"52cf9717af42a7002a40e9ed09c383248fce78da9c6652fc9f28a5ddac9c0069","txs":[{"amount":250000000000,"block_height":41104,"tx_hash":"3db35a20cdbc215af901087258a7f14d361731ace99d5f927e89fdf87b96c8a2","unlock_time":0},{"amount":500000000000,"block_height":41104,"tx_hash":"f387b9de42801795061113e072cc9be2c41af3f88141588b432c9473ab70617f","unlock_time":0}]},{"paymentId":"a1b2c3d4e5f61234","txs":[{"amount":1500000000000,"block_height":41108,"tx_hash":"0b2c5b715d5089b052789b53185721605bd2c336d6a2c011a120089b648b22b6","unlock_time":0}]}]; + +const safex = require('safex-nodejs-libwallet'); +const path = require('path'); +const minute = 60000; +var wallet; +var sent = false; + +var args = { + 'path': './baaaa.bin', + 'password': '', + 'network': 'testnet', + 'daemonAddress': 'localhost:29393', + 'restoreHeight': 0, + 'mnemonic': 'deftly large tirade gumball android leech sidekick opened iguana voice gels focus poaching itches network espionage much jailed vaults winter oatmeal eleven science siren winter' +}; + +var p1 = "a1b2c4d4e5e61235"; +var p2 = "a1b2c5d4e5e61236"; +var p3 = "a1b2c6d4e5e61237"; + +console.log("Listening for payments.".yellow); + +let payments = new utils.Payments(); +payments.listenForPayments(); +setTimeout(payments.clearLedger.bind(payments), 500); + +console.log(JSON.stringify([...(payments.ledger)])); \ No newline at end of file diff --git a/payments/src/rpcCall.js b/payments/src/rpcCall.js index 4b73e1f..e80bec8 100644 --- a/payments/src/rpcCall.js +++ b/payments/src/rpcCall.js @@ -40,11 +40,11 @@ async function executeMethod(rpcEndpoint, methodName, params) { params: params }; if(debugRPCCall) { - console.log('====================== REQ ===================================='.yellow); - console.log('#DEBUG: Host: '.magenta + rpcEndpoint); - console.log('#DEBUG: Method: '.magenta + methodName); - console.log('#DEBUG: Request body: '.magenta + JSON.stringify(json_data)); - console.log('==============================================================='.yellow); + console.debug('====================== REQ ===================================='.yellow); + console.debug('#DEBUG: Host: '.magenta + rpcEndpoint); + console.debug('#DEBUG: Method: '.magenta + methodName); + console.debug('#DEBUG: Request body: '.magenta + JSON.stringify(json_data)); + console.debug('==============================================================='.yellow); } request.post(rpcEndpoint, {json: json_data}, (error, res, body) => { if (error) { @@ -57,10 +57,10 @@ async function executeMethod(rpcEndpoint, methodName, params) { if (body.error) { if(debugRPCCall) { - console.log('====================== REQ ERR ================================'.red); - console.log('#DEBUG: Method: '.yellow + methodName); - console.log('#DEBUG: '.yellow + JSON.stringify(body)); - console.log('==============================================================='.red); + console.debug('====================== REQ ERR ================================'.red); + console.debug('#DEBUG: Method: '.yellow + methodName); + console.debug('#DEBUG: '.yellow + JSON.stringify(body)); + console.debug('==============================================================='.red); } reject('API Error: '+ body.error.message); return; diff --git a/payments/src/sfx-payments.js b/payments/src/sfx-payments.js new file mode 100644 index 0000000..8444172 --- /dev/null +++ b/payments/src/sfx-payments.js @@ -0,0 +1,152 @@ +// Copyright (c) 2018, The Safex Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +const color = require('colors'); +const bigInt = require('big-integer'); +const walletRpc = require('./wallet-rpc').WalletRPC; +const nodeRpc = require('./node-rpc').NodeRPC; + + +class SafexPayments { + constructor(walletRPCPort, nodeRPCPort) { + this.walletRPC = new walletRpc(walletRPCPort); + this.nodeRpc = new nodeRpc(nodeRPCPort); + } + + /** + * Returning TX statuses for given paymentId. Returned values are from the beginning of blockchain. + * @param paymentId + * @returns {Promise { + returnVals.push({ + amount : input.amount, + block_height : input.block_height, + tx_hash : input.tx_hash, + unlock_time: input.unlock_time + }); + }); + } + + return {paymentId: paymentId, txs: returnVals}; + } + + async getPaymentStatusBulk(paymentIds, start_block_height) { + let results = await this.walletRPC.checkForPayments(paymentIds, start_block_height); + + // PostProcessing + var returnVals = new Object(); + + if(results.result.payments){ + // Refer test/test_data/getPaymentMultiple.json to see how input looks like for multiple tx for same + // paymentId. + results.result.payments.forEach((input) => { + if(! returnVals.hasOwnProperty(input.payment_id)) { + returnVals[input.payment_id] = { + paymentId: input.payment_id, + txs: [] + }; + } + returnVals[input.payment_id].txs.push({ + amount : input.amount, + block_height : input.block_height, + tx_hash : input.tx_hash, + unlock_time: input.unlock_time + }); + }); + } + + return Object.values(returnVals); + } + + async getIntegratedAddress(paymentId) { + let results = await this.walletRPC.makeIntegratedAddress(paymentId); + return { integrated_address : results.result.integrated_address}; + } + + async splitIntegratedAddress(integratedAddress) { + let results = await this.walletRPC.splitIntegratedAddress(integratedAddress); + return { paymentId: results.result.payment_id, address: results.result.standard_address}; + } + + async getInfo() { + let results = await this.nodeRpc.getInfo(); + return { + height: results.result.height, + free_space: results.result.free_space, + mainnet: results.result.mainnet, + stagenet: results.result.stagenet, + testnet: results.result.testnet, + status: results.result.status, + start_time: results.result.start_time + }; + } + + async getAddress() { + let results = await this.walletRPC.getAddress(); + return { payment_address : results.result.address}; + } + + async getLastBlockHeight() { + let results = await this.nodeRpc.getLastBlockHeight(); + return results.result.count; + } + + async getHardForkInfo() { + let results = await this.nodeRpc.getHardForkInfo(); + return { + enabled : results.result.enabled, + state : results.result.state, + status : results.result.status, + version : results.result.version + }; + } + + async openWallet(filepath, password) { + let results = await this.walletRPC.open(filepath, password); + return results.result; + }; + +} + +module.exports.SafexPayments = SafexPayments; \ No newline at end of file diff --git a/payments/src/sqliteWrapper.js b/payments/src/sqliteWrapper.js deleted file mode 100644 index 058124f..0000000 --- a/payments/src/sqliteWrapper.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = { - query: query -} - -const sqlite3 = require('sqlite3').verbose(); - -let db = new sqlite3.Database('db/test.db', sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => { - if(err) { - console.error(err.message); - } - else { - console.log('Connected to the database!!'); - } - -}); - -function query(queryString, callback) { - db.all(queryString, [], (err, rows) => { - callback(rows, err); - }); -} \ No newline at end of file diff --git a/payments/test/test_SafexPayments.js b/payments/test/test_SafexPayments.js index eab1033..d2ef5bf 100644 --- a/payments/test/test_SafexPayments.js +++ b/payments/test/test_SafexPayments.js @@ -36,7 +36,7 @@ const config = require('../config.json'); chai.use(chaiAsPromised); -const sfx_pay = require('../index'); +const sfx_pay = require('../src/sfx-payments'); describe('SafexPayments', function(){ describe('#getPaymentStatusOne()', function(){