diff --git a/.env.example b/.env.example deleted file mode 100644 index 56b5d092dc..0000000000 --- a/.env.example +++ /dev/null @@ -1,32 +0,0 @@ - -# NETWORK SETTINGS -# Unless testing only localy and unless behind NAT, you have to set your public IP address -NODE_IP=127.0.0.1 -NODE_PORT=5278 -NODE_RPC_PORT=8900 -NODE_REMOTE_CONTROL_PORT=3000 - -# GRAPH STORAGE SETTINGS -# Set arangodb or neo4j -GRAPH_DATABASE=arangodb - -# GRAPH DATABASE / ArangoDB -DB_USERNAME=root -DB_PASSWORD=root -DB_PORT=8529 -DB_DATABASE=origintrail -DB_HOST=localhost - -# TRUFFLE SETTINGS -TRUFFLE_MNEMONIC= -RINKEBY_ACCESS_KEY= - -# recommended for testing - identity difficulty reduced to 2 -TEST_NETWORK_ENABLED=1 - -# Is this a bootstrap node -IS_BOOTSTRAP_NODE=0 - -# Sent logs to OriginTrail - TestNet -SEND_LOGS=1 -LOGS_LEVEL_DEBUG=1 diff --git a/.travis.yml b/.travis.yml index ea514cfb5a..55b1b14eb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ before_script: - cp .origintrail_noderc.travis .origintrail_noderc - npm run bootstrap - npm install -g ganache-cli@6.1.5 &> /dev/null - - npm install -g truffle@beta &> /dev/null + - npm install -g truffle@5.0.0-beta.1 &> /dev/null script: - npm run lint - 'if [ "$TRAVIS_EVENT_TYPE" != "push" ]; then npm run test:bdd; fi' diff --git a/README.md b/README.md index 23273e8d39..e14cbb3c3b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ __OriginTrail is building first Purpose-built Protocol for Supply Chains Based o This repository contains a work-in-progress code for a network node. Please have in mind that code is still __testnet phase__ developed where all main features will work, with some limitations. -__Test Network__ is available from end of June 2018 as a beta of our protocol, while __Main Network__ is scheduled for Q3 2018. For further information about our roadmap please see our [website](https://origintrail.io/roadmap). +__Test Network__ is available from end of June 2018 as a beta of our protocol, while __Main Network__ is scheduled for Q1 2019. For further information about our roadmap please see our [website](https://origintrail.io/roadmap). [Please see our main documentation page for more information](http://docs.origintrail.io) diff --git a/config/config.json b/config/config.json index 68472ae3ca..3036f8430e 100644 --- a/config/config.json +++ b/config/config.json @@ -255,7 +255,7 @@ "network_id": "rinkeby", "gas_limit": "1500000", "gas_price": "20000000000", - "hub_contract_address": "0xc362EEf31796378e730E5C69BD9B32Bb4B77ec31", + "hub_contract_address": "0x6C314872A7e97A6F1dC7De2dc43D28500dd36f22", "rpc_node_host": "https://rinkeby.infura.io/1WRiEqAQ9l4SW6fGdiDt", "rpc_node_port": "", "plugins": [ @@ -276,7 +276,7 @@ "network": { "hostname": "127.0.0.1", "id": "StablenetV1.0.0", - "bootstraps": ["https://82.196.10.12:5278/#ca87147a501adf39eaa648c2b09735559ee3511d"], + "bootstraps": ["https://82.196.10.12:5278/#e2f7ab11d0dd34595dfb2e71b4937ec8d790df84"], "remoteWhitelist": ["54.93.223.161", "127.0.0.1"], "solutionDifficulty": 14, "identityDifficulty": 12 diff --git a/install-aws.sh b/install-aws.sh index 2f04824c0c..6337617a0e 100644 --- a/install-aws.sh +++ b/install-aws.sh @@ -26,6 +26,5 @@ sudo yum install patch cd ~ git clone -b master https://github.com/OriginTrail/ot-node.git cd ot-node && npm install -cp .env.example .env -echo "Installation complete. Please configure .env file." \ No newline at end of file +echo "Installation complete. Please configure .origintrail_noderc file." \ No newline at end of file diff --git a/install.sh b/install.sh index c338066ffe..dd288e55bd 100644 --- a/install.sh +++ b/install.sh @@ -66,9 +66,6 @@ sudo pip3 install python-dotenv sudo apt-get install git git clone -b master https://github.com/OriginTrail/ot-node.git -cd ot-node -mkdir keys data &> /dev/null -cp .env.example .env +cd ot-node && npm install -npm install -echo "Installation complete. Please configure .env file." \ No newline at end of file +echo "Installation complete. Please configure .origintrail_noderc file." \ No newline at end of file diff --git a/isStartHealthy.js b/isStartHealthy.js deleted file mode 100644 index 1102580f9f..0000000000 --- a/isStartHealthy.js +++ /dev/null @@ -1,16 +0,0 @@ -const fs = require('fs'); - -// index 0 and 1 are used for node and .js file name -const fileToRead = process.argv[2]; - -fs.readFile(`${fileToRead}.log`, (err, data) => { - if (err) throw err; - if (data.indexOf('OT Node listening at https://127.0.0.1:5278') >= 0) { - console.log('npm start is healthy!'); - process.exit(0); - } else { - console.log('npm start is not healthy!'); - console.log('RPC server did not start listening as expected, check npm start localy'); - process.exit(-1); - } -}); diff --git a/modules/Blockchain.js b/modules/Blockchain.js index 41f91cd52f..9525e1c890 100644 --- a/modules/Blockchain.js +++ b/modules/Blockchain.js @@ -421,8 +421,8 @@ class Blockchain { return this.blockchain.nodeHasApproval(nodeId); } - async getBalances() { - return this.blockchain.getBalances(); + async hasEnoughFunds() { + return this.blockchain.hasEnoughFunds(); } /** diff --git a/modules/Blockchain/Ethereum/abi/erc725.json b/modules/Blockchain/Ethereum/abi/erc725.json new file mode 100644 index 0000000000..64843ffaa1 --- /dev/null +++ b/modules/Blockchain/Ethereum/abi/erc725.json @@ -0,0 +1,301 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "key", + "type": "bytes32" + }, + { + "indexed": false, + "name": "purposes", + "type": "uint256[]" + }, + { + "indexed": true, + "name": "keyType", + "type": "uint256" + } + ], + "name": "KeyAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "key", + "type": "bytes32" + }, + { + "indexed": false, + "name": "purposes", + "type": "uint256[]" + }, + { + "indexed": true, + "name": "keyType", + "type": "uint256" + } + ], + "name": "KeyRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "executionId", + "type": "uint256" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": true, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "ExecutionRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "executionId", + "type": "uint256" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": true, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "Executed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "executionId", + "type": "uint256" + }, + { + "indexed": false, + "name": "approved", + "type": "bool" + } + ], + "name": "Approved", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "name": "_key", + "type": "bytes32" + }, + { + "name": "_purposes", + "type": "uint256[]" + }, + { + "name": "_type", + "type": "uint256" + } + ], + "name": "addKey", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_id", + "type": "uint256" + }, + { + "name": "_approve", + "type": "bool" + } + ], + "name": "approve", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_data", + "type": "bytes" + } + ], + "name": "execute", + "outputs": [ + { + "name": "executionId", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_key", + "type": "bytes32" + } + ], + "name": "removeKey", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_key", + "type": "bytes32" + } + ], + "name": "getKey", + "outputs": [ + { + "name": "purposes", + "type": "uint256[]" + }, + { + "name": "keyType", + "type": "uint256" + }, + { + "name": "key", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_key", + "type": "bytes32" + } + ], + "name": "getKeyPurposes", + "outputs": [ + { + "name": "purposes", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_purpose", + "type": "uint256" + } + ], + "name": "getKeysByPurpose", + "outputs": [ + { + "name": "keys", + "type": "bytes32[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_key", + "type": "bytes32" + }, + { + "name": "_purpose", + "type": "uint256" + } + ], + "name": "keyHasPurpose", + "outputs": [ + { + "name": "result", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ] \ No newline at end of file diff --git a/modules/Blockchain/Ethereum/index.js b/modules/Blockchain/Ethereum/index.js index 219ad8068c..79300326ae 100644 --- a/modules/Blockchain/Ethereum/index.js +++ b/modules/Blockchain/Ethereum/index.js @@ -988,7 +988,7 @@ class Ethereum { * Check balances * @returns {Promise} */ - async getBalances() { + async hasEnoughFunds() { this.log.trace('Checking balances'); let enoughETH = true; let enoughTRAC = true; diff --git a/modules/EventEmitter.js b/modules/EventEmitter.js index 4dc66389d9..216035183a 100644 --- a/modules/EventEmitter.js +++ b/modules/EventEmitter.js @@ -449,7 +449,10 @@ class EventEmitter { message: error.message, }); remoteControl.importFailed(error); - notifyError(error); + + if (error.type !== 'ImporterError') { + notifyError(error); + } return; } diff --git a/modules/GS1Importer.js b/modules/GS1Importer.js index 3fb1a8d5cc..bfdc700dd2 100644 --- a/modules/GS1Importer.js +++ b/modules/GS1Importer.js @@ -5,6 +5,7 @@ const Utilities = require('./Utilities'); const models = require('../models'); const ImportUtilities = require('./ImportUtilities'); const { denormalizeGraph, normalizeGraph } = require('./Database/graph-converter'); +const { ImporterError } = require('./errors'); class GS1Importer { /** @@ -676,6 +677,22 @@ class GS1Importer { }); try { + const sortedEvents = eventVertices.sort((a, b) => { + if (a._key < b._key) { + return -1; + } + if (a._key > b._key) { + return 1; + } + return 0; + }); + + for (let i = 1; i < sortedEvents.length; i += 1) { + if (sortedEvents[i]._key === sortedEvents[i - 1]._key) { + throw new ImporterError('Double event identifiers'); + } + } + for (const vertex of allVertices) { if (vertex.identifiers !== null) { for (const identifier in vertex.identifiers) { diff --git a/modules/errors/importer-error.js b/modules/errors/importer-error.js new file mode 100644 index 0000000000..aeab0018f1 --- /dev/null +++ b/modules/errors/importer-error.js @@ -0,0 +1,16 @@ +/** + * Represents error that occurred during dataset import. + */ +class ImporterError extends Error { + constructor(message) { + super(message); + + // Ensure the name of this error is the same as the class name + this.name = this.constructor.name; + + // This clips the constructor invocation from the stack trace. + Error.captureStackTrace(this, this.constructor); + } +} + +module.exports = ImporterError; diff --git a/modules/errors/index.js b/modules/errors/index.js index ddaf36c835..9192d850b3 100644 --- a/modules/errors/index.js +++ b/modules/errors/index.js @@ -2,3 +2,5 @@ module.exports.NetworkRequestIgnoredError = require('./network-request-ignored-error'); module.exports.TransactionFailedError = require('./transaction-failed-error'); + +module.exports.ImporterError = require('./importer-error'); diff --git a/modules/importer.js b/modules/importer.js index f271136baf..91c3015053 100644 --- a/modules/importer.js +++ b/modules/importer.js @@ -252,8 +252,7 @@ class Importer { } catch (error) { this.log.error(`Import error: ${error}.\n${error.stack}`); this.remoteControl.importError(`Import error: ${error}.`); - this.notifyError(error); - const errorObject = { message: error.toString(), status: 400 }; + const errorObject = { type: error.name, message: error.toString(), status: 400 }; return { response: null, error: errorObject, diff --git a/modules/service/profile-service.js b/modules/service/profile-service.js index 520a41f7c5..a021224fd1 100644 --- a/modules/service/profile-service.js +++ b/modules/service/profile-service.js @@ -68,8 +68,22 @@ class ProfileService { * @returns {Promise} */ async isProfileCreated() { + if (!this.config.erc725Identity) { + throw Error('ProfileService not initialized.'); + } + const profile = await this.blockchain.getProfile(this.config.erc725Identity); - return !new BN(profile.stake, 10).eq(new BN(0, 10)); + + const zero = new BN(0); + const stake = new BN(profile.stake, 10); + const stakeReserved = new BN(profile.stakeReserved, 10); + const reputation = new BN(profile.reputation, 10); + const withdrawalTimestamp = new BN(profile.withdrawalTimestamp, 10); + const withdrawalAmount = new BN(profile.withdrawalAmount, 10); + const nodeId = new BN(profile.nodeId, 10); + return !(stake.eq(zero) && stakeReserved.eq(zero) && + reputation.eq(zero) && withdrawalTimestamp.eq(zero) && + withdrawalAmount.eq(zero) && nodeId.eq(zero)); } /** diff --git a/ot-node.js b/ot-node.js index 6581e9dceb..a5d08bfd36 100644 --- a/ot-node.js +++ b/ot-node.js @@ -383,26 +383,33 @@ class OTNode { emitter.initialize(); // check does node_wallet has sufficient funds - if (process.env.NODE_ENV !== 'development') { - try { - appState.enoughFunds = await blockchain.getBalances(); - if (appState.enoughFunds === false && !await profileService.isProfileCreated()) { + try { + appState.enoughFunds = await blockchain.hasEnoughFunds(); + const identityFilePath = path.join( + config.appDataPath, + config.erc725_identity_filepath, + ); + // If ERC725 exist assume profile is created. No need to check for the funds + if (!fs.existsSync(identityFilePath)) { + // Profile does not exists. Check if we have enough funds. + appState.enoughFunds = await blockchain.hasEnoughFunds(); + if (!appState.enoughFunds) { log.warn('Insufficient funds to create profile'); process.exit(1); } + } + } catch (err) { + notifyBugsnag(err); + log.error(`Failed to check for funds. ${err.message}.`); + process.exit(1); + } + setInterval(async () => { + try { + appState.enoughFunds = await blockchain.hasEnoughFunds(); } catch (err) { notifyBugsnag(err); } - setInterval(async () => { - try { - appState.enoughFunds = await blockchain.getBalances(); - } catch (err) { - notifyBugsnag(err); - } - }, 1800000); - } else { - appState.enoughFunds = true; - } + }, 1800000); // Connecting to graph database const graphStorage = container.resolve('graphStorage'); @@ -447,6 +454,14 @@ class OTNode { } await transport.start(); + // Check if ERC725 has valid node ID. + const profile = await blockchain.getProfile(config.erc725Identity); + + if (!profile.nodeId.toLowerCase().startsWith(`0x${config.identity.toLowerCase()}`)) { + throw Error('ERC725 profile not created for this node ID. ' + + `My identity ${config.identity}, profile's node id: ${profile.nodeId}.`); + } + // Initialise API const apiUtilities = container.resolve('apiUtilities'); await this.startRPC(apiUtilities); diff --git a/package-lock.json b/package-lock.json index 4def57b8c4..6b84a64a1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.24", + "version": "2.0.25", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a9e01fcfd8..5bbfa4989f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.24", + "version": "2.0.25", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { @@ -16,7 +16,7 @@ "test:api:nocov": "mocha --exit $(find test/api -name '*.js')", "test:protocol": "nyc mocha --exit $(find test/protocol -name '*.js')", "test:protocol:nocov": "mocha --exit $(find test/protocol -name '*.js')", - "test:bdd": "cucumber-js --tags @itworks --fail-fast --format progress-bar --format-options '{\"colorsEnabled\": true}' test/bdd/ -r test/bdd/steps/", + "test:bdd": "cucumber-js --fail-fast --format progress-bar --format-options '{\"colorsEnabled\": true}' test/bdd/ -r test/bdd/steps/", "test:bdd:cov": " nyc cucumber-js --fail-fast --format progress-bar --format-options '{\"colorsEnabled\": true}' test/bdd/ -r test/bdd/steps/", "test:bdd:verbose": "cucumber-js --fail-fast --format event-protocol --format-options '{\"colorsEnabled\": true}' test/bdd/ -r test/bdd/steps/", "start": "node node_version_check.js && node ot-node.js", diff --git a/test/bdd/features/erc725profile.feature b/test/bdd/features/erc725profile.feature new file mode 100644 index 0000000000..ff81f781ff --- /dev/null +++ b/test/bdd/features/erc725profile.feature @@ -0,0 +1,22 @@ +Feature: ERC725 Profile features + Background: Setup local blockchain and bootstraps + Given the blockchain is set up + And 1 bootstrap is running + + Scenario: Expect node to create profile + Given I setup 1 node + And I start the node + Then the 1st node should have a valid ERC725 identity + And the 1st node should have a valid profile + + Scenario: Expect node to create profile and stake only once + Given I setup 1 node + And I start the node + Then the 1st node should have a valid ERC725 identity + And the 1st node should have a valid profile + Given I stop the nodes + # Try to start the node without the money. + And the 1st node's spend all the Tokens + And the 1st node's spend all the Ethers + And I start the node + Then the 1st node should start normally diff --git a/test/bdd/features/importer.feature b/test/bdd/features/importer.feature index b555b51ce2..82f84ab090 100644 --- a/test/bdd/features/importer.feature +++ b/test/bdd/features/importer.feature @@ -3,7 +3,6 @@ Feature: Test basic importer features Given the blockchain is set up And 1 bootstrap is running - @itworks Scenario: Check that second WOT import does not mess up first import's hash value (same data set) Given I setup 1 node And I start the node @@ -16,7 +15,6 @@ Feature: Test basic importer features Then the last import's hash should be the same as one manually calculated Then checking again first import's root hash should point to remembered value - @itworks Scenario: Check that WOT import is connecting to the same batch from GS1 import Given I setup 1 node And I start the node diff --git a/test/bdd/features/network.feature b/test/bdd/features/network.feature index e8babe6596..6f15f64928 100644 --- a/test/bdd/features/network.feature +++ b/test/bdd/features/network.feature @@ -3,13 +3,11 @@ Feature: Test basic network features Given the blockchain is set up And 1 bootstrap is running - @itworks Scenario: Start network with 5 nodes and check do they see each other Given I setup 5 nodes And I start the nodes Then all nodes should be aware of each other - @itworks Scenario: Test replication DC -> DH Given the replication difficulty is 0 And I setup 5 nodes @@ -21,7 +19,6 @@ Feature: Test basic network features And I wait for replications to finish Then the last import should be the same on all nodes that replicated data - @itworks Scenario: Check that second gs1 import does not mess up first import's hash value Given I setup 4 nodes And I start the nodes @@ -35,7 +32,6 @@ Feature: Test basic network features And I wait for 10 seconds Then checking again first import's root hash should point to remembered value - @itworks Scenario: Smoke check data-layer basic endpoints Given I setup 2 nodes And I start the nodes @@ -46,7 +42,6 @@ Feature: Test basic network features Given I query DC node locally for last imported data set id Then response hash should match last imported data set id - @itworks Scenario: DC->DH->DV replication + DV network read + DV purchase Given the replication difficulty is 0 And I setup 5 nodes @@ -65,7 +60,6 @@ Feature: Test basic network features Given the DV purchases import from the last query from a DH Then the last import should be the same on DC and DV nodes - @itworks Scenario: Smoke check /api/withdraw endpoint Given I setup 1 node And I start the node @@ -73,7 +67,6 @@ Feature: Test basic network features Given I attempt to withdraw 5 tokens from DC profile Then DC wallet and DC profile balances should diff by 5 with rounding error of 0.1 - @itworks Scenario: Smoke check /api/deposit endpoint Given I setup 1 node And I start the node @@ -81,7 +74,6 @@ Feature: Test basic network features Given I attempt to deposit 50 tokens from DC wallet Then DC wallet and DC profile balances should diff by 50 with rounding error of 0.1 - @itworks Scenario: Smoke check /api/consensus endpoint Given I setup 1 node And I start the node @@ -93,7 +85,6 @@ Feature: Test basic network features Given DC calls consensus endpoint for sender: "urn:ot:object:actor:id:Company_Pink" Then last consensus response should have 1 event with 1 match - @itworks Scenario: DV purchases data directly from DC, no DHes Given the replication difficulty is 0 And I setup 1 node @@ -111,7 +102,6 @@ Feature: Test basic network features Given the DV purchases import from the last query from the DC Then the last import should be the same on DC and DV nodes - @itworks Scenario: 2nd DV purchases data from 1st DV, no DHes Given the replication difficulty is 0 And I setup 1 node @@ -137,7 +127,6 @@ Feature: Test basic network features Then the last import should be the same on DC and DV nodes Then the last import should be the same on DC and DV2 nodes - @itworks Scenario: API calls should be forbidden Given I setup 1 node And I override configuration for all nodes @@ -146,7 +135,6 @@ Feature: Test basic network features And I use 1st node as DC Then API calls will be forbidden - @itworks Scenario: API calls should not be authorized Given I setup 1 node And I override configuration for all nodes diff --git a/test/bdd/steps/blockchain.js b/test/bdd/steps/blockchain.js index 1998259a32..26428c99bd 100644 --- a/test/bdd/steps/blockchain.js +++ b/test/bdd/steps/blockchain.js @@ -4,6 +4,7 @@ const { And, But, Given, Then, When, } = require('cucumber'); const { expect } = require('chai'); +const BN = require('bn.js'); const LocalBlockchain = require('./lib/local-blockchain'); @@ -39,3 +40,38 @@ Given(/^the replication difficulty is (\d+)$/, async function (difficulty) { expect(currentDifficulty).to.be.equal(difficulty.toString()); } }); + +Given(/^the (\d+)[st|nd|rd|th]+ node's spend all the (Ethers|Tokens)$/, async function (nodeIndex, currencyType) { + expect(this.state.nodes.length, 'No started nodes.').to.be.greaterThan(0); + expect(this.state.bootstraps.length, 'No bootstrap nodes.').to.be.greaterThan(0); + expect(nodeIndex, 'Invalid index.').to.be.within(0, this.state.nodes.length); + expect(currencyType).to.be.oneOf(['Ethers', 'Tokens']); + + const node = this.state.nodes[nodeIndex - 1]; + const { web3 } = this.state.localBlockchain; + + const targetWallet = this.state.localBlockchain.web3.eth.accounts.create(); + const nodeWallet = node.options.nodeConfiguration.node_wallet; + + if (currencyType === 'Ethers') { + const balance = await this.state.localBlockchain.getBalanceInEthers(nodeWallet); + const balanceBN = new BN(balance, 10); + const toSend = balanceBN.sub(new BN(await web3.eth.getGasPrice(), 10).mul(new BN(21000))); + await web3.eth.sendTransaction({ + to: targetWallet.address, + from: nodeWallet, + value: toSend, + gas: 21000, + gasPrice: await web3.eth.getGasPrice(), + }); + expect(await this.state.localBlockchain.getBalanceInEthers(nodeWallet)).to.equal('0'); + } else if (currencyType === 'Tokens') { + const balance = + await this.state.localBlockchain.tokenInstance.methods.balanceOf(nodeWallet).call(); + + await this.state.localBlockchain.tokenInstance.methods + .transfer(targetWallet.address, balance) + .send({ from: nodeWallet, gas: 3000000 }); + expect(await this.state.localBlockchain.tokenInstance.methods.balanceOf(nodeWallet).call()).to.equal('0'); + } +}); diff --git a/test/bdd/steps/common.js b/test/bdd/steps/common.js new file mode 100644 index 0000000000..112ce2a137 --- /dev/null +++ b/test/bdd/steps/common.js @@ -0,0 +1,16 @@ +/* eslint-disable no-unused-expressions */ + +const { + And, But, Given, Then, When, +} = require('cucumber'); +const { expect } = require('chai'); + + +Then(/^the (\d+)[st|nd|rd|th]+ node should start normally$/, function (nodeIndex) { + expect(this.state.nodes.length, 'No started nodes.').to.be.greaterThan(0); + expect(this.state.bootstraps.length, 'No bootstrap nodes.').to.be.greaterThan(0); + expect(nodeIndex, 'Invalid index.').to.be.within(0, this.state.nodes.length); + + const node = this.state.nodes[nodeIndex - 1]; + expect(node.isRunning).to.be.true; +}); diff --git a/test/bdd/steps/erc725identity.js b/test/bdd/steps/erc725identity.js new file mode 100644 index 0000000000..67c766c496 --- /dev/null +++ b/test/bdd/steps/erc725identity.js @@ -0,0 +1,62 @@ +/* eslint-disable no-unused-expressions */ + +const { + And, But, Given, Then, When, +} = require('cucumber'); +const { expect } = require('chai'); +const path = require('path'); +const fs = require('fs'); +const { keccak_256 } = require('js-sha3'); +const BN = require('bn.js'); + +const utilities = require('./lib/utilities'); +const erc725ProfileAbi = require('../../../modules/Blockchain/Ethereum/abi/erc725'); + +Then(/^the (\d+)[st|nd|rd|th]+ node should have a valid ERC725 identity/, async function (nodeIndex) { + expect(this.state.nodes.length, 'No started nodes.').to.be.greaterThan(0); + expect(this.state.bootstraps.length, 'No bootstrap nodes.').to.be.greaterThan(0); + expect(nodeIndex, 'Invalid index.').to.be.within(0, this.state.nodes.length); + + const node = this.state.nodes[nodeIndex - 1]; + + // Profile file should exist in app-data-path. + const erc725ProfileJsonPath = path.join(node.options.configDir, 'erc725_identity.json'); + const erc725Profile = JSON.parse(fs.readFileSync(erc725ProfileJsonPath, 'utf8')); + expect(erc725Profile).to.have.key('identity'); + + const erc725ProfileAddress = erc725Profile.identity; + const { web3 } = this.state.localBlockchain; + const erc725Contract = new web3.eth.Contract(erc725ProfileAbi); + erc725Contract.options.address = erc725ProfileAddress; + + const nodeWallet = node.options.nodeConfiguration.node_wallet; + const hashedAddress = keccak_256(Buffer.from(utilities.denormalizeHex(nodeWallet), 'hex')); + + + const result = + await erc725Contract.methods.getKey(utilities.normalizeHex(hashedAddress)).call(); + + expect(result).to.have.keys(['0', '1', '2', 'purposes', 'keyType', 'key']); + expect(result.purposes).to.have.ordered.members(['1', '2', '3', '4']); +}); + +Then(/^the (\d+)[st|nd|rd|th]+ node should have a valid profile$/, async function (nodeIndex) { + expect(this.state.nodes.length, 'No started nodes.').to.be.greaterThan(0); + expect(this.state.bootstraps.length, 'No bootstrap nodes.').to.be.greaterThan(0); + expect(nodeIndex, 'Invalid index.').to.be.within(0, this.state.nodes.length); + + const node = this.state.nodes[nodeIndex - 1]; + const nodeId = node.state.identity; + // Profile file should exist in app-data-path. + const erc725ProfileJsonPath = path.join(node.options.configDir, 'erc725_identity.json'); + const erc725Profile = JSON.parse(fs.readFileSync(erc725ProfileJsonPath, 'utf8')); + expect(erc725Profile).to.have.key('identity'); + + const erc725ProfileAddress = erc725Profile.identity; + const result = + await this.state.localBlockchain.profileStorageInstance + .methods.profile(erc725ProfileAddress).call(); + + expect(result.nodeId, `Got ${JSON.stringify(result)}`).to.equal(`0x${nodeId}000000000000000000000000`); + expect(new BN(result.stake).gt(new BN(0)), `Got ${JSON.stringify(result)}`).to.be.true; +}); diff --git a/test/bdd/steps/lib/local-blockchain.js b/test/bdd/steps/lib/local-blockchain.js index c7b70708e2..b54a681ba2 100644 --- a/test/bdd/steps/lib/local-blockchain.js +++ b/test/bdd/steps/lib/local-blockchain.js @@ -7,6 +7,8 @@ const path = require('path'); const EthWallet = require('ethereumjs-wallet'); const assert = require('assert'); +const utilities = require('./utilities'); + const accountPrivateKeys = [ '3cf97be6177acdd12796b387f58f84f177d0fe20d8558004e8db9a41cf90392a', '1e60c8e9aa35064cd2eaa4c005bda2b76ef1a858feebb6c8e131c472d16f9740', @@ -351,6 +353,10 @@ class LocalBlockchain { static wallets() { return wallets; } + + async getBalanceInEthers(wallet) { + return this.web3.eth.getBalance(wallet); + } } module.exports = LocalBlockchain; diff --git a/test/bdd/steps/lib/otnode.js b/test/bdd/steps/lib/otnode.js index 920e100b32..001a9c69a4 100644 --- a/test/bdd/steps/lib/otnode.js +++ b/test/bdd/steps/lib/otnode.js @@ -88,9 +88,6 @@ class OtNode extends EventEmitter { // DV side. datasetId = { queryId, replyId } this.state.purchasedDatasets = {}; - // Temp solution until node.log is moved to the configDir. - this.logStream = fs.createWriteStream(path.join(this.options.configDir, 'node-cucumber.log')); - this.logger.log(`Node initialized at: '${this.options.configDir}'.`); this.initialized = true; } @@ -116,6 +113,13 @@ class OtNode extends EventEmitter { assert(this.initialized); assert(!this.stared); this.logger.log(`Starting node ${this.id}.`); + + // Temp solution until node.log is moved to the configDir. + this.logStream = fs.createWriteStream( + path.join(this.options.configDir, 'node-cucumber.log'), + { flags: 'a+' }, + ); + // Starting node should be done with following code: // this.process = spawn('npm', ['start', '--', `--configDir=${this.options.configDir}`]); // The problem is with it spawns two child process thus creating the problem when diff --git a/test/bdd/steps/lib/utilities.js b/test/bdd/steps/lib/utilities.js index 3c4e44d762..cde7a85ae1 100644 --- a/test/bdd/steps/lib/utilities.js +++ b/test/bdd/steps/lib/utilities.js @@ -5,6 +5,34 @@ function calculateImportHash(data) { return `0x${sha3_256(sortedStringify(data, null, 0))}`; } +/** + * Normalizes hex number + * @param number Hex number + * @returns {string} Normalized hex number + */ +function normalizeHex(number) { + number = number.toLowerCase(); + if (!number.startsWith('0x')) { + return `0x${number}`; + } + return number; +} + +/** + * Denormalizes hex number + * @param number Hex number + * @returns {string|null} Normalized hex number + */ +function denormalizeHex(number) { + number = number.toLowerCase(); + if (number.startsWith('0x')) { + return number.substring(2); + } + return number; +} + module.exports = { calculateImportHash, + normalizeHex, + denormalizeHex, }; diff --git a/test/bdd/steps/network.js b/test/bdd/steps/network.js index f7c2e9ffc0..8c4768227d 100644 --- a/test/bdd/steps/network.js +++ b/test/bdd/steps/network.js @@ -155,6 +155,23 @@ Given(/^I start the node[s]*$/, { timeout: 3000000 }, function (done) { Promise.all(nodesStarts).then(() => done()); }); +Given(/^I stop the nodes[s]*$/, { timeout: 3000000 }, function () { + expect(this.state.bootstraps.length).to.be.greaterThan(0); + expect(this.state.nodes.length).to.be.greaterThan(0); + + const nodesStops = []; + + this.state.nodes.forEach((node) => { + nodesStops.push(new Promise((accept, reject) => { + node.once('finished', () => accept()); + node.once('error', reject); + })); + node.stop(); + }); + + return Promise.all(nodesStops); +}); + Then(/^all nodes should be aware of each other$/, function (done) { expect(this.state.nodes.length, 'No started nodes').to.be.greaterThan(0); expect(this.state.bootstraps.length, 'No bootstrap nodes').to.be.greaterThan(0); diff --git a/test/modules/gs1-importer.test.js b/test/modules/gs1-importer.test.js index 177034fbc2..9492082dad 100644 --- a/test/modules/gs1-importer.test.js +++ b/test/modules/gs1-importer.test.js @@ -538,6 +538,18 @@ describe('GS1 Importer tests', () => { it('and throw an error releted to missing SenderContactInformation', async () => expect(gs1.parseGS1(await Utilities.fileContents(xmlWithoutSenderContactinfo))).to.be.rejectedWith(Error, "Cannot read property 'EmailAddress' of undefined")); }); + describe('Double event identifiers should fail', () => { + const xmlDoubleIds = path.join(__dirname, 'test_xml/DoubleEventId.xml'); + + it('Should fail to import double event identifiers', async () => expect(gs1.parseGS1(await Utilities.fileContents(xmlDoubleIds))).to.rejectedWith(Error, 'Double event identifiers')); + }); + + describe('Multiple same identifiers for different vertices should import correctly', () => { + const xmlDoubleIds = path.join(__dirname, 'test_xml/MultipleIdentifiers.xml'); + + it('Should import without error', async () => expect(gs1.parseGS1(await Utilities.fileContents(xmlDoubleIds))).to.be.fulfilled); + }); + afterEach('Drop DB', async () => { if (systemDb) { const listOfDatabases = await systemDb.listDatabases(); diff --git a/test/modules/test_xml/DoubleEventId.xml b/test/modules/test_xml/DoubleEventId.xml new file mode 100644 index 0000000000..93fdcd48b2 --- /dev/null +++ b/test/modules/test_xml/DoubleEventId.xml @@ -0,0 +1,139 @@ + + + + + 1.0 + + urn:ot:object:actor:id:Company_1 + + Abraham Smith + abraham_Smith@comp1.com + + + + urn:ot:object:actor:id:OT + + Betty Johnson + betty@pink.com + + + + GS1 + V1.3 + 100001 + Shipment + 2018-01-01T00:31:52Z + + + + BusinessProcess + Shipment/version2-251 + EDI-Shipment + + + + + + + + + + Company_1 + Company_1 + 0xe1e9c5379c5df627a8de3a951fa493028394a050 + + + + + + + Building + Labeling area + urn:ot:object:actor:id:Company_1 + + + + + + + P1 + CAT1 + + + + + + + urn:ot:object:product:id:P1 + + + + + + + + + + + 2018-01-20T00:00:01 + -00:00 + + urn:epc:id:sgtin:B1 + + OBSERVE + urn:epcglobal:cbv:bizstep:labeling + urn:epcglobal:cbv:disp:active + + urn:epc:id:sgln:Company_1 + + + urn:epc:id:sgln:Company_1 + + + + + urn:epc:id:sgtin:B1 + 1 + PCS + + + + E1 + urn:ot:event:Observation + Labeling + + + + + 2018-01-20T00:00:01 + -00:00 + + urn:epc:id:sgtin:B1 + + OBSERVE + urn:epcglobal:cbv:bizstep:labeling + urn:epcglobal:cbv:disp:active + + urn:epc:id:sgln:Company_1 + + + urn:epc:id:sgln:Company_1 + + + + + urn:epc:id:sgtin:B1 + 1 + PCS + + + + E1 + urn:ot:event:Observation + Labeling + + + + + + \ No newline at end of file diff --git a/test/modules/test_xml/MultipleIdentifiers.xml b/test/modules/test_xml/MultipleIdentifiers.xml new file mode 100644 index 0000000000..684d78f743 --- /dev/null +++ b/test/modules/test_xml/MultipleIdentifiers.xml @@ -0,0 +1,111 @@ + + + + + 1.0 + + urn:ot:object:actor:id:Company_1 + + Abraham Smith + abraham_Smith@comp1.com + + + + urn:ot:object:actor:id:OT + + Betty Johnson + betty@pink.com + + + + GS1 + V1.3 + 100001 + Shipment + 2018-01-01T00:31:52Z + + + + BusinessProcess + Shipment/version2-251 + EDI-Shipment + + + + + + + + + + Company_1 + Company_1 + 0xe1e9c5379c5df627a8de3a951fa493028394a050 + + + + + + + Building + Labeling area + urn:ot:object:actor:id:Company_1 + + + + + + + P1 + CAT1 + 1234567890123 + + + + + + + urn:ot:object:product:id:P1 + 1234567890123 + + + + + + + + + + + 2018-01-20T00:00:01 + -00:00 + + urn:epc:id:sgtin:B1 + + OBSERVE + urn:epcglobal:cbv:bizstep:labeling + urn:epcglobal:cbv:disp:active + + urn:epc:id:sgln:Company_1 + + + urn:epc:id:sgln:Company_1 + + + + + urn:epc:id:sgtin:B1 + 1 + PCS + + + + E1 + urn:ot:event:Observation + Labeling + + + + + + \ No newline at end of file