From 7b18567cedbf1d47460b6d165e433e5dd797a544 Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Fri, 7 Dec 2018 12:07:19 +0100 Subject: [PATCH 01/14] Profile contract cleanup (#785) * Set default minimal stake to 1000 TRAC * Simplify token withdrawal logic * Update truffle test --- .../Blockchain/Ethereum/contracts/Profile.sol | 18 ++---------------- modules/Blockchain/Ethereum/test/offer.test.js | 4 ++-- .../Blockchain/Ethereum/test/profile.test.js | 2 +- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/modules/Blockchain/Ethereum/contracts/Profile.sol b/modules/Blockchain/Ethereum/contracts/Profile.sol index 8a21741487..b0c96904b1 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -11,7 +11,7 @@ contract Profile { Hub public hub; ProfileStorage public profileStorage; - uint256 public minimalStake = 10**20; // TODO Determine minimum stake + uint256 public minimalStake = 10**21; uint256 public withdrawalTime = 5 minutes; constructor(address hubAddress) public { @@ -92,21 +92,7 @@ contract Profile { // Verify sender require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2)); - if(profileStorage.getWithdrawalPending(identity)){ - if(block.timestamp < profileStorage.getWithdrawalTimestamp(identity)){ - // Transfer already reserved tokens to user identity - profileStorage.transferTokens(msg.sender, profileStorage.getWithdrawalAmount(identity)); - - uint256 balance = profileStorage.getStake(identity); - balance = balance.sub(profileStorage.getWithdrawalAmount(identity)); - profileStorage.setStake(identity, balance); - - emit TokensWithdrawn(identity, profileStorage.getWithdrawalAmount(identity), balance); - } - else { - require(false, "Withrdrawal process already pending!"); - } - } + require(profileStorage.getWithdrawalPending(identity) == false, "Withrdrawal process already pending!"); uint256 availableBalance = profileStorage.getStake(identity).sub(profileStorage.getStakeReserved(identity)); diff --git a/modules/Blockchain/Ethereum/test/offer.test.js b/modules/Blockchain/Ethereum/test/offer.test.js index 90812bc047..c7699fa4e9 100644 --- a/modules/Blockchain/Ethereum/test/offer.test.js +++ b/modules/Blockchain/Ethereum/test/offer.test.js @@ -26,7 +26,7 @@ var errored = true; var DC_identity; var DC_wallet; var offerId; -var tokensToDeposit = (new BN(5)).mul(new BN(10).pow(new BN(20))); +var tokensToDeposit = (new BN(5)).mul(new BN(10).pow(new BN(21))); // Offer variables const dataSetId = '0x8cad6896887d99d70db8ce035d331ba2ade1a5e1161f38ff7fda76cf7c308cde'; @@ -89,7 +89,7 @@ contract('Offer testing', async (accounts) => { for (var i = 0; i < accounts.length; i += 1) { promises[i] = trac.increaseApproval( profile.address, - (new BN(5)).mul(new BN(10).pow(new BN(20))), + tokensToDeposit, { from: accounts[i] }, ); } diff --git a/modules/Blockchain/Ethereum/test/profile.test.js b/modules/Blockchain/Ethereum/test/profile.test.js index e02dc1d1f8..94c3b1deb1 100644 --- a/modules/Blockchain/Ethereum/test/profile.test.js +++ b/modules/Blockchain/Ethereum/test/profile.test.js @@ -22,7 +22,7 @@ var web3; var Ganache = require('ganache-core'); // Global values -const amountToDeposit = (new BN(10)).pow(new BN(20)); +const amountToDeposit = (new BN(10)).pow(new BN(21)); const amountToWithdraw = (new BN(100)); const nodeId = '0x4cad6896887d99d70db8ce035d331ba2ade1a5e1161f38ff7fda76cf7c308cde'; From 8974e77aa630d66bb8fd95995e1be688d35e0218 Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Fri, 7 Dec 2018 12:08:08 +0100 Subject: [PATCH 02/14] Require nodeID and enable nodeID change (#777) * Require a node to submit a nodeId when creating a profile * Add a function for changing a profile's nodeId * Update abi file for profile smart contract --- modules/Blockchain/Ethereum/abi/profile.json | 46 +++++++++++++++++++ .../Blockchain/Ethereum/contracts/Profile.sol | 14 +++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/modules/Blockchain/Ethereum/abi/profile.json b/modules/Blockchain/Ethereum/abi/profile.json index f9c37115f9..66478bcbe2 100644 --- a/modules/Blockchain/Ethereum/abi/profile.json +++ b/modules/Blockchain/Ethereum/abi/profile.json @@ -71,6 +71,20 @@ "stateMutability": "view", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "newMinimalStake", + "type": "uint256" + } + ], + "name": "setMinimalStake", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": false, "inputs": [ @@ -103,6 +117,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "identity", + "type": "address" + }, + { + "name": "newNodeId", + "type": "bytes32" + } + ], + "name": "setNodeId", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": true, "inputs": [], @@ -139,6 +171,20 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "newWithdrawalTime", + "type": "uint256" + } + ], + "name": "setWithdrawalTime", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": false, "inputs": [ diff --git a/modules/Blockchain/Ethereum/contracts/Profile.sol b/modules/Blockchain/Ethereum/contracts/Profile.sol index b0c96904b1..7ac6cfdc87 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -6,6 +6,7 @@ import {Ownable, Hub} from './Hub.sol'; import {Identity, ERC725} from './Identity.sol'; import {ERC20} from './TracToken.sol'; + contract Profile { using SafeMath for uint256; Hub public hub; @@ -44,6 +45,7 @@ contract Profile { ERC20 tokenContract = ERC20(hub.tokenAddress()); require(tokenContract.allowance(msg.sender, this) >= initialBalance, "Sender allowance must be equal to or higher than initial balance"); require(tokenContract.balanceOf(msg.sender) >= initialBalance, "Sender balance must be equal to or higher than initial balance!"); + require(uint256(profileNodeId) != 0, "Cannot create a profile without a nodeId submitted"); tokenContract.transferFrom(msg.sender, address(profileStorage), initialBalance); @@ -133,7 +135,17 @@ contract Profile { profileStorage.getStake(identity).sub(profileStorage.getWithdrawalAmount(identity)) ); } - + + function setNodeId(address identity, bytes32 newNodeId) + public { + // Verify sender + require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2), + "Sender does not have action permission for submitted identity"); + require(uint256(newNodeId) != 0, "Cannot set a blank nodeId"); + + profileStorage.setNodeId(identity, newNodeId); + } + function reserveTokens(address payer, address identity1, address identity2, address identity3, uint256 amount) public onlyHolding { if(profileStorage.getWithdrawalPending(payer)) { From 3ddbeb51d5dea748ed8ef6036081f4d2923e87d1 Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Fri, 7 Dec 2018 19:18:56 +0100 Subject: [PATCH 03/14] Handle failed finalize offer (#788) --- modules/command/dc/dc-offer-finalize-command.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/command/dc/dc-offer-finalize-command.js b/modules/command/dc/dc-offer-finalize-command.js index 2142026e43..ae60b6fba8 100644 --- a/modules/command/dc/dc-offer-finalize-command.js +++ b/modules/command/dc/dc-offer-finalize-command.js @@ -116,6 +116,14 @@ class DCOfferFinalizeCommand extends Command { commands: [depositToken], }; } + + this.logger.error(`Offer ${offerId} has not been finalized.`); + + offer.status = 'FAILED'; + offer.message = `Offer for ${offerId} has not been finalized. ${err.message}`; + await offer.save({ fields: ['status', 'message'] }); + + await this.replicationService.cleanup(offer.id); return Command.empty(); } From 9d372dc9bfefd261c11f0e2ea478fb821719d028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandar=20Veljkovi=C4=87?= <35335520+alexveljkovic@users.noreply.github.com> Date: Fri, 7 Dec 2018 19:45:44 +0100 Subject: [PATCH 04/14] Bugfix/hotfixes (#789) --- modules/Database/Arangojs.js | 11 +++++++++++ modules/GS1Importer.js | 8 ++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/modules/Database/Arangojs.js b/modules/Database/Arangojs.js index 1cb770cb19..f71115a761 100644 --- a/modules/Database/Arangojs.js +++ b/modules/Database/Arangojs.js @@ -236,6 +236,17 @@ class ArangoJS { OUTBOUND 'ot_vertices/${startVertex._key}' ot_edges OPTIONS {bfs: false, uniqueVertices: 'path'} + FILTER edge.edge_type != 'IDENTIFIES' + AND edge.edge_type != 'IDENTIFIED_BY' + AND edge._to != 'ot_vertices/Actor' + AND edge._to != 'ot_vertices/Product' + AND edge._to != 'ot_vertices/Location' + AND edge._to != 'ot_vertices/Transport' + AND edge._to != 'ot_vertices/Transformation' + AND edge._to != 'ot_vertices/Observation' + AND edge._to != 'ot_vertices/Ownership' + AND vertex.vertex_type != 'CLASS' + AND vertex.vertex_type != 'IDENTIFIER' RETURN path`; const rawGraph = await this.runQuery(queryString); diff --git a/modules/GS1Importer.js b/modules/GS1Importer.js index ad93ee9224..93b932cf02 100644 --- a/modules/GS1Importer.js +++ b/modules/GS1Importer.js @@ -112,10 +112,10 @@ class GS1Importer { } } - if (eventListElement.extension && eventListElement.extension.TransformationEvent) { - for (const transformationEvent of - this.helper.arrayze(eventListElement.extension.TransformationEvent)) { - events.push(transformationEvent); + if (eventListElement.extension) { + const extensions = this.helper.arrayze(eventListElement.extension); + for (const currentExtension of extensions) { + events.push(currentExtension.TransformationEvent); } } } From f47397aec63479867120f40c5a0bbce003af8eeb Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Fri, 7 Dec 2018 19:54:18 +0100 Subject: [PATCH 05/14] bump version (#790) --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f7f34fbc2c..e40e4595cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.29", + "version": "2.0.30", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 60366c138b..1457e548e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.29", + "version": "2.0.30", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From d6b3e57686968ab3352f35d4c1ef3dfb75827ba5 Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Fri, 7 Dec 2018 22:52:09 +0100 Subject: [PATCH 06/14] update config (#793) --- config/config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.json b/config/config.json index 95a6e76ebe..9663bdc0ec 100644 --- a/config/config.json +++ b/config/config.json @@ -498,8 +498,8 @@ "dc_holding_time_in_minutes": 10080, "dc_token_amount_per_holder": "50000000000000000000", "dc_litigation_interval_in_minutes": 5, - "dh_max_holding_time_in_minutes": 10080, - "dh_min_token_price": "10000000000000000000", + "dh_max_holding_time_in_minutes": 525600, + "dh_min_token_price": "20000000000000000000", "dh_min_litigation_interval_in_minutes": 5, "deposit_on_demand": true, "dc_choose_time": 600000, From cec6ccf97befe53c3f498a034c29287a2393a9ce Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Fri, 7 Dec 2018 22:56:38 +0100 Subject: [PATCH 07/14] bump version (#794) --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e40e4595cd..2d7a86ede3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.30", + "version": "2.0.31", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1457e548e2..ac9dffbe41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.30", + "version": "2.0.31", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From 136f2a7e2a2ccf04d53c0ae7a6c510d7b1e22313 Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Sat, 8 Dec 2018 00:08:52 +0100 Subject: [PATCH 08/14] Update config (#796) * update config * bump version --- config/config.json | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/config.json b/config/config.json index 9663bdc0ec..b7adaefed9 100644 --- a/config/config.json +++ b/config/config.json @@ -498,7 +498,7 @@ "dc_holding_time_in_minutes": 10080, "dc_token_amount_per_holder": "50000000000000000000", "dc_litigation_interval_in_minutes": 5, - "dh_max_holding_time_in_minutes": 525600, + "dh_max_holding_time_in_minutes": 10080, "dh_min_token_price": "20000000000000000000", "dh_min_litigation_interval_in_minutes": 5, "deposit_on_demand": true, diff --git a/package-lock.json b/package-lock.json index 2d7a86ede3..7fa9087fde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.31", + "version": "2.0.32", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ac9dffbe41..cdd1e3c921 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.31", + "version": "2.0.32", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From 9f68ef05f0a9cc1ad375006d5bff1aca35b20988 Mon Sep 17 00:00:00 2001 From: radomir-sebek Date: Mon, 10 Dec 2018 14:41:06 +0100 Subject: [PATCH 09/14] Scenario 12- Remote event connection on DV side (#162083739) (#798) --- test/bdd/features/datalayer.feature | 17 +++++++++++++++-- test/bdd/features/network.feature | 8 ++++---- test/bdd/features/protocol-issues.feature | 2 +- test/bdd/steps/endpoints.js | 19 ++++++++++++++++--- test/bdd/steps/network.js | 16 +++++++++++++--- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/test/bdd/features/datalayer.feature b/test/bdd/features/datalayer.feature index 4ee5935fa2..c0b818e653 100644 --- a/test/bdd/features/datalayer.feature +++ b/test/bdd/features/datalayer.feature @@ -77,12 +77,12 @@ Feature: Data layer related features And I use 2nd node as DV Given DV publishes query consisting of path: "identifiers.id", value: "urn:epc:id:sgtin:Batch_1" and opcode: "EQ" to the network Then all nodes with last import should answer to last network query by DV - Given the DV purchases import from the last query from the DC + Given the DV purchases last import from the last query from the DC Given I query DV node locally for last imported data set id Then DV's local query response should contain hashed private attributes @second - Scenario: Remote event connection on DH + Scenario: Remote event connection on DH and DV Given I setup 5 nodes And I start the nodes And I use 1st node as DC @@ -96,4 +96,17 @@ Feature: Data layer related features Given DH calls consensus endpoint for sender: "urn:ot:object:actor:id:Company_Green" Then last consensus response should have 1 event with 1 match Given DH calls consensus endpoint for sender: "urn:ot:object:actor:id:Company_Pink" + Then last consensus response should have 1 event with 1 match + Given I additionally setup 1 node + And I start additional nodes + And I use 6th node as DV + Given DV publishes query consisting of path: "identifiers.id", value: "urn:epc:id:sgtin:Batch_1" and opcode: "EQ" to the network + Then all nodes with last import should answer to last network query by DV + And the DV purchases last import from the last query from a DH + Given DV publishes query consisting of path: "uid", value: "urn:epc:id:sgln:Building_Green_V1" and opcode: "EQ" to the network + Then all nodes with second last import should answer to last network query by DV + And the DV purchases second last import from the last query from a DH + And DV calls consensus endpoint for sender: "urn:ot:object:actor:id:Company_Pink" + Then last consensus response should have 1 event with 1 match + And DV calls consensus endpoint for sender: "urn:ot:object:actor:id:Company_Green" Then last consensus response should have 1 event with 1 match \ No newline at end of file diff --git a/test/bdd/features/network.feature b/test/bdd/features/network.feature index 3f94a86ee2..a8a3ee1c56 100644 --- a/test/bdd/features/network.feature +++ b/test/bdd/features/network.feature @@ -38,7 +38,7 @@ Feature: Test basic network features And I use 6th node as DV Given DV publishes query consisting of path: "identifiers.id", value: "urn:epc:id:sgtin:Batch_1" and opcode: "EQ" to the network Then all nodes with last import should answer to last network query by DV - Given the DV purchases import from the last query from a DH + Given the DV purchases last import from the last query from a DH Then the last import should be the same on DC and DV nodes Then DV's last purchase's hash should be the same as one manually calculated @@ -57,7 +57,7 @@ Feature: Test basic network features And I use 2nd node as DV Given DV publishes query consisting of path: "identifiers.id", value: "urn:epc:id:sgtin:Batch_1" and opcode: "EQ" to the network Then all nodes with last import should answer to last network query by DV - Given the DV purchases import from the last query from the DC + Given the DV purchases last import from the last query from the DC Then the last import should be the same on DC and DV nodes @first @@ -75,14 +75,14 @@ Feature: Test basic network features And I use 2nd node as DV Given DV publishes query consisting of path: "identifiers.id", value: "urn:epc:id:sgtin:Batch_1" and opcode: "EQ" to the network Then all nodes with last import should answer to last network query by DV - Given the DV purchases import from the last query from the DC + Given the DV purchases last import from the last query from the DC Then the last import should be the same on DC and DV nodes Given I additionally setup 1 node And I start additional nodes And I use 3rd node as DV2 Given DV2 publishes query consisting of path: "identifiers.id", value: "urn:epc:id:sgtin:Batch_1" and opcode: "EQ" to the network Then all nodes with last import should answer to last network query by DV2 - Given the DV2 purchases import from the last query from a DV + Given the DV2 purchases last import from the last query from a DV 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 diff --git a/test/bdd/features/protocol-issues.feature b/test/bdd/features/protocol-issues.feature index 7db5c678ee..66285b4146 100644 --- a/test/bdd/features/protocol-issues.feature +++ b/test/bdd/features/protocol-issues.feature @@ -23,5 +23,5 @@ Feature: Protocol related issues. And I start the 7th node And I use 7th node as DV Given DV publishes query consisting of path: "identifiers.id", value: "urn:epc:id:sgtin:Batch_1" and opcode: "EQ" to the network - And the DV purchases import from the last query from the DC + And the DV purchases last import from the last query from the DC Then the last import should be the same on DC and DV nodes diff --git a/test/bdd/steps/endpoints.js b/test/bdd/steps/endpoints.js index d8205c7243..521f9029fe 100644 --- a/test/bdd/steps/endpoints.js +++ b/test/bdd/steps/endpoints.js @@ -20,6 +20,11 @@ Given(/^DC imports "([^"]*)" as ([GS1|WOT]+)$/, async function (importFilePath, const importResponse = await httpApiHelper.apiImport(host, importFilePath, importType); expect(importResponse).to.have.keys(['data_set_id', 'message', 'wallet']); + + // sometimes there is a need to remember import before the last one + if (this.state.lastImport) { + this.state.secondLastImport = this.state.lastImport; + } this.state.lastImport = importResponse; }); @@ -102,16 +107,24 @@ Given(/^([DV|DV2]+) publishes query consisting of path: "(\S+)", value: "(\S+)" return new Promise((accept, reject) => dv.once('dv-network-query-processed', () => accept())); }); -Given(/^the ([DV|DV2]+) purchases import from the last query from (a DH|the DC|a DV)$/, function (whichDV, fromWhom, done) { +Given(/^the ([DV|DV2]+) purchases ([last import|second last import]+) from the last query from (a DH|the DC|a DV)$/, function (whichDV, whichImport, fromWhom, done) { expect(whichDV, 'Query can be made either by DV or DV2.').to.satisfy(val => (val === 'DV' || val === 'DV2')); + expect(whichImport, 'last import or second last import are allowed values').to.be.oneOf(['last import', 'second last import']); + if (whichImport === 'last import') { + whichImport = 'lastImport'; + } else if (whichImport === 'second last import') { + whichImport = 'secondLastImport'; + } else { + throw Error('provided import is not valid'); + } expect(!!this.state[whichDV.toLowerCase()], 'DV/DV2 node not defined. Use other step to define it.').to.be.equal(true); - expect(!!this.state.lastImport, 'Nothing was imported. Use other step to do it.').to.be.equal(true); + expect(!!this.state[whichImport], 'Nothing was imported. Use other step to do it.').to.be.equal(true); expect(this.state.lastQueryNetworkId, 'Query not published yet.').to.not.be.undefined; const { dc } = this.state; const dv = this.state[whichDV.toLowerCase()]; const queryId = this.state.lastQueryNetworkId; - const dataSetId = this.state.lastImport.data_set_id; + const dataSetId = this.state[whichImport].data_set_id; let sellerNode; const confirmationsSoFar = diff --git a/test/bdd/steps/network.js b/test/bdd/steps/network.js index 1eb4ca37e9..a03b88090f 100644 --- a/test/bdd/steps/network.js +++ b/test/bdd/steps/network.js @@ -562,9 +562,19 @@ Given(/^I start additional node[s]*$/, { timeout: 60000 }, function () { return Promise.all(additionalNodesStarts); }); -Then(/^all nodes with last import should answer to last network query by ([DV|DV2]+)$/, { timeout: 90000 }, async function (whichDV) { +Then(/^all nodes with ([last import|second last import]+) should answer to last network query by ([DV|DV2]+)$/, { timeout: 90000 }, async function (whichImport, whichDV) { + expect(whichImport, 'last import or second last import are allowed values').to.be.oneOf(['last import', 'second last import']); + if (whichImport === 'last import') { + whichImport = 'lastImport'; + } else if (whichImport === 'second last import') { + whichImport = 'secondLastImport'; + } else { + throw Error('provided import is not valid'); + } + expect(!!this.state[whichDV.toLowerCase()], 'DV/DV2 node not defined. Use other step to define it.').to.be.equal(true); expect(this.state.lastQueryNetworkId, 'Query not published yet.').to.not.be.undefined; + expect(!!this.state[whichImport], 'Nothing was imported. Use other step to do it.').to.be.equal(true); const dv = this.state[whichDV.toLowerCase()]; @@ -575,7 +585,7 @@ Then(/^all nodes with last import should answer to last network query by ([DV|DV promises.push(new Promise(async (accept) => { const body = await httpApiHelper.apiImportsInfo(node.state.node_rpc_url); body.find((importInfo) => { - if (importInfo.data_set_id === this.state.lastImport.data_set_id) { + if (importInfo.data_set_id === this.state[whichImport].data_set_id) { nodeCandidates.push(node.state.identity); return true; } @@ -599,7 +609,7 @@ Then(/^all nodes with last import should answer to last network query by ([DV|DV // const intervalHandler; const intervalHandler = setInterval(async () => { const confirmationsSoFar = - dv.nodeConfirmsForDataSetId(queryId, this.state.lastImport.data_set_id); + dv.nodeConfirmsForDataSetId(queryId, this.state[whichImport].data_set_id); if (Date.now() - startTime > 60000) { clearTimeout(intervalHandler); reject(Error('Not enough confirmations for query. ' + From 540e33b8dcc79713f371c2678a5bb6166a603649 Mon Sep 17 00:00:00 2001 From: radomir-sebek Date: Tue, 11 Dec 2018 14:39:06 +0100 Subject: [PATCH 10/14] Scenario 8 and 9 (#162083739) (#800) * initial commit for scenario 8 and 9 * fixing multi-part json query --- test/bdd/features/datalayer.feature | 27 +++++++++++++---- test/bdd/steps/datalayer.js | 13 ++++---- test/bdd/steps/endpoints.js | 47 ++++++++++++++++++----------- test/bdd/steps/network.js | 11 ++----- 4 files changed, 60 insertions(+), 38 deletions(-) diff --git a/test/bdd/features/datalayer.feature b/test/bdd/features/datalayer.feature index c0b818e653..663d7f5879 100644 --- a/test/bdd/features/datalayer.feature +++ b/test/bdd/features/datalayer.feature @@ -23,7 +23,8 @@ Feature: Data layer related features And I start the nodes And I use 1st node as DC And DC imports "importers/xml_examples/Basic/01_Green_to_pink_shipment.xml" as GS1 - Given I query DC node locally with path: "identifiers.id", value: "urn:epc:id:sgtin:Batch_1" and opcode: "EQ" + Given I create json query with path: "identifiers.id", value: "urn:epc:id:sgtin:Batch_1" and opcode: "EQ" + And DC node makes local query with previous json query Then response should contain only last imported data set id Given I query DC node locally for last imported data set id Then response hash should match last imported data set id @@ -37,17 +38,20 @@ Feature: Data layer related features Then imported data is compliant with 01_Green_to_pink_shipment.xml file @second - Scenario: Dataset immutability I - Given I setup 1 node + Scenario: Dataset immutability DC and DH side + Given I setup 5 node And I start the node And I use 1st node as DC And DC imports "importers/xml_examples/Basic/01_Green_to_pink_shipment.xml" as GS1 Given DC initiates the replication for last imported dataset - And DC waits for last offer to get written to blockchain + And I wait for replications to finish And DC imports "importers/xml_examples/Retail/01_Green_to_pink_shipment.xml" as GS1 Given DC initiates the replication for last imported dataset - And DC waits for last offer to get written to blockchain + And I wait for replications to finish Then DC's 2 dataset hashes should match blockchain values + And I use 2nd node as DH + Then DH's 2 dataset hashes should match blockchain values + @second Scenario: Dataset immutability II @@ -109,4 +113,15 @@ Feature: Data layer related features And DV calls consensus endpoint for sender: "urn:ot:object:actor:id:Company_Pink" Then last consensus response should have 1 event with 1 match And DV calls consensus endpoint for sender: "urn:ot:object:actor:id:Company_Green" - Then last consensus response should have 1 event with 1 match \ No newline at end of file + Then last consensus response should have 1 event with 1 match + + @second + Scenario: Data location with multiple identifiers + Given I setup 1 node + And I start the node + And I use 1st node as DC + And DC imports "test/modules/test_xml/MultipleIdentifiers.xml" as GS1 + Given I create json query with path: "identifiers.uid", value: "urn:ot:object:product:id:P1" and opcode: "EQ" + And I append json query with path: "identifiers.ean13", value: "1234567890123" and opcode: "EQ" + Given DC node makes local query with previous json query + Then response should contain only last imported data set id \ No newline at end of file diff --git a/test/bdd/steps/datalayer.js b/test/bdd/steps/datalayer.js index 41cbb4d208..1a516b5741 100644 --- a/test/bdd/steps/datalayer.js +++ b/test/bdd/steps/datalayer.js @@ -71,21 +71,22 @@ Then(/^imported data is compliant with 01_Green_to_pink_shipment.xml file$/, asy ).to.be.above(0); }); -Then(/^DC's (\d+) dataset hashes should match blockchain values$/, async function (datasetsCount) { - expect(!!this.state.dc, 'DC node not defined. Use other step to define it.').to.be.equal(true); +Then(/^(DC|DH)'s (\d+) dataset hashes should match blockchain values$/, async function (nodeType, datasetsCount) { + expect(nodeType, 'Node type can only be DC or DH').to.be.oneOf(['DC', 'DH']); + expect(!!this.state[nodeType.toLowerCase()], 'DC/DH node not defined. Use other step to define it.').to.be.equal(true); expect(datasetsCount >= 1, 'datasetsCount should be positive integer').to.be.true; - const { dc } = this.state; - const myApiImportsInfo = await httpApiHelper.apiImportsInfo(dc.state.node_rpc_url); + const myNode = this.state[nodeType.toLowerCase()]; + const myApiImportsInfo = await httpApiHelper.apiImportsInfo(myNode.state.node_rpc_url); expect(myApiImportsInfo.length, 'We should have preciselly this many datasets').to.be.equal(datasetsCount); for (const i in Array.from({ length: myApiImportsInfo.length })) { const myDataSetId = myApiImportsInfo[i].data_set_id; - const myFingerprint = await httpApiHelper.apiFingerprint(dc.state.node_rpc_url, myDataSetId); + const myFingerprint = await httpApiHelper.apiFingerprint(myNode.state.node_rpc_url, myDataSetId); expect(utilities.isZeroHash(myFingerprint.root_hash), 'root hash value should not be zero hash').to.be.equal(false); - const myEdgesVertices = await httpApiHelper.apiQueryLocalImportByDataSetId(dc.state.node_rpc_url, myDataSetId); + const myEdgesVertices = await httpApiHelper.apiQueryLocalImportByDataSetId(myNode.state.node_rpc_url, myDataSetId); expect(myEdgesVertices, 'Should have corresponding keys').to.have.keys(['edges', 'vertices']); const calculatedImportHash = utilities.calculateImportHash(myEdgesVertices); diff --git a/test/bdd/steps/endpoints.js b/test/bdd/steps/endpoints.js index 521f9029fe..6f03cc6bd1 100644 --- a/test/bdd/steps/endpoints.js +++ b/test/bdd/steps/endpoints.js @@ -48,15 +48,10 @@ Given(/^DC initiates the replication for last imported dataset$/, { timeout: 600 this.state.lastReplication = response; }); -Given(/^I query ([DC|DH|DV]+) node locally with path: "(\S+)", value: "(\S+)" and opcode: "(\S+)"$/, async function (targetNode, path, value, opcode) { - expect(targetNode, 'Node type can only be DC, DH or DV.').to.satisfy(val => (val === 'DC' || val === 'DH' || val === 'DV')); +Given(/^I create json query with path: "(\S+)", value: "(\S+)" and opcode: "(\S+)"$/, async function (path, value, opcode) { expect(opcode, 'Opcode should only be EQ or IN.').to.satisfy(val => (val === 'EQ' || val === 'IN')); - expect(!!this.state[targetNode.toLowerCase()], 'Target node not defined. Use other step to define it.').to.be.equal(true); - - const host = this.state[targetNode.toLowerCase()].state.node_rpc_url; - - const jsonQuery = { + const myJsonQuery = { query: [ { @@ -66,7 +61,31 @@ Given(/^I query ([DC|DH|DV]+) node locally with path: "(\S+)", value: "(\S+)" an }, ], }; - const response = await httpApiHelper.apiQueryLocal(host, jsonQuery); + + this.state.jsonQuery = myJsonQuery; +}); + +Given(/^I append json query with path: "(\S+)", value: "(\S+)" and opcode: "(\S+)"$/, async function (path, value, opcode) { + expect(opcode, 'Opcode should only be EQ or IN.').to.satisfy(val => (val === 'EQ' || val === 'IN')); + expect(!!this.state.jsonQuery, 'json query must exist').to.be.equal(true); + + const myAppendQueryObject = { + path, + value, + opcode, + }; + + this.state.jsonQuery.query.push(myAppendQueryObject); +}); + +Given(/^(DC|DH|DV) node makes local query with previous json query$/, async function (targetNode) { + expect(targetNode, 'Node type can only be DC, DH or DV.').to.satisfy(val => (val === 'DC' || val === 'DH' || val === 'DV')); + expect(!!this.state[targetNode.toLowerCase()], 'Target node not defined. Use other step to define it.').to.be.equal(true); + + + const host = this.state[targetNode.toLowerCase()].state.node_rpc_url; + + const response = await httpApiHelper.apiQueryLocal(host, this.state.jsonQuery); this.state.apiQueryLocalResponse = response; }); @@ -107,16 +126,10 @@ Given(/^([DV|DV2]+) publishes query consisting of path: "(\S+)", value: "(\S+)" return new Promise((accept, reject) => dv.once('dv-network-query-processed', () => accept())); }); -Given(/^the ([DV|DV2]+) purchases ([last import|second last import]+) from the last query from (a DH|the DC|a DV)$/, function (whichDV, whichImport, fromWhom, done) { +Given(/^the ([DV|DV2]+) purchases (last import|second last import) from the last query from (a DH|the DC|a DV)$/, function (whichDV, whichImport, fromWhom, done) { expect(whichDV, 'Query can be made either by DV or DV2.').to.satisfy(val => (val === 'DV' || val === 'DV2')); - expect(whichImport, 'last import or second last import are allowed values').to.be.oneOf(['last import', 'second last import']); - if (whichImport === 'last import') { - whichImport = 'lastImport'; - } else if (whichImport === 'second last import') { - whichImport = 'secondLastImport'; - } else { - throw Error('provided import is not valid'); - } + expect(whichImport, 'last import or second last import are only allowed values').to.be.oneOf(['last import', 'second last import']); + whichImport = (whichImport === 'last import') ? 'lastImport' : 'secondLastImport'; expect(!!this.state[whichDV.toLowerCase()], 'DV/DV2 node not defined. Use other step to define it.').to.be.equal(true); expect(!!this.state[whichImport], 'Nothing was imported. Use other step to do it.').to.be.equal(true); expect(this.state.lastQueryNetworkId, 'Query not published yet.').to.not.be.undefined; diff --git a/test/bdd/steps/network.js b/test/bdd/steps/network.js index a03b88090f..9ab5f33728 100644 --- a/test/bdd/steps/network.js +++ b/test/bdd/steps/network.js @@ -562,16 +562,9 @@ Given(/^I start additional node[s]*$/, { timeout: 60000 }, function () { return Promise.all(additionalNodesStarts); }); -Then(/^all nodes with ([last import|second last import]+) should answer to last network query by ([DV|DV2]+)$/, { timeout: 90000 }, async function (whichImport, whichDV) { +Then(/^all nodes with (last import|second last import) should answer to last network query by ([DV|DV2]+)$/, { timeout: 90000 }, async function (whichImport, whichDV) { expect(whichImport, 'last import or second last import are allowed values').to.be.oneOf(['last import', 'second last import']); - if (whichImport === 'last import') { - whichImport = 'lastImport'; - } else if (whichImport === 'second last import') { - whichImport = 'secondLastImport'; - } else { - throw Error('provided import is not valid'); - } - + whichImport = (whichImport === 'last import') ? 'lastImport' : 'secondLastImport'; expect(!!this.state[whichDV.toLowerCase()], 'DV/DV2 node not defined. Use other step to define it.').to.be.equal(true); expect(this.state.lastQueryNetworkId, 'Query not published yet.').to.not.be.undefined; expect(!!this.state[whichImport], 'Nothing was imported. Use other step to do it.').to.be.equal(true); From e8d9c1c29d0541a2f5294a751a87f031262c4ca4 Mon Sep 17 00:00:00 2001 From: "radomir.sebek" Date: Thu, 6 Dec 2018 20:02:04 +0100 Subject: [PATCH 11/14] Adding /api/info endpoint to Postman collection --- postman/OriginTrail.postman_collection.json | 24 ++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/postman/OriginTrail.postman_collection.json b/postman/OriginTrail.postman_collection.json index a9eb3a41f5..a787139874 100644 --- a/postman/OriginTrail.postman_collection.json +++ b/postman/OriginTrail.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "eb8e304f-5b3e-4272-b420-14ab73000f2f", + "_postman_id": "07bdc6bb-9620-41a9-b6b0-904949a48586", "name": "OriginTrail", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -485,6 +485,28 @@ }, "response": [] }, + { + "name": "api/info", + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{baseUrl}}/api/info", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "info" + ] + } + }, + "response": [] + }, { "name": "/api/consensus/{{sender_id}}", "request": { From bc31cff17ee6a9635cd8701755c826c5d7b56e77 Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Wed, 12 Dec 2018 10:11:07 +0100 Subject: [PATCH 12/14] Fix delay for big numbers [#162540108] (#799) * fix delay for big numbers --- modules/command/command-executor.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/modules/command/command-executor.js b/modules/command/command-executor.js index f6fc65ac03..e9087502d2 100644 --- a/modules/command/command-executor.js +++ b/modules/command/command-executor.js @@ -4,6 +4,8 @@ const Command = require('./command'); const sleep = require('sleep-async')().Promise; +const MAX_DELAY_IN_MILLS = 14400 * 60 * 1000; // 10 days + /** * Command statuses * @type {{failed: string, expired: string, started: string, pending: string, completed: string}} @@ -106,7 +108,7 @@ class CommandExecutor { const waitMs = (command.ready_at + command.delay) - now; if (waitMs > 0) { this.logger.trace(`Command ${command.name} with ID ${command.id} should be delayed`); - await this.add(command, waitMs, false); + await this.add(command, Math.min(waitMs, MAX_DELAY_IN_MILLS), false); return; } @@ -192,6 +194,16 @@ class CommandExecutor { * @param insert */ async add(command, delay = 0, insert = true) { + const now = Date.now(); + + if (delay != null && delay > MAX_DELAY_IN_MILLS) { + if (command.ready_at == null) { + command.ready_at = now; + } + command.ready_at += delay; + delay = MAX_DELAY_IN_MILLS; + } + if (insert) { command = await this._insert(command); } @@ -243,9 +255,9 @@ class CommandExecutor { command.sequence = command.sequence.slice(1); } if (!command.ready_at) { - command.ready_at = Date.now(); + command.ready_at = Date.now(); // take current time } - if (!command.delay) { + if (command.delay == null) { command.delay = 0; } if (!command.transactional) { From 33c691b345455fa1161fe0d092dc3345d4ad2c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20Obradovi=C4=87?= Date: Wed, 12 Dec 2018 18:21:34 +0100 Subject: [PATCH 13/14] Added /api/balance, deposit-on-demand, turned off, cleaner set to 4 days (#802) - Added /api/balance. - Deposit-on-demand turned off by default for all environments except develop. - Cleaner command period set to 4 days. --- config/config.json | 8 ++--- modules/Utilities.js | 9 +++-- modules/command/common/cleaner-command.js | 2 +- modules/service/rest-api-service.js | 40 +++++++++++++++++++++++ 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/config/config.json b/config/config.json index b7adaefed9..f278fe963c 100644 --- a/config/config.json +++ b/config/config.json @@ -195,7 +195,7 @@ "dh_max_holding_time_in_minutes": 1440, "dh_min_token_price": "1", "dh_min_litigation_interval_in_minutes": 5, - "deposit_on_demand": true, + "deposit_on_demand": false, "dc_choose_time": 600000, "requireApproval": false }, @@ -295,7 +295,7 @@ "dh_max_holding_time_in_minutes": 10080, "dh_min_token_price": "10000000000000000000", "dh_min_litigation_interval_in_minutes": 5, - "deposit_on_demand": true, + "deposit_on_demand": false, "dc_choose_time": 600000, "requireApproval": false }, @@ -398,7 +398,7 @@ "dh_max_holding_time_in_minutes": 10080, "dh_min_token_price": "10000000000000000000", "dh_min_litigation_interval_in_minutes": 5, - "deposit_on_demand": true, + "deposit_on_demand": false, "dc_choose_time": 600000, "requireApproval": false }, @@ -501,7 +501,7 @@ "dh_max_holding_time_in_minutes": 10080, "dh_min_token_price": "20000000000000000000", "dh_min_litigation_interval_in_minutes": 5, - "deposit_on_demand": true, + "deposit_on_demand": false, "dc_choose_time": 600000, "requireApproval": true } diff --git a/modules/Utilities.js b/modules/Utilities.js index 8f72c1ea5e..5f880fedfd 100644 --- a/modules/Utilities.js +++ b/modules/Utilities.js @@ -253,9 +253,10 @@ class Utilities { * @param web3 Instance of Web3 * @param wallet Address of the wallet. * @param tokenContractAddress Contract address. + * @param humanReadable format result in floating point TRAC value or not i.e. 0.3. * @returns {Promise} */ - static async getTracTokenBalance(web3, wallet, tokenContractAddress) { + static async getTracTokenBalance(web3, wallet, tokenContractAddress, humanReadable = true) { const walletDenormalized = this.denormalizeHex(wallet); // '0x70a08231' is the contract 'balanceOf()' ERC20 token function in hex. const contractData = (`0x70a08231000000000000000000000000${walletDenormalized}`); @@ -264,7 +265,11 @@ class Utilities { data: contractData, }); const tokensInWei = web3.utils.toBN(result).toString(); - return web3.utils.fromWei(tokensInWei, 'ether'); + if (humanReadable) { + return web3.utils.fromWei(tokensInWei, 'ether'); + } + + return tokensInWei; } /** diff --git a/modules/command/common/cleaner-command.js b/modules/command/common/cleaner-command.js index 69afd1ffa1..1ddf6c5f87 100644 --- a/modules/command/common/cleaner-command.js +++ b/modules/command/common/cleaner-command.js @@ -34,7 +34,7 @@ class CleanerCommand extends Command { name: 'cleanerCommand', data: { }, - period: 60 * 60 * 1000, + period: 4 * 24 * 60 * 60 * 1000, transactional: false, }; Object.assign(command, map); diff --git a/modules/service/rest-api-service.js b/modules/service/rest-api-service.js index 28c0cdaa5a..1f90f72374 100644 --- a/modules/service/rest-api-service.js +++ b/modules/service/rest-api-service.js @@ -138,10 +138,50 @@ class RestAPIService { _exposeAPIRoutes(server) { const { importController, dcController, transport, emitter, + blockchain, web3, config, } = this.ctx; this._registerNodeInfoRoute(server, false); + server.get('/api/balance', async (req, res) => { + this.logger.api('Get balance.'); + + try { + const humanReadable = req.query.humanReadable === 'true'; + + const walletEthBalance = await web3.eth.getBalance(config.node_wallet); + const walletTokenBalance = await Utilities.getTracTokenBalance( + web3, + config.node_wallet, + blockchain.getTokenContractAddress(), + false, + ); + const profile = await blockchain.getProfile(config.erc725Identity); + const profileMinimalStake = await blockchain.getProfileMinimumStake(); + + const body = { + wallet: { + address: config.node_wallet, + ethBalance: humanReadable ? web3.utils.fromWei(walletEthBalance, 'ether') : walletEthBalance, + tokenBalance: humanReadable ? web3.utils.fromWei(walletTokenBalance, 'ether') : walletTokenBalance, + }, + profile: { + staked: humanReadable ? web3.utils.fromWei(profile.stake, 'ether') : profile.stake, + reserved: humanReadable ? web3.utils.fromWei(profile.stakeReserved, 'ether') : profile.stakeReserved, + minimalStake: humanReadable ? web3.utils.fromWei(profileMinimalStake, 'ether') : profileMinimalStake, + }, + }; + + res.status(200); + res.send(body); + } catch (error) { + this.logger.error(`Failed to get balance. ${error.message}.`); + res.status(503); + res.send({}); + } + }); + + /** * Data import route * @param importfile - file or text data From 4332861655c3f5742fe6b99ea8a61aa00d04a6e0 Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Wed, 12 Dec 2018 18:22:44 +0100 Subject: [PATCH 14/14] Version 2.0.33 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7fa9087fde..7b2c24fce8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.32", + "version": "2.0.33", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index cdd1e3c921..528f80d94c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.32", + "version": "2.0.33", "description": "OriginTrail node", "main": ".eslintrc.js", "config": {