Compare commits

...
12 Commits
7 changed files with 389 additions and 27 deletions
+14 -14
View File
@@ -9,12 +9,12 @@
"version": "2.0.0",
"license": "MIT",
"dependencies": {
"@psf/bch-js": "6.5.7",
"@psf/bch-js": "6.7.3",
"axios": "0.27.2",
"bcryptjs": "2.4.3",
"glob": "7.1.6",
"ipfs": "0.62.3",
"ipfs-coord": "8.0.4",
"ipfs-coord": "8.0.8",
"ipfs-http-client": "55.0.0",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
@@ -1445,9 +1445,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@psf/bch-js": {
"version": "6.5.7",
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-6.5.7.tgz",
"integrity": "sha512-V9UweiRCCU4UzM51Ct2Szhpv6V0B686t5ZjMHNkPu8zyJ9+H7mQfteeQECHK5j38QHvLchfUOif5WWQxP6GRVQ==",
"version": "6.7.3",
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-6.7.3.tgz",
"integrity": "sha512-z6oJvPAXxSObPhqUAxCkCuPL2bp3Bs1oyhBZpQ5SR6cbJzcOMhuI4bHho2srH5agSCU+bbjehBMRGvo18eGW0A==",
"license": "MIT",
"dependencies": {
"@chris.troutner/bip32-utils": "1.0.5",
@@ -7728,9 +7728,9 @@
}
},
"node_modules/ipfs-coord": {
"version": "8.0.4",
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-8.0.4.tgz",
"integrity": "sha512-jwZsrDlEWSHMnAde8PdyUScTzRlTCIvPAziChoczj5ZOZJIUbxLNkGphBI0xDj2G/LzjPrU1Ko/QZrU+oLFUnw==",
"version": "8.0.8",
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-8.0.8.tgz",
"integrity": "sha512-2ivZ7dCIrpS7VBN0DohaGBYRe3bzybkGbl0lOTyNeCUhrrYniC66dAnfdCY8CmpEeXoEYsaW2hLmohz4jDHywQ==",
"license": "MIT",
"dependencies": {
"axios": "0.21.4",
@@ -20970,9 +20970,9 @@
"version": "1.1.0"
},
"@psf/bch-js": {
"version": "6.5.7",
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-6.5.7.tgz",
"integrity": "sha512-V9UweiRCCU4UzM51Ct2Szhpv6V0B686t5ZjMHNkPu8zyJ9+H7mQfteeQECHK5j38QHvLchfUOif5WWQxP6GRVQ==",
"version": "6.7.3",
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-6.7.3.tgz",
"integrity": "sha512-z6oJvPAXxSObPhqUAxCkCuPL2bp3Bs1oyhBZpQ5SR6cbJzcOMhuI4bHho2srH5agSCU+bbjehBMRGvo18eGW0A==",
"requires": {
"@chris.troutner/bip32-utils": "1.0.5",
"@psf/bip21": "2.0.1",
@@ -25362,9 +25362,9 @@
}
},
"ipfs-coord": {
"version": "8.0.4",
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-8.0.4.tgz",
"integrity": "sha512-jwZsrDlEWSHMnAde8PdyUScTzRlTCIvPAziChoczj5ZOZJIUbxLNkGphBI0xDj2G/LzjPrU1Ko/QZrU+oLFUnw==",
"version": "8.0.8",
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-8.0.8.tgz",
"integrity": "sha512-2ivZ7dCIrpS7VBN0DohaGBYRe3bzybkGbl0lOTyNeCUhrrYniC66dAnfdCY8CmpEeXoEYsaW2hLmohz4jDHywQ==",
"requires": {
"axios": "0.21.4",
"bch-encrypt-lib": "2.0.0",
+2 -2
View File
@@ -24,12 +24,12 @@
},
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-service",
"dependencies": {
"@psf/bch-js": "6.5.7",
"@psf/bch-js": "6.7.3",
"axios": "0.27.2",
"bcryptjs": "2.4.3",
"glob": "7.1.6",
"ipfs": "0.62.3",
"ipfs-coord": "8.0.4",
"ipfs-coord": "8.0.8",
"ipfs-http-client": "55.0.0",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
+2 -2
View File
@@ -18,8 +18,8 @@ export DEBUG_LEVEL=2
# Production settings that use external IPFS node.
# https://github.com/christroutner/docker-ipfs
export SVC_ENV=production
#export IPFS_HOST=localhost
export IPFS_HOST=172.17.0.1
export IPFS_HOST=localhost
# export IPFS_HOST=172.17.0.1
export IPFS_API_PORT=5001
# Configure IPFS ports
+143
View File
@@ -60,6 +60,10 @@ class BCHRPC {
await this.rateLimit.limiter(rpcData.from)
return await this.utxos(rpcData)
case 'utxosBulk':
await this.rateLimit.limiter(rpcData.from)
return await this.utxosBulk(rpcData)
case 'broadcast':
await this.rateLimit.limiter(rpcData.from)
return await this.broadcast(rpcData)
@@ -366,6 +370,145 @@ class BCHRPC {
}
}
/**
* @api {JSON} /bch UTXOs Bulk
* @apiPermission public
* @apiName UTXOs Bulk
* @apiGroup JSON BCH
* @apiDescription This endpoint provides the same data as as the utxos
* endpoint, but it allows retrieval of UTXOs for up to 20 addresses at
* a time. This reduces the number of JSON RPC calls, and is very handy
* for HD wallets that need to quickly scan a lot of addresses.
*
* Given an array of addresses, this endpoint will return an object
* with the following properties
*
* - jsonrpc: "" - jsonrpc version
* - id: "" - jsonrpc id
* - result: {} - Result of the request with the RPC information
* - method: "" - Method used in the request
* - receiver: "" - Receiver address
* - value: [] - Final result value of the request
* - address: "" - the address these UTXOs are associated with
* - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH
* - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and
* not spent, to be safe.
* - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens
* - type1: {}
* - tokens: [] - SLP token Type 1 tokens.
* - mintBatons: [] - SLP token Type 1 mint batons.
* - nft: {}
* - tokens: [] - NFT tokens
* - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
* - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "utxos", "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"}}
*
* @apiSuccessExample {json} Success-Response:
*
* {
* "jsonrpc":"2.0",
* "id":"555",
* "result":{
* "method":"bch",
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
* "value":[
* {
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj",
* "bchUtxos":[
* {
* "height":631219,
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
* "tx_pos":0,
* "value":1000,
* "txid":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
* "vout":0,
* "isValid":false
* }
* ],
* "nullUtxos":[
*
* ],
* "slpUtxos":{
* "type1":{
* "mintBatons":[
*
* ],
* "tokens":[
*
* ]
* },
* "nft":{
* "groupMintBatons":[
*
* ],
* "groupTokens":[
*
* ],
* "tokens":[
*
* ]
* }
* }
* }
* ]
* }
* }
*
*/
async utxosBulk (rpcData) {
try {
// console.log('createUser rpcData: ', rpcData)
const addrs = rpcData.payload.params.addresses
// console.log('addrs: ', addrs)
// Input validation
if (!Array.isArray(addrs)) {
throw new Error('addresses parameter must be an array')
}
if (addrs.length > 20) {
throw new Error('addresses parameter must not exceed 20 elements')
}
const result = []
for (let i = 0; i < addrs.length; i++) {
const thisAddr = addrs[i]
const data = await this.bchjs.Utxo.get(thisAddr)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
result.push({
address: thisAddr,
utxos: data
})
}
// console.log('result: ', result)
const retObj = {}
retObj.status = 200
retObj.success = true
retObj.data = result
// console.log('retObj: ', retObj)
return retObj
} catch (err) {
console.error('Error in JSON RPC BCH utxosBulk()')
console.error('Error in JSON RPC utxosBulk(): ', err)
// throw err
// Return an error response
return {
success: false,
status: 422,
message: err.message,
endpoint: 'utxosBulk'
}
}
}
/**
* @api {JSON} /bch Broadcast
* @apiPermission public
@@ -27,7 +27,7 @@ describe('#BCH', () => {
payload: {
params: {
utxo: {
txid: 'b94e1ff82eb5781f98296f0af2488ff06202f12ee92b0175963b8dba688d1b40',
txid: 'a2059b1321e96a90a386894a68fa5829756118895b3cdb9a0393d94fd2ceed93',
vout: 0
}
}
@@ -45,7 +45,7 @@ describe('#BCH', () => {
payload: {
params: {
utxo: {
tx_hash: 'b94e1ff82eb5781f98296f0af2488ff06202f12ee92b0175963b8dba688d1b40',
tx_hash: 'a2059b1321e96a90a386894a68fa5829756118895b3cdb9a0393d94fd2ceed93',
tx_pos: 0
}
}
@@ -100,13 +100,13 @@ describe('#BCH', () => {
const rpcData = {
payload: {
params: {
tokenId: 'c85042ab08a2099f27de880a30f9a42874202751d834c42717a20801a00aab0d'
tokenId: '5f31905f335fa932879c5aabfd1c14ac748f6696148bd300f845ea5016ad573e'
}
}
}
const result = await uut.getTokenData(rpcData)
// console.log('result: ', result)
console.log('result: ', result)
// assert.equal(result.isValid, true)
assert.property(result.tokenData, 'immutableData')
@@ -118,6 +118,25 @@ describe('#BCHRPC', () => {
assert.equal(result, true)
})
it('should route to the utxosBulk method', async () => {
// Mock dependencies
sandbox.stub(uut, 'utxosBulk').resolves(true)
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const txCall = jsonrpc.request(id, 'bch', {
endpoint: 'utxosBulk'
})
const jsonStr = JSON.stringify(txCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
rpcData.from = 'Origin request'
const result = await uut.bchRouter(rpcData)
assert.equal(result, true)
})
it('should route to the broadcast method', async () => {
// Mock dependencies
sandbox.stub(uut, 'broadcast').resolves(true)
@@ -175,6 +194,63 @@ describe('#BCHRPC', () => {
assert.equal(result, true)
})
it('should route to the utxoIsValid method', async () => {
// Mock dependencies
sandbox.stub(uut, 'utxoIsValid').resolves(true)
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'utxoIsValid'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
rpcData.from = 'Origin request'
const result = await uut.bchRouter(rpcData)
assert.equal(result, true)
})
it('should route to the getTokenData method', async () => {
// Mock dependencies
sandbox.stub(uut, 'getTokenData').resolves(true)
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'getTokenData'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
rpcData.from = 'Origin request'
const result = await uut.bchRouter(rpcData)
assert.equal(result, true)
})
it('should route to the getTokenData2 method', async () => {
// Mock dependencies
sandbox.stub(uut, 'getTokenData2').resolves(true)
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'getTokenData2'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
rpcData.from = 'Origin request'
const result = await uut.bchRouter(rpcData)
assert.equal(result, true)
})
it('should return 500 status on routing issue', async () => {
// Mock dependencies
sandbox.stub(uut, 'txHistory').rejects(new Error('test error'))
@@ -301,7 +377,7 @@ describe('#BCHRPC', () => {
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'utxos',
addresses: 'testAddr'
address: 'testAddr'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
@@ -322,7 +398,7 @@ describe('#BCHRPC', () => {
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'utxos',
addresses: 'testAddr'
address: 'testAddr'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
@@ -337,6 +413,98 @@ describe('#BCHRPC', () => {
})
})
describe('#utxosBulk', () => {
it('should return UTXO data from bchjs for multiple addresses', async () => {
// Mock dependencies
sandbox.stub(uut.bchjs.Utxo, 'get').resolves({ success: true })
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'utxosBulk',
addresses: ['testAddr1', 'testAddr2']
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.utxosBulk(rpcData)
// console.log('response: ', response)
assert.equal(response.success, true)
assert.equal(response.status, 200)
})
it('should return an error for invalid address', async () => {
// Force an error
sandbox.stub(uut.bchjs.Utxo, 'get').rejects(new Error('Invalid address'))
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'utxosBulk',
addresses: ['testAddr1', 'testAddr2']
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.utxosBulk(rpcData)
// console.log('response: ', response)
assert.equal(response.success, false)
assert.equal(response.status, 422)
assert.equal(response.message, 'Invalid address')
assert.equal(response.endpoint, 'utxosBulk')
})
it('should throw error if input is not an array', async () => {
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'utxosBulk',
addresses: 'singleAddress'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.utxosBulk(rpcData)
// console.log('response: ', response)
assert.equal(response.success, false)
assert.equal(response.status, 422)
assert.equal(response.message, 'addresses parameter must be an array')
assert.equal(response.endpoint, 'utxosBulk')
})
it('should throw error if input array is larger than 20 elements', async () => {
// Generate an array larger than 20 elements
const inputArray = []
for (let i = 0; i < 25; i++) {
inputArray.push(i)
}
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'utxosBulk',
addresses: inputArray
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.utxosBulk(rpcData)
// console.log('response: ', response)
assert.equal(response.success, false)
assert.equal(response.status, 422)
assert.equal(response.message, 'addresses parameter must not exceed 20 elements')
assert.equal(response.endpoint, 'utxosBulk')
})
})
describe('#broadcast', () => {
it('should return data from bchjs', async () => {
// Mock dependencies
@@ -385,6 +553,32 @@ describe('#BCHRPC', () => {
assert.equal(response.message, 'Invalid data')
assert.equal(response.endpoint, 'broadcast')
})
it('should return an error if full node returns a string error', async () => {
// Force an error
sandbox
.stub(uut.bchjs.RawTransactions, 'sendRawTransaction')
// .rejects(new Error('Invalid data'))
.rejects({ error: 'Invalid data' })
// Generate the parsed data that the main router would pass to this
// endpoint.
const id = uid()
const rpcCall = jsonrpc.request(id, 'bch', {
endpoint: 'broadcast',
hex: 'testHex'
})
const jsonStr = JSON.stringify(rpcCall, null, 2)
const rpcData = jsonrpc.parse(jsonStr)
const response = await uut.broadcast(rpcData)
// console.log('response: ', response)
assert.equal(response.success, false)
assert.equal(response.status, 422)
assert.equal(response.message, 'Invalid data')
assert.equal(response.endpoint, 'broadcast')
})
})
describe('#txData', () => {
@@ -568,7 +762,8 @@ describe('#BCHRPC', () => {
// Mock dependencies
sandbox
.stub(uut.bchjs.Utxo, 'isValid')
.rejects(new Error('test error'))
// .rejects(new Error('test error'))
.rejects({ error: 'test error' })
const rpcData = {
payload: {
@@ -619,7 +814,8 @@ describe('#BCHRPC', () => {
// Mock dependencies
sandbox
.stub(uut.bchjs.PsfSlpIndexer, 'getTokenData')
.rejects(new Error('test error'))
// .rejects(new Error('test error'))
.rejects({ error: 'test error' })
const rpcData = {
payload: {
@@ -667,7 +863,8 @@ describe('#BCHRPC', () => {
// Mock dependencies
sandbox
.stub(uut.bchjs.PsfSlpIndexer, 'getTokenData2')
.rejects(new Error('test error'))
// .rejects(new Error('test error'))
.rejects({ error: 'test error' })
const rpcData = {
payload: {
+22
View File
@@ -55,6 +55,28 @@ describe('#server', () => {
// Not throwing an error is a success
})
it('should warn if user admin can not be created', async () => {
// Mock dependencies and force desired code path
sandbox.stub(uut.mongoose, 'connect').resolves()
sandbox.stub(uut.controllers, 'initAdapters').resolves()
sandbox.stub(uut.controllers, 'initUseCases').resolves()
sandbox.stub(uut.controllers, 'attachRESTControllers').resolves()
sandbox.stub(uut.adminLib, 'createSystemUser').rejects('test error')
sandbox.stub(uut.controllers, 'attachControllers').resolves()
uut.config.env = 'dev'
const result = await uut.startServer()
// console.log('result: ', result)
assert.property(result, 'env')
// Turn off the server.
uut.server.close()
// Restor config env
uut.config.env = 'test'
})
})
describe('#sleep', () => {