From bd11433979bbd6f7ca8e7376bdc0399053f5a3bb Mon Sep 17 00:00:00 2001 From: Uros Kukic Date: Fri, 14 Dec 2018 17:31:22 +0100 Subject: [PATCH 01/48] Add operational and management wallets in Identity smart contract --- .../Blockchain/Ethereum/contracts/ByteArr.sol | 2 + .../Ethereum/contracts/Identity.sol | 43 ++++++++++++++++--- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/modules/Blockchain/Ethereum/contracts/ByteArr.sol b/modules/Blockchain/Ethereum/contracts/ByteArr.sol index a53b7ccb7..ba7a66450 100644 --- a/modules/Blockchain/Ethereum/contracts/ByteArr.sol +++ b/modules/Blockchain/Ethereum/contracts/ByteArr.sol @@ -24,6 +24,7 @@ library ByteArr { self[index] = self[self.length-1]; delete self[self.length-1]; + self.length = self.length - 1; return self; } @@ -33,6 +34,7 @@ library ByteArr { self[index] = self[self.length-1]; delete self[self.length-1]; + self.length = self.length - 1; return self; } diff --git a/modules/Blockchain/Ethereum/contracts/Identity.sol b/modules/Blockchain/Ethereum/contracts/Identity.sol index 7aa47ce46..55d8ba9cb 100644 --- a/modules/Blockchain/Ethereum/contracts/Identity.sol +++ b/modules/Blockchain/Ethereum/contracts/Identity.sol @@ -22,16 +22,41 @@ contract Identity is ERC725 { mapping (uint256 => bytes32[]) keysByPurpose; mapping (uint256 => Execution) executions; - constructor(address sender) public { - bytes32 _key = keccak256(abi.encodePacked(sender)); - keys[_key].key = _key; - keys[_key].purposes = [1,2,3,4]; - keys[_key].keyType = 1; - keysByPurpose[1].push(_key); - emit KeyAdded(_key, keys[_key].purposes, 1); + constructor(address operational, address management) public { + require(operational != address(0) && management != address(0)); + + bytes32 _management_key = keccak256(abi.encodePacked(management)); + + keys[_management_key].key = _management_key; + + keys[_management_key].keyType = 1; + + keys[_management_key].purposes = [1,2,3,4]; + + keysByPurpose[1].push(_management_key); + keysByPurpose[2].push(_management_key); + keysByPurpose[3].push(_management_key); + keysByPurpose[4].push(_management_key); + emit KeyAdded(_management_key, keys[_management_key].purposes, 1); + + if(operational != management){ + bytes32 _operational_key = keccak256(abi.encodePacked(operational)); + + keys[_operational_key].key = _operational_key; + + keys[_operational_key].keyType = 1; + + keys[_operational_key].purposes = [2,4]; + + keysByPurpose[2].push(_operational_key); + keysByPurpose[4].push(_operational_key); + + emit KeyAdded(_operational_key, keys[_operational_key].purposes, 1); + } } function addKey(bytes32 _key, uint256[] _purposes, uint256 _type) external returns (bool success) { + require(keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1)); require(keys[_key].key != _key); keys[_key].key = _key; @@ -99,7 +124,11 @@ contract Identity is ERC725 { } function removeKey(bytes32 _key) external returns (bool success) { + require(keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1)); require(keys[_key].key == _key); + + require(!(keysByPurpose[1].length == 1 && keyHasPurpose(_key, 1)), "Cannot delete only management key!"); + emit KeyRemoved(keys[_key].key, keys[_key].purposes, keys[_key].keyType); for (uint i = 0; i < keys[_key].purposes.length; i++) { From e678d64259425c7183a71cefc816e97209d48c7d Mon Sep 17 00:00:00 2001 From: Uros Kukic Date: Fri, 14 Dec 2018 18:16:00 +0100 Subject: [PATCH 02/48] Implement operational and management wallets in profile contract --- modules/Blockchain/Ethereum/abi/profile.json | 91 ++++++++++++++++--- .../Blockchain/Ethereum/contracts/Profile.sol | 39 ++++++-- 2 files changed, 109 insertions(+), 21 deletions(-) diff --git a/modules/Blockchain/Ethereum/abi/profile.json b/modules/Blockchain/Ethereum/abi/profile.json index 66478bcbe..2f9c46333 100644 --- a/modules/Blockchain/Ethereum/abi/profile.json +++ b/modules/Blockchain/Ethereum/abi/profile.json @@ -19,25 +19,27 @@ }, { "constant": false, - "inputs": [ - { - "name": "profileNodeId", - "type": "bytes32" - }, - { - "name": "initialBalance", - "type": "uint256" - }, + "inputs": [], + "name": "getVersion", + "outputs": [ { - "name": "senderHas725", - "type": "bool" - }, + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ { - "name": "identity", + "name": "oldIdentity", "type": "address" } ], - "name": "createProfile", + "name": "transferProfile", "outputs": [], "payable": false, "stateMutability": "nonpayable", @@ -99,6 +101,20 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "constant": true, + "inputs": [], + "name": "version", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, { "constant": false, "inputs": [ @@ -117,6 +133,36 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "managementWallet", + "type": "address" + }, + { + "name": "profileNodeId", + "type": "bytes32" + }, + { + "name": "initialBalance", + "type": "uint256" + }, + { + "name": "senderHas725", + "type": "bool" + }, + { + "name": "identity", + "type": "address" + } + ], + "name": "createProfile", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": false, "inputs": [ @@ -292,6 +338,23 @@ "name": "IdentityCreated", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "oldIdentity", + "type": "address" + }, + { + "indexed": false, + "name": "newIdentity", + "type": "address" + } + ], + "name": "IdentityTransferred", + "type": "event" + }, { "anonymous": false, "inputs": [ diff --git a/modules/Blockchain/Ethereum/contracts/Profile.sol b/modules/Blockchain/Ethereum/contracts/Profile.sol index 7ac6cfdc8..14dd0a601 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -12,6 +12,8 @@ contract Profile { Hub public hub; ProfileStorage public profileStorage; + string public version = "1.0.0"; + uint256 public minimalStake = 10**21; uint256 public withdrawalTime = 5 minutes; @@ -26,9 +28,10 @@ contract Profile { "Function can only be called by Holding contract!"); _; } - + event ProfileCreated(address profile, uint256 initialBalance); event IdentityCreated(address profile, address newIdentity); + event IdentityTransferred(address oldIdentity, address newIdentity); event TokenDeposit(address profile, uint256 amount); event TokensDeposited(address profile, uint256 amountDeposited, uint256 newBalance); @@ -41,7 +44,7 @@ contract Profile { event TokensReleased(address profile, uint256 amount); event TokensTransferred(address sender, address receiver, uint256 amount); - function createProfile(bytes32 profileNodeId, uint256 initialBalance, bool senderHas725, address identity) public { + function createProfile(address managementWallet, bytes32 profileNodeId, uint256 initialBalance, bool senderHas725, address identity) public { 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!"); @@ -50,7 +53,7 @@ contract Profile { tokenContract.transferFrom(msg.sender, address(profileStorage), initialBalance); if(!senderHas725) { - Identity newIdentity = new Identity(msg.sender); + Identity newIdentity = new Identity(msg.sender, managementWallet); emit IdentityCreated(msg.sender, address(newIdentity)); profileStorage.setStake(address(newIdentity), initialBalance); @@ -60,7 +63,7 @@ contract Profile { } else { // Verify sender - require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2)); + require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2), "Sender does not have action permission for identity!"); profileStorage.setStake(identity, initialBalance); profileStorage.setNodeId(identity, profileNodeId); @@ -75,9 +78,26 @@ contract Profile { } } + function transferProfile(address oldIdentity) public { + require(ERC725(oldIdentity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); + + Identity newIdentity = new Identity(msg.sender, msg.sender); + emit IdentityCreated(msg.sender, address(newIdentity)); + + profileStorage.setStake(address(newIdentity), profileStorage.getStake(oldIdentity)); + profileStorage.setStakeReserved(address(newIdentity), profileStorage.getStakeReserved(oldIdentity)); + profileStorage.setNodeId(address(newIdentity), profileStorage.getNodeId(oldIdentity)); + + profileStorage.setStake(oldIdentity, 0); + profileStorage.setStakeReserved(oldIdentity, 0); + profileStorage.setNodeId(oldIdentity, bytes32(0)); + + emit IdentityTransferred(oldIdentity, newIdentity); + } + function depositTokens(address identity, uint256 amount) public { // Verify sender - require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2)); + require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); ERC20 tokenContract = ERC20(hub.tokenAddress()); require(tokenContract.allowance(msg.sender, this) >= amount, "Sender allowance must be equal to or higher than chosen amount"); @@ -92,7 +112,7 @@ contract Profile { function startTokenWithdrawal(address identity, uint256 amount) public { // Verify sender - require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2)); + require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); require(profileStorage.getWithdrawalPending(identity) == false, "Withrdrawal process already pending!"); @@ -114,7 +134,7 @@ contract Profile { function withdrawTokens(address identity) public { // Verify sender - require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2), "Sender does not have action permission for identity!"); + require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); require(profileStorage.getWithdrawalPending(identity) == true, "Cannot withdraw tokens before starting token withdrawal!"); require(profileStorage.getWithdrawalTimestamp(identity) < block.timestamp, "Cannot withdraw tokens before withdrawal timestamp!"); @@ -229,4 +249,9 @@ contract Profile { require (msg.sender == hub.owner(), "Function can only be called by hub owner!"); if(withdrawalTime != newWithdrawalTime) withdrawalTime = newWithdrawalTime; } + + function getVersion() + public returns (string) { + return version; + } } From 3422568c4630c6b35291a98da46aff6be9c4fe0c Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Fri, 14 Dec 2018 19:28:59 +0100 Subject: [PATCH 03/48] Change logs to recommend token deposits (#804) --- modules/service/dc-service.js | 2 +- modules/service/dh-service.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/service/dc-service.js b/modules/service/dc-service.js index 6da349ec2..36ac3a330 100644 --- a/modules/service/dc-service.js +++ b/modules/service/dc-service.js @@ -148,7 +148,7 @@ class DCService { let depositCommand = null; if (remainder) { if (!this.config.deposit_on_demand) { - const message = 'Not enough tokens. Deposit on demand feature is disabled. Please, enable it in your configuration.'; + const message = 'Not enough tokens. To replicate data please deposit more tokens to your profile.'; this.logger.warn(message); throw new Error(message); } diff --git a/modules/service/dh-service.js b/modules/service/dh-service.js index b7c00ff6b..f36ecc2dc 100644 --- a/modules/service/dh-service.js +++ b/modules/service/dh-service.js @@ -166,7 +166,7 @@ class DHService { if (remainder) { if (!this.config.deposit_on_demand) { - throw new Error('Not enough tokens. Deposit on demand feature is disabled. Please, enable it in your configuration.'); + throw new Error('Not enough tokens. To take additional jobs please complete any finished jobs or deposit more tokens to your profile.'); } bid.deposit = remainder.toString(); From fe99b3d7f49877162e7afee64ae643d9e50e32bc Mon Sep 17 00:00:00 2001 From: Uros Kukic Date: Fri, 14 Dec 2018 19:42:42 +0100 Subject: [PATCH 04/48] Return new identity address in transfer profile function --- modules/Blockchain/Ethereum/abi/profile.json | 7 ++++++- modules/Blockchain/Ethereum/contracts/Profile.sol | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/Blockchain/Ethereum/abi/profile.json b/modules/Blockchain/Ethereum/abi/profile.json index 2f9c46333..12af46010 100644 --- a/modules/Blockchain/Ethereum/abi/profile.json +++ b/modules/Blockchain/Ethereum/abi/profile.json @@ -40,7 +40,12 @@ } ], "name": "transferProfile", - "outputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], "payable": false, "stateMutability": "nonpayable", "type": "function" diff --git a/modules/Blockchain/Ethereum/contracts/Profile.sol b/modules/Blockchain/Ethereum/contracts/Profile.sol index 14dd0a601..93c246511 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -78,7 +78,7 @@ contract Profile { } } - function transferProfile(address oldIdentity) public { + function transferProfile(address oldIdentity) public returns(address newIdentity){ require(ERC725(oldIdentity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); Identity newIdentity = new Identity(msg.sender, msg.sender); @@ -93,6 +93,7 @@ contract Profile { profileStorage.setNodeId(oldIdentity, bytes32(0)); emit IdentityTransferred(oldIdentity, newIdentity); + return address(newIdentity); } function depositTokens(address identity, uint256 amount) public { From 0499a4b9e4068383aae3e53db0ac3c85c2103ab8 Mon Sep 17 00:00:00 2001 From: Uros Kukic Date: Fri, 14 Dec 2018 20:44:05 +0100 Subject: [PATCH 05/48] Update truffle tests --- modules/Blockchain/Ethereum/test/offer.test.js | 1 + modules/Blockchain/Ethereum/test/profile.test.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/modules/Blockchain/Ethereum/test/offer.test.js b/modules/Blockchain/Ethereum/test/offer.test.js index c7699fa4e..63835205e 100644 --- a/modules/Blockchain/Ethereum/test/offer.test.js +++ b/modules/Blockchain/Ethereum/test/offer.test.js @@ -101,6 +101,7 @@ contract('Offer testing', async (accounts) => { for (i = 0; i < accounts.length; i += 1) { // eslint-disable-next-line no-await-in-loop res = await profile.createProfile( + accounts[i], '0x4cad6896887d99d70db8ce035d331ba2ade1a5e1161f38ff7fda76cf7c308cde', tokensToDeposit, false, diff --git a/modules/Blockchain/Ethereum/test/profile.test.js b/modules/Blockchain/Ethereum/test/profile.test.js index 94c3b1deb..ae2a5a66c 100644 --- a/modules/Blockchain/Ethereum/test/profile.test.js +++ b/modules/Blockchain/Ethereum/test/profile.test.js @@ -73,6 +73,7 @@ contract('Profile contract testing', async (accounts) => { promises = []; for (i = 0; i < accounts.length; i += 1) { promises[i] = profile.createProfile( + accounts[i], nodeId, amountToDeposit, true, @@ -154,6 +155,7 @@ contract('Profile contract testing', async (accounts) => { for (i = 0; i < accounts.length; i += 1) { // eslint-disable-next-line no-await-in-loop const res = await profile.createProfile( + accounts[i], nodeId, amountToDeposit, false, From c927b2cd6ba8c43389af12b76b4c85844c992ad9 Mon Sep 17 00:00:00 2001 From: Uros Kukic Date: Fri, 14 Dec 2018 20:46:25 +0100 Subject: [PATCH 06/48] Reduce profile size to enable deployment --- modules/Blockchain/Ethereum/abi/profile.json | 428 +++++++++--------- .../Blockchain/Ethereum/contracts/Profile.sol | 9 +- 2 files changed, 209 insertions(+), 228 deletions(-) diff --git a/modules/Blockchain/Ethereum/abi/profile.json b/modules/Blockchain/Ethereum/abi/profile.json index 12af46010..d7737639e 100644 --- a/modules/Blockchain/Ethereum/abi/profile.json +++ b/modules/Blockchain/Ethereum/abi/profile.json @@ -1,143 +1,4 @@ [ - { - "constant": false, - "inputs": [ - { - "name": "identity", - "type": "address" - }, - { - "name": "amount", - "type": "uint256" - } - ], - "name": "startTokenWithdrawal", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "getVersion", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "oldIdentity", - "type": "address" - } - ], - "name": "transferProfile", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "hub", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "withdrawalTime", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newMinimalStake", - "type": "uint256" - } - ], - "name": "setMinimalStake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "identity", - "type": "address" - } - ], - "name": "withdrawTokens", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "version", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "identity", - "type": "address" - }, - { - "name": "amount", - "type": "uint256" - } - ], - "name": "depositTokens", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, { "constant": false, "inputs": [ @@ -176,39 +37,21 @@ "type": "address" }, { - "name": "newNodeId", - "type": "bytes32" + "name": "amount", + "type": "uint256" } ], - "name": "setNodeId", + "name": "depositTokens", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, - { - "constant": true, - "inputs": [], - "name": "minimalStake", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, { "constant": false, "inputs": [ { - "name": "sender", - "type": "address" - }, - { - "name": "receiver", + "name": "profile", "type": "address" }, { @@ -216,21 +59,7 @@ "type": "uint256" } ], - "name": "transferTokens", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newWithdrawalTime", - "type": "uint256" - } - ], - "name": "setWithdrawalTime", + "name": "releaseTokens", "outputs": [], "payable": false, "stateMutability": "nonpayable", @@ -270,44 +99,47 @@ "constant": false, "inputs": [ { - "name": "profile", - "type": "address" - }, - { - "name": "amount", + "name": "newMinimalStake", "type": "uint256" } ], - "name": "releaseTokens", + "name": "setMinimalStake", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { - "constant": true, - "inputs": [], - "name": "profileStorage", - "outputs": [ + "constant": false, + "inputs": [ { - "name": "", + "name": "identity", "type": "address" + }, + { + "name": "newNodeId", + "type": "bytes32" } ], + "name": "setNodeId", + "outputs": [], "payable": false, - "stateMutability": "view", + "stateMutability": "nonpayable", "type": "function" }, { + "constant": false, "inputs": [ { - "name": "hubAddress", - "type": "address" + "name": "newWithdrawalTime", + "type": "uint256" } ], + "name": "setWithdrawalTime", + "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "constructor" + "type": "function" }, { "anonymous": false, @@ -319,11 +151,11 @@ }, { "indexed": false, - "name": "initialBalance", + "name": "amount", "type": "uint256" } ], - "name": "ProfileCreated", + "name": "TokensReleased", "type": "event" }, { @@ -336,29 +168,35 @@ }, { "indexed": false, - "name": "newIdentity", - "type": "address" + "name": "amountWithdrawn", + "type": "uint256" + }, + { + "indexed": false, + "name": "newBalance", + "type": "uint256" } ], - "name": "IdentityCreated", + "name": "TokensWithdrawn", "type": "event" }, { - "anonymous": false, + "constant": false, "inputs": [ { - "indexed": false, - "name": "oldIdentity", + "name": "identity", "type": "address" }, { - "indexed": false, - "name": "newIdentity", - "type": "address" + "name": "amount", + "type": "uint256" } ], - "name": "IdentityTransferred", - "type": "event" + "name": "startTokenWithdrawal", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" }, { "anonymous": false, @@ -370,11 +208,11 @@ }, { "indexed": false, - "name": "amount", + "name": "amountReserved", "type": "uint256" } ], - "name": "TokenDeposit", + "name": "TokensReserved", "type": "event" }, { @@ -406,14 +244,9 @@ "indexed": false, "name": "profile", "type": "address" - }, - { - "indexed": false, - "name": "amountReserved", - "type": "uint256" } ], - "name": "TokensReserved", + "name": "TokenWithdrawalCancelled", "type": "event" }, { @@ -445,9 +278,14 @@ "indexed": false, "name": "profile", "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" } ], - "name": "TokenWithdrawalCancelled", + "name": "TokenDeposit", "type": "event" }, { @@ -455,21 +293,33 @@ "inputs": [ { "indexed": false, - "name": "profile", + "name": "oldIdentity", "type": "address" }, { "indexed": false, - "name": "amountWithdrawn", - "type": "uint256" + "name": "newIdentity", + "type": "address" + } + ], + "name": "IdentityTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "profile", + "type": "address" }, { "indexed": false, - "name": "newBalance", - "type": "uint256" + "name": "newIdentity", + "type": "address" } ], - "name": "TokensWithdrawn", + "name": "IdentityCreated", "type": "event" }, { @@ -482,11 +332,11 @@ }, { "indexed": false, - "name": "amount", + "name": "initialBalance", "type": "uint256" } ], - "name": "TokensReleased", + "name": "ProfileCreated", "type": "event" }, { @@ -510,5 +360,141 @@ ], "name": "TokensTransferred", "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "name": "oldIdentity", + "type": "address" + } + ], + "name": "transferProfile", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "sender", + "type": "address" + }, + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transferTokens", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "identity", + "type": "address" + } + ], + "name": "withdrawTokens", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "hubAddress", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "constant": true, + "inputs": [], + "name": "hub", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minimalStake", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "profileStorage", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "version", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "withdrawalTime", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" } ] \ No newline at end of file diff --git a/modules/Blockchain/Ethereum/contracts/Profile.sol b/modules/Blockchain/Ethereum/contracts/Profile.sol index 93c246511..8a6c03fe5 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -12,7 +12,7 @@ contract Profile { Hub public hub; ProfileStorage public profileStorage; - string public version = "1.0.0"; + uint256 public version = 100; uint256 public minimalStake = 10**21; uint256 public withdrawalTime = 5 minutes; @@ -78,7 +78,7 @@ contract Profile { } } - function transferProfile(address oldIdentity) public returns(address newIdentity){ + function transferProfile(address oldIdentity) public returns(address){ require(ERC725(oldIdentity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); Identity newIdentity = new Identity(msg.sender, msg.sender); @@ -250,9 +250,4 @@ contract Profile { require (msg.sender == hub.owner(), "Function can only be called by hub owner!"); if(withdrawalTime != newWithdrawalTime) withdrawalTime = newWithdrawalTime; } - - function getVersion() - public returns (string) { - return version; - } } From b9e43d82736bc7b571354401d48876ee11a5a938 Mon Sep 17 00:00:00 2001 From: Uros Kukic Date: Fri, 14 Dec 2018 21:15:07 +0100 Subject: [PATCH 07/48] Disable adding empty keys to identities --- modules/Blockchain/Ethereum/contracts/Identity.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/Blockchain/Ethereum/contracts/Identity.sol b/modules/Blockchain/Ethereum/contracts/Identity.sol index 55d8ba9cb..76def1de0 100644 --- a/modules/Blockchain/Ethereum/contracts/Identity.sol +++ b/modules/Blockchain/Ethereum/contracts/Identity.sol @@ -57,6 +57,7 @@ contract Identity is ERC725 { function addKey(bytes32 _key, uint256[] _purposes, uint256 _type) external returns (bool success) { require(keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1)); + require(_key != bytes32(0)); require(keys[_key].key != _key); keys[_key].key = _key; @@ -125,6 +126,8 @@ contract Identity is ERC725 { function removeKey(bytes32 _key) external returns (bool success) { require(keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1)); + require(_key != bytes32(0)); + require(keys[_key].key == _key); require(!(keysByPurpose[1].length == 1 && keyHasPurpose(_key, 1)), "Cannot delete only management key!"); From 1e2d53047cb60a65d3a9550c22a9091545242e89 Mon Sep 17 00:00:00 2001 From: Uros Kukic Date: Fri, 14 Dec 2018 23:11:07 +0100 Subject: [PATCH 08/48] Update truffle tests --- modules/Blockchain/Ethereum/test/profile.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/Blockchain/Ethereum/test/profile.test.js b/modules/Blockchain/Ethereum/test/profile.test.js index ae2a5a66c..b90f1c1c5 100644 --- a/modules/Blockchain/Ethereum/test/profile.test.js +++ b/modules/Blockchain/Ethereum/test/profile.test.js @@ -51,7 +51,7 @@ contract('Profile contract testing', async (accounts) => { var identities = []; for (var i = 0; i < accounts.length; i += 1) { // eslint-disable-next-line no-await-in-loop - identities[i] = await Identity.new(accounts[i], { from: accounts[i] }); + identities[i] = await Identity.new(accounts[i], accounts[i], { from: accounts[i] }); } var initialBalances = []; From bf29cc5f1754a8bd21737b4659f86975e2d2a557 Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Fri, 14 Dec 2018 23:20:12 +0100 Subject: [PATCH 09/48] Update old profiles --- modules/Blockchain.js | 60 ++++++++++++++++++++------ modules/Blockchain/Ethereum/index.js | 63 ++++++++++++++++++++++++++- modules/service/profile-service.js | 64 ++++++++++++++++++++++++++-- ot-node.js | 1 + 4 files changed, 170 insertions(+), 18 deletions(-) diff --git a/modules/Blockchain.js b/modules/Blockchain.js index 9525e1c89..e9a28f69f 100644 --- a/modules/Blockchain.js +++ b/modules/Blockchain.js @@ -61,14 +61,22 @@ class Blockchain { /** * Creates node profile on the Bidding contract + * @param managementWallet - Management wallet * @param profileNodeId - Network node ID * @param initialBalance - Initial profile balance * @param isSender725 - Is sender ERC 725? * @param blockchainIdentity - ERC 725 identity (empty if there is none) * @return {Promise} */ - createProfile(profileNodeId, initialBalance, isSender725, blockchainIdentity) { + createProfile( + managementWallet, + profileNodeId, + initialBalance, + isSender725, + blockchainIdentity, + ) { return this.blockchain.createProfile( + managementWallet, profileNodeId, initialBalance, isSender725, blockchainIdentity, ); @@ -204,13 +212,13 @@ class Blockchain { } /** - * Subscribe to a particular event - * @param event - * @param importId - * @param endMs - * @param endCallback - * @param filterFn - */ + * Subscribe to a particular event + * @param event + * @param importId + * @param endMs + * @param endCallback + * @param filterFn + */ subscribeToEvent(event, importId, endMs = 5 * 60 * 1000, endCallback, filterFn) { return this.blockchain .subscribeToEvent(event, importId, endMs, endCallback, filterFn); @@ -282,10 +290,10 @@ class Blockchain { } /** - * Gets status of the offer - * @param importId - * @return {Promise} - */ + * Gets status of the offer + * @param importId + * @return {Promise} + */ getOfferStatus(importId) { return this.blockchain.getOfferStatus(importId); } @@ -345,6 +353,7 @@ class Blockchain { async confirmPurchase(importId, dhWallet) { return this.blockchain.confirmPurchase(importId, dhWallet); } + async cancelPurchase(importId, correspondentWallet, senderIsDh) { return this.blockchain.cancelPurchase(importId, correspondentWallet, senderIsDh); } @@ -432,6 +441,33 @@ class Blockchain { getTokenContractAddress() { return this.blockchain.getTokenContractAddress(); } + + /** + * Returns purposes of the wallet. + * @param {string} - erc725Identity + * @param {string} - wallet + * @return {Promise<[]>} + */ + getWalletPurposes(erc725Identity, wallet) { + return this.blockchain.getWalletPurposes(erc725Identity, wallet); + } + + /** + * Transfers identity to new address. + * @param {string} - erc725identity + */ + transferProfile(erc725identity) { + return this.blockchain.transferProfile(erc725identity); + } + + /** + * Returns true if ERC725 contract is older version. + * @param {string} - address of ERC 725 identity. + * @return {Promise} + */ + async isErc725IdentityOld(address) { + return this.blockchain.isErc725IdentityOld(address); + } } module.exports = Blockchain; diff --git a/modules/Blockchain/Ethereum/index.js b/modules/Blockchain/Ethereum/index.js index 79300326a..c7c59c5af 100644 --- a/modules/Blockchain/Ethereum/index.js +++ b/modules/Blockchain/Ethereum/index.js @@ -4,6 +4,10 @@ const Utilities = require('../../Utilities'); const Models = require('../../../models'); const Op = require('sequelize/lib/operators'); const uuidv4 = require('uuid/v4'); +const ethereumAbi = require('ethereumjs-abi'); +const md5 = require('md5'); + +const oldErc725codeMd5 = '4fbb9cd89fa6b148d1f0c3b96fdadcfc'; class Ethereum { /** @@ -111,6 +115,10 @@ class Ethereum { this.holdingStorageContractAddress, ); + // ERC725 identity contract data. Every user has own instance. + const erc725IdentityAbiFile = fs.readFileSync('./modules/Blockchain/Ethereum/abi/erc725.json'); + this.erc725IdentityContractAbi = JSON.parse(erc725IdentityAbiFile); + this.contractsByName = { HOLDING_CONTRACT: this.holdingContract, PROFILE_CONTRACT: this.profileContract, @@ -245,22 +253,30 @@ class Ethereum { /** * Creates node profile on the Bidding contract + * @param managementWallet - Management wallet * @param profileNodeId - Network node ID * @param initialBalance - Initial profile balance * @param isSender725 - Is sender ERC 725? * @param blockchainIdentity - ERC 725 identity (empty if there is none) * @return {Promise} */ - createProfile(profileNodeId, initialBalance, isSender725, blockchainIdentity) { + createProfile( + managementWallet, + profileNodeId, + initialBalance, + isSender725, + blockchainIdentity, + ) { const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), gasPrice: this.web3.utils.toHex(this.config.gas_price), to: this.profileContractAddress, }; - this.log.trace(`CreateProfile(${profileNodeId}, ${initialBalance}, ${isSender725})`); + this.log.trace(`CreateProfile(${managementWallet}, ${profileNodeId}, ${initialBalance}, ${isSender725})`); return this.transactions.queueTransaction( this.profileContractAbi, 'createProfile', [ + managementWallet, Utilities.normalizeHex(profileNodeId), initialBalance, isSender725, blockchainIdentity, ], options, @@ -1024,6 +1040,49 @@ class Ethereum { getTokenContractAddress() { return this.tokenContractAddress; } + + /** + * Returns purposes of the wallet. + * @param {string} - erc725Identity + * @param {string} - wallet + * @return {Promise<[]>} + */ + getWalletPurposes(erc725Identity, wallet) { + const erc725IdentityContract = new this.web3.eth.Contract( + this.erc725IdentityContractAbi, + erc725Identity, + ); + + const key = ethereumAbi.soliditySHA3(['address'], [wallet]).toString('hex'); + return erc725IdentityContract.methods.getKeyPurposes(Utilities.normalizeHex(key)).call(); + } + + /** + * Transfers identity to new address. + * @param {string} - erc725identity + */ + transferProfile(erc725identity) { + const options = { + gasLimit: this.web3.utils.toHex(this.config.gas_limit), + gasPrice: this.web3.utils.toHex(this.config.gas_price), + to: this.profileContractAddress, + }; + + this.log.trace(`transferProfile (${erc725identity})`); + return this.transactions.queueTransaction( + this.profileContractAbi, 'transferProfile', + [erc725identity], options, + ); + } + + /** + * Returns true if ERC725 contract is older version. + * @param {string} - address of ERC 725 identity. + * @return {Promise} + */ + async isErc725IdentityOld(address) { + return oldErc725codeMd5 === md5(await this.web3.eth.getCode(address)); + } } module.exports = Ethereum; diff --git a/modules/service/profile-service.js b/modules/service/profile-service.js index 63f00e075..23145a4b3 100644 --- a/modules/service/profile-service.js +++ b/modules/service/profile-service.js @@ -32,6 +32,23 @@ class ProfileService { if (identityExists && await this.isProfileCreated()) { this.logger.notify(`Profile has already been created for node ${this.config.identity}`); + let walletToCheck; + if (this.config.management_wallet) { + walletToCheck = this.config.management_wallet; + } else { + this.logger.important('Management wallet not set. Please set one.'); + walletToCheck = this.config.node_wallet; + } + + // Check financial wallet permissions. + const permissions = await this.blockchain.getWalletPurposes( + this.config.erc725Identity, + walletToCheck, + ); + + if (!permissions.includes('1')) { + throw Error(`Management wallet ${walletToCheck} doesn't have enough permissions.`); + } return; } @@ -42,10 +59,22 @@ class ProfileService { // set empty identity if there is none const identity = this.config.erc725Identity ? this.config.erc725Identity : new BN(0, 16); - await this.blockchain.createProfile( - this.config.identity, - new BN(profileMinStake, 10), identityExists, identity, - ); + + if (this.config.management_wallet) { + await this.blockchain.createProfile( + this.config.management_wallet, + this.config.identity, + new BN(profileMinStake, 10), identityExists, identity, + ); + } else { + this.logger.important('Management wallet not set. Creating profile with operating wallet only.' + + ' Please set management one.'); + await this.blockchain.createProfile( + this.config.node_wallet, + this.config.identity, + new BN(profileMinStake, 10), identityExists, identity, + ); + } if (!identityExists) { const event = await this.blockchain.subscribeToEvent('IdentityCreated', null, 5 * 60 * 1000, null, eventData => Utilities.compareHexStrings(eventData.profile, this.config.node_wallet)); if (event) { @@ -181,6 +210,33 @@ class ProfileService { }); this.logger.info(`Token withdrawal started for amount ${amount}.`); } + + /** + * Check for ERC725 identity version and executes upgrade of the profile. + * @return {Promise} + */ + async upgradeProfile() { + if (await this.blockchain.isErc725IdentityOld(this.config.erc725Identity)) { + this.logger.important('Old profile detected. Upgrading to new one.'); + try { + const result = await this.blockchain.transferProfile(this.config.erc725Identity); + const newErc725Identity = + Utilities.normalizeHex(result.logs[1].data.substr( + result.logs[1].data.length - 40, + 40, + )); + + this.logger.important('**************************************************************************'); + this.logger.important(`Your ERC725 profile has been upgraded and now has the new address: ${newErc725Identity}`); + this.logger.important('Please backup your ERC725 identity file.'); + this.logger.important('**************************************************************************'); + this.config.erc725Identity = newErc725Identity; + this._saveIdentity(newErc725Identity); + } catch (transferError) { + throw Error(`Failed to transfer profile. ${transferError}. ${transferError.stack}`); + } + } + } } module.exports = ProfileService; diff --git a/ot-node.js b/ot-node.js index a8690545c..59636f2ea 100644 --- a/ot-node.js +++ b/ot-node.js @@ -443,6 +443,7 @@ class OTNode { try { await profileService.initProfile(); + await profileService.upgradeProfile(); } catch (e) { log.error('Failed to create profile'); console.log(e); From a8e7f3c0f0185fc3c9ac3e957e8b3a5c3a84c7b9 Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Fri, 14 Dec 2018 23:59:09 +0100 Subject: [PATCH 10/48] One time payout migration (#806) * one-time migration * fix migration * fix ot-node --- modules/migration/m1-payout-all-migration.js | 50 ++++++++++++++++++++ ot-node.js | 43 ++++++++++++++++- 2 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 modules/migration/m1-payout-all-migration.js diff --git a/modules/migration/m1-payout-all-migration.js b/modules/migration/m1-payout-all-migration.js new file mode 100644 index 000000000..c225f465e --- /dev/null +++ b/modules/migration/m1-payout-all-migration.js @@ -0,0 +1,50 @@ +const models = require('../../models'); + +/** + * Runs all pending payout commands + */ +class M1PayoutAllMigration { + constructor(ctx) { + this.logger = ctx.logger; + this.payOutHandler = ctx.dhPayOutCommand; + } + + /** + * Run migration + */ + async run() { + /* get all pending payouts */ + const pendingPayOuts = await models.commands.findAll({ + where: { + status: 'PENDING', + name: 'dhPayOutCommand', + }, + }); + + for (const pendingPayOut of pendingPayOuts) { + const data = this.payOutHandler.unpack(pendingPayOut.data); + + let retries = 3; + while (retries > 0) { + try { + // eslint-disable-next-line + await this.payOutHandler.execute({ data }); // run payout + pendingPayOut.status = 'COMPLETED'; + pendingPayOut.save({ + fields: ['status'], + }); + break; + } catch (e) { + retries -= 1; + if (retries > 0) { + this.logger.error(`Failed to run payout migration. Retrying... ${e}`); + } else { + this.logger.error(`Failed to run payout migration. Stop retrying... ${e}`); + } + } + } + } + } +} + +module.exports = M1PayoutAllMigration; diff --git a/ot-node.js b/ot-node.js index 59636f2ea..abf811258 100644 --- a/ot-node.js +++ b/ot-node.js @@ -327,7 +327,7 @@ class OTNode { context = container.cradle; - container.loadModules(['modules/command/**/*.js', 'modules/controller/**/*.js', 'modules/service/**/*.js', 'modules/Blockchain/plugin/hyperledger/*.js'], { + container.loadModules(['modules/command/**/*.js', 'modules/controller/**/*.js', 'modules/service/**/*.js', 'modules/Blockchain/plugin/hyperledger/*.js', 'modules/migration/*.js'], { formatName: 'camelCase', resolverOptions: { lifetime: awilix.Lifetime.SINGLETON, @@ -476,6 +476,8 @@ class OTNode { await remoteControl.connect(); } + await this._runMigration(container); + const commandExecutor = container.resolve('commandExecutor'); await commandExecutor.init(); await commandExecutor.replay(); @@ -483,6 +485,43 @@ class OTNode { appState.started = true; } + /** + * Run one time migration + * Note: implement migration service + * @param container + * @deprecated + * @private + */ + async _runMigration(container) { + const config = container.resolve('config'); + + const migrationsStartedMills = Date.now(); + log.info('Initializing code migrations...'); + + const m1PayoutAllMigrationFilename = '0_m1PayoutAllMigrationFile'; + const migrationDir = path.join(config.appDataPath, 'migrations'); + const migrationFilePath = path.join(migrationDir, m1PayoutAllMigrationFilename); + if (!fs.existsSync(migrationFilePath)) { + const m1PayoutAllMigration = container.resolve('m1PayoutAllMigration'); + + try { + await m1PayoutAllMigration.run(); + log.warn(`One-time payout migration completed. Lasted ${Date.now() - migrationsStartedMills} millisecond(s)`); + + await Utilities.writeContentsToFile( + migrationDir, m1PayoutAllMigrationFilename, + JSON.stringify({ + status: 'COMPLETED', + }), + ); + } catch (e) { + log.error(`Failed to run code migrations. Lasted ${Date.now() - migrationsStartedMills} millisecond(s). ${e.message}`); + } + } + + log.info(`Code migrations completed. Lasted ${Date.now() - migrationsStartedMills}`); + } + /** * Starts bootstrap node * @return {Promise} @@ -492,7 +531,7 @@ class OTNode { injectionMode: awilix.InjectionMode.PROXY, }); - container.loadModules(['modules/Blockchain/plugin/hyperledger/*.js'], { + container.loadModules(['modules/Blockchain/plugin/hyperledger/*.js', 'modules/migration/*.js'], { formatName: 'camelCase', resolverOptions: { lifetime: awilix.Lifetime.SINGLETON, From bef536ecdf84e4c010e7a1521aeef41dbed58fc6 Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Fri, 14 Dec 2018 23:59:22 +0100 Subject: [PATCH 11/48] Disable deposit and withdrawal (#807) --- config/config.json | 10 +- .../Blockchain/Ethereum/test/profile.test.js | 2 +- modules/DVService.js | 3 +- modules/EventEmitter.js | 24 ----- .../command/common/deposit-tokens-command.js | 92 ----------------- .../profile-approval-increase-command.js | 66 ------------- .../common/token-withdrawal-command.js | 69 ------------- .../common/token-withdrawal-start-command.js | 58 ----------- .../token-withdrawal-wait-started-command.js | 99 ------------------- .../command/dc/dc-offer-finalize-command.js | 20 ++-- .../dc/dc-offer-mining-completed-command.js | 16 ++- modules/service/dc-service.js | 69 ++++--------- modules/service/dh-service.js | 65 +++--------- modules/service/profile-service.js | 48 +++------ modules/service/rest-api-service.js | 34 ------- test/bdd/features/endpoints.feature | 16 --- test/bdd/steps/endpoints.js | 38 ------- test/bdd/steps/lib/http-api-helper.js | 74 -------------- test/bdd/steps/network.js | 3 + test/protocol/protocol.test.js | 3 - 20 files changed, 68 insertions(+), 741 deletions(-) delete mode 100644 modules/command/common/deposit-tokens-command.js delete mode 100644 modules/command/common/profile-approval-increase-command.js delete mode 100644 modules/command/common/token-withdrawal-command.js delete mode 100644 modules/command/common/token-withdrawal-start-command.js delete mode 100644 modules/command/common/token-withdrawal-wait-started-command.js diff --git a/config/config.json b/config/config.json index f278fe963..9b13d82e5 100644 --- a/config/config.json +++ b/config/config.json @@ -53,7 +53,7 @@ "blockchain": { "blockchain_title": "Ethereum", "network_id": "rinkeby", - "gas_limit": "1500000", + "gas_limit": "2000000", "gas_price": "20000000000", "hub_contract_address": "0xa13635b8D91BCdEC59067eE2Da7A17292578bB08", "rpc_node_host": "https://rinkeby.infura.io/1WRiEqAQ9l4SW6fGdiDt", @@ -153,7 +153,7 @@ "blockchain": { "blockchain_title": "Ethereum", "network_id": "rinkeby", - "gas_limit": "1500000", + "gas_limit": "2000000", "gas_price": "20000000000", "hub_contract_address": "0x0A0253150F35D9a766e450D6F749fFFD2B21eEC6", "rpc_node_host": "https://rinkeby.infura.io/1WRiEqAQ9l4SW6fGdiDt", @@ -253,7 +253,7 @@ "blockchain": { "blockchain_title": "Ethereum", "network_id": "rinkeby", - "gas_limit": "1500000", + "gas_limit": "2000000", "gas_price": "20000000000", "hub_contract_address": "0x6C314872A7e97A6F1dC7De2dc43D28500dd36f22", "rpc_node_host": "https://rinkeby.infura.io/1WRiEqAQ9l4SW6fGdiDt", @@ -353,7 +353,7 @@ "blockchain": { "blockchain_title": "Ethereum", "network_id": "rinkeby", - "gas_limit": "1500000", + "gas_limit": "2000000", "gas_price": "20000000000", "hub_contract_address": "0xE2726Bc7c82d45601eF68DB9EED92af18D0C4E0f", "rpc_node_host": "https://rinkeby.infura.io/1WRiEqAQ9l4SW6fGdiDt", @@ -456,7 +456,7 @@ "blockchain": { "blockchain_title": "Ethereum", "network_id": "mainnet", - "gas_limit": "1500000", + "gas_limit": "2000000", "gas_price": "20000000000", "hub_contract_address": "0xa287d7134fb40bef071c932286bd2cd01efccf30", "rpc_node_host": "https://mainnet.infura.io/7c072f60df864d22884e705c5bf3d83f", diff --git a/modules/Blockchain/Ethereum/test/profile.test.js b/modules/Blockchain/Ethereum/test/profile.test.js index b90f1c1c5..b52a661a3 100644 --- a/modules/Blockchain/Ethereum/test/profile.test.js +++ b/modules/Blockchain/Ethereum/test/profile.test.js @@ -212,7 +212,7 @@ contract('Profile contract testing', async (accounts) => { }); // eslint-disable-next-line no-undef - it('Should deposit tokens to profile', async () => { + it.skip('Should deposit tokens to profile', async () => { // Get contracts used in hook const trac = await TracToken.deployed(); const profile = await Profile.deployed(); diff --git a/modules/DVService.js b/modules/DVService.js index fc395fafd..c878f6032 100644 --- a/modules/DVService.js +++ b/modules/DVService.js @@ -150,8 +150,7 @@ class DVService { .add(stakeAmount); if (profileBalance.lt(condition)) { - await this.blockchain.increaseBiddingApproval(condition.sub(profileBalance)); - await this.blockchain.depositTokens(condition.sub(profileBalance)); + throw new Error('Not enough funds to handle data read response'); } // Sign escrow. diff --git a/modules/EventEmitter.js b/modules/EventEmitter.js index 69aa39055..6989e5210 100644 --- a/modules/EventEmitter.js +++ b/modules/EventEmitter.js @@ -599,30 +599,6 @@ class EventEmitter { } }); - this._on('api-deposit-tokens', async (data) => { - const { trac_amount } = data; - - try { - logger.info(`Deposit ${trac_amount} TRAC to profile triggered`); - - await profileService.depositTokens(trac_amount); - remoteControl.tokenDepositSucceeded(`${trac_amount} TRAC deposited to your profile`); - - data.response.status(200); - data.response.send({ - message: `Successfully deposited ${trac_amount} TRAC to profile`, - }); - } catch (error) { - logger.error(`Failed to deposit tokens. ${error}.`); - notifyError(error); - data.response.status(400); - data.response.send({ - message: `Failed to deposit tokens. ${error}.`, - }); - remoteControl.tokensDepositFailed(`Failed to deposit tokens. ${error}.`); - } - }); - this._on('api-withdraw-tokens', async (data) => { const { trac_amount } = data; diff --git a/modules/command/common/deposit-tokens-command.js b/modules/command/common/deposit-tokens-command.js deleted file mode 100644 index 5294138e0..000000000 --- a/modules/command/common/deposit-tokens-command.js +++ /dev/null @@ -1,92 +0,0 @@ -const BN = require('../../../node_modules/bn.js/lib/bn'); - -const Command = require('../command'); -const Utilities = require('../../Utilities'); - -/** - * Deposits tokens on blockchain - */ -class DepositTokensCommand extends Command { - constructor(ctx) { - super(ctx); - this.web3 = ctx.web3; - this.logger = ctx.logger; - this.config = ctx.config; - this.blockchain = ctx.blockchain; - } - - /** - * Executes command and produces one or more events - * @param command - */ - async execute(command) { - const { amount } = command.data; - this.logger.notify(`Deposit amount: ${amount} mTRAC.`); - - const blockchainIdentity = Utilities.normalizeHex(this.config.erc725Identity); - await this._printBalances(blockchainIdentity, 'Old'); - await this.blockchain.depositTokens(blockchainIdentity, amount); - await this._printBalances(blockchainIdentity, 'New'); - return this.continueSequence(this.pack(command.data), command.sequence); - } - - /** - * Print balances - * @param blockchainIdentity - * @param timeFrame string to describe before or after withdrawal operation - * @return {Promise} - * @private - */ - async _printBalances(blockchainIdentity, timeFrame) { - const balance = await this.blockchain.getProfileBalance(this.config.node_wallet); - const balanceInTRAC = this.web3.utils.fromWei(balance, 'ether'); - this.logger.info(`${timeFrame} wallet balance: ${balanceInTRAC} TRAC`); - - const profile = await this.blockchain.getProfile(blockchainIdentity); - const profileBalance = profile.stake; - const profileBalanceInTRAC = this.web3.utils.fromWei(profileBalance, 'ether'); - this.logger.info(`${timeFrame} profile balance: ${profileBalanceInTRAC} TRAC`); - } - - /** - * Pack data for DB - * @param data - */ - pack(data) { - Object.assign(data, { - amount: data.amount.toString(), - }); - return data; - } - - /** - * Unpack data from database - * @param data - * @returns {Promise<*>} - */ - unpack(data) { - const parsed = data; - Object.assign(parsed, { - amount: new BN(data.amount, 10), - }); - return parsed; - } - - /** - * Builds default command - * @param map - * @returns {{add, data: *, delay: *, deadline: *}} - */ - default(map) { - const command = { - name: 'depositTokensCommand', - retries: 3, - delay: 0, - transactional: false, - }; - Object.assign(command, map); - return command; - } -} - -module.exports = DepositTokensCommand; diff --git a/modules/command/common/profile-approval-increase-command.js b/modules/command/common/profile-approval-increase-command.js deleted file mode 100644 index 4de3cca9c..000000000 --- a/modules/command/common/profile-approval-increase-command.js +++ /dev/null @@ -1,66 +0,0 @@ -const Command = require('../command'); -const BN = require('../../../node_modules/bn.js/lib/bn'); - -/** - * Increases approval for Profile contract on blockchain - */ -class ProfileApprovalIncreaseCommand extends Command { - constructor(ctx) { - super(ctx); - this.logger = ctx.logger; - this.blockchain = ctx.blockchain; - } - - /** - * Executes command and produces one or more events - * @param command - */ - async execute(command) { - const { amount } = command.data; - this.logger.notify(`Giving approval to profile contract for amount: ${amount} mTRAC.`); - - await this.blockchain.increaseProfileApproval(amount); - return this.continueSequence(this.pack(command.data), command.sequence); - } - - /** - * Pack data for DB - * @param data - */ - pack(data) { - Object.assign(data, { - amount: data.amount.toString(), - }); - return data; - } - - /** - * Unpack data from database - * @param data - * @returns {Promise<*>} - */ - unpack(data) { - const parsed = data; - Object.assign(parsed, { - amount: new BN(data.amount, 10), - }); - return parsed; - } - - /** - * Builds default command - * @param map - * @returns {{add, data: *, delay: *, deadline: *}} - */ - default(map) { - const command = { - name: 'profileApprovalIncreaseCommand', - delay: 0, - transactional: false, - }; - Object.assign(command, map); - return command; - } -} - -module.exports = ProfileApprovalIncreaseCommand; diff --git a/modules/command/common/token-withdrawal-command.js b/modules/command/common/token-withdrawal-command.js deleted file mode 100644 index d18dad3a4..000000000 --- a/modules/command/common/token-withdrawal-command.js +++ /dev/null @@ -1,69 +0,0 @@ -const Command = require('../command'); -const Utilities = require('../../Utilities'); - -/** - * Starts token withdrawal operation - */ -class TokenWithdrawalCommand extends Command { - constructor(ctx) { - super(ctx); - this.config = ctx.config; - this.logger = ctx.logger; - this.web3 = ctx.web3; - this.blockchain = ctx.blockchain; - this.remoteControl = ctx.remoteControl; - } - - /** - * Executes command and produces one or more events - * @param command - */ - async execute(command) { - const { - amount, - } = command.data; - - const blockchainIdentity = Utilities.normalizeHex(this.config.erc725Identity); - await this._printBalances(blockchainIdentity, 'Old'); - await this.blockchain.withdrawTokens(blockchainIdentity); - this.logger.important(`Token withdrawal for amount ${amount} completed.`); - await this._printBalances(blockchainIdentity, 'New'); - return Command.empty(); - } - - /** - * Print balances - * @param blockchainIdentity - * @param timeFrame string to describe before or after withdrawal operation - * @return {Promise} - * @private - */ - async _printBalances(blockchainIdentity, timeFrame) { - const balance = await this.blockchain.getProfileBalance(this.config.node_wallet); - const balanceInTRAC = this.web3.utils.fromWei(balance, 'ether'); - this.logger.info(`${timeFrame} wallet balance: ${balanceInTRAC} TRAC`); - - const profile = await this.blockchain.getProfile(blockchainIdentity); - const profileBalance = profile.stake; - const profileBalanceInTRAC = this.web3.utils.fromWei(profileBalance, 'ether'); - this.logger.info(`${timeFrame} profile balance: ${profileBalanceInTRAC} TRAC`); - } - - /** - * Builds default command - * @param map - * @returns {{add, data: *, delay: *, deadline: *}} - */ - default(map) { - const command = { - name: 'tokenWithdrawalCommand', - delay: 30000, - retries: 3, - transactional: false, - }; - Object.assign(command, map); - return command; - } -} - -module.exports = TokenWithdrawalCommand; diff --git a/modules/command/common/token-withdrawal-start-command.js b/modules/command/common/token-withdrawal-start-command.js deleted file mode 100644 index 48f49c5cd..000000000 --- a/modules/command/common/token-withdrawal-start-command.js +++ /dev/null @@ -1,58 +0,0 @@ -const BN = require('../../../node_modules/bn.js/lib/bn'); -const Command = require('../command'); -const Utilities = require('../../Utilities'); - -/** - * Starts token withdrawal operation - */ -class TokenWithdrawalStartCommand extends Command { - constructor(ctx) { - super(ctx); - this.config = ctx.config; - this.logger = ctx.logger; - this.blockchain = ctx.blockchain; - this.remoteControl = ctx.remoteControl; - } - - /** - * Executes command and produces one or more events - * @param command - */ - async execute(command) { - const { - amount, - } = command.data; - - await this.blockchain.startTokenWithdrawal( - Utilities.normalizeHex(this.config.erc725Identity), - new BN(amount, 10), - ); - return { - commands: [ - { - name: 'tokenWithdrawalWaitStartedCommand', - period: 5000, - deadline_at: Date.now() + (5 * 60 * 1000), - data: command.data, - }, - ], - }; - } - - /** - * Builds default command - * @param map - * @returns {{add, data: *, delay: *, deadline: *}} - */ - default(map) { - const command = { - name: 'tokenWithdrawalStartCommand', - delay: 0, - transactional: false, - }; - Object.assign(command, map); - return command; - } -} - -module.exports = TokenWithdrawalStartCommand; diff --git a/modules/command/common/token-withdrawal-wait-started-command.js b/modules/command/common/token-withdrawal-wait-started-command.js deleted file mode 100644 index f76bf3516..000000000 --- a/modules/command/common/token-withdrawal-wait-started-command.js +++ /dev/null @@ -1,99 +0,0 @@ -const BN = require('../../../node_modules/bn.js/lib/bn'); - -const Command = require('../command'); -const Models = require('../../../models/index'); - -/** - * Repeatable command that checks whether offer is ready or not - */ -class TokenWithdrawalWaitStartedCommand extends Command { - constructor(ctx) { - super(ctx); - this.logger = ctx.logger; - this.config = ctx.config; - } - - /** - * Executes command and produces one or more events - * @param command - */ - async execute(command) { - const { - amount, - } = command.data; - - const events = await Models.events.findAll({ - where: { - event: 'WithdrawalInitiated', - finished: 0, - }, - }); - if (events) { - const event = events.find((e) => { - const { - profile: eventProfile, - } = JSON.parse(e.data); - return eventProfile.toLowerCase() - .includes(this.config.erc725Identity.toLowerCase()); - }); - if (event) { - event.finished = true; - await event.save({ fields: ['finished'] }); - - const { - amount: eAmount, - withdrawalDelayInSeconds: eWithdrawalDelayInSeconds, - } = JSON.parse(event.data); - this.logger.important(`Token withdrawal for amount ${amount} initiated.`); - - const amountBN = new BN(amount, 10); - const eAmountBN = new BN(eAmount, 10); - if (!amountBN.eq(eAmountBN)) { - this.logger.warn(`Not enough tokens for withdrawal [${amount}]. All the tokens will be withdrawn [${eAmount}]`); - } - const { data } = command; - Object.assign(data, { - amount: eAmount, - }); - return { - commands: [ - { - name: 'tokenWithdrawalCommand', - delay: eWithdrawalDelayInSeconds * 1000, - data, - }, - ], - }; - } - } - return Command.repeat(); - } - - /** - * Execute strategy when event is too late - * @param command - */ - async expired(command) { - // TODO implement - return Command.empty(); - } - - /** - * Builds default AddCommand - * @param map - * @returns {{add, data: *, delay: *, deadline: *}} - */ - default(map) { - const command = { - name: 'tokenWithdrawalWaitStartedCommand', - delay: 0, - period: 5000, - deadline_at: Date.now() + (5 * 60 * 1000), - transactional: false, - }; - Object.assign(command, map); - return command; - } -} - -module.exports = TokenWithdrawalWaitStartedCommand; diff --git a/modules/command/dc/dc-offer-finalize-command.js b/modules/command/dc/dc-offer-finalize-command.js index ae60b6fba..c998b77ec 100644 --- a/modules/command/dc/dc-offer-finalize-command.js +++ b/modules/command/dc/dc-offer-finalize-command.js @@ -105,22 +105,16 @@ class DCOfferFinalizeCommand extends Command { }; } - const depositToken = await this.dcService.chainDepositCommandIfNeeded( - offer.token_amount_per_holder, - command.data, - ['dcOfferFinalizeCommand'], - ); - if (depositToken) { - this.logger.warn(`Failed to finalize offer ${offerId} because DC didn't have enough funds. Trying again...`); - return { - commands: [depositToken], - }; + let errorMessage = err.message; + const hasFunds = await this.dcService + .hasProfileBalanceForOffer(offer.token_amount_per_holder); + if (!hasFunds) { + errorMessage = 'Not enough tokens. To replicate data please deposit more tokens to your profile.'; } - - this.logger.error(`Offer ${offerId} has not been finalized.`); + this.logger.error(`Offer ${offerId} has not been finalized. ${errorMessage}`); offer.status = 'FAILED'; - offer.message = `Offer for ${offerId} has not been finalized. ${err.message}`; + offer.message = `Offer for ${offerId} has not been finalized. ${errorMessage}`; await offer.save({ fields: ['status', 'message'] }); await this.replicationService.cleanup(offer.id); diff --git a/modules/command/dc/dc-offer-mining-completed-command.js b/modules/command/dc/dc-offer-mining-completed-command.js index 5fb9fad43..794d59855 100644 --- a/modules/command/dc/dc-offer-mining-completed-command.js +++ b/modules/command/dc/dc-offer-mining-completed-command.js @@ -59,18 +59,14 @@ class DcOfferMiningCompletedCommand extends Command { offer.message = 'Found a solution for DHs provided'; await offer.save({ fields: ['status', 'message'] }); + const hasFunds = await this.dcService + .hasProfileBalanceForOffer(offer.token_amount_per_holder); + if (!hasFunds) { + throw new Error('Not enough tokens. To replicate data please deposit more tokens to your profile'); + } + const commandData = { offerId, solution }; const commandSequence = ['dcOfferFinalizeCommand']; - const depositCommand = await this.dcService.chainDepositCommandIfNeeded( - offer.token_amount_per_holder, - commandData, - commandSequence, - ); - if (depositCommand) { - return { - commands: [depositCommand], - }; - } return { commands: [ { diff --git a/modules/service/dc-service.js b/modules/service/dc-service.js index 6da349ec2..557fc1f70 100644 --- a/modules/service/dc-service.js +++ b/modules/service/dc-service.js @@ -4,7 +4,7 @@ const Encryption = require('../Encryption'); const models = require('../../models'); -const DEFAILT_NUMBER_OF_HOLDERS = 3; +const DEFAULT_NUMBER_OF_HOLDERS = 3; class DCService { constructor(ctx) { @@ -31,6 +31,13 @@ class DCService { dataSetId, dataRootHash, holdingTimeInMinutes, tokenAmountPerHolder, dataSizeInBytes, litigationIntervalInMinutes, ) { + const hasFunds = await this.hasProfileBalanceForOffer(tokenAmountPerHolder); + if (!hasFunds) { + const message = 'Not enough tokens. To replicate data please deposit more tokens to your profile.'; + this.logger.warn(message); + throw new Error(message); + } + const offer = await models.offers.create({ data_set_id: dataSetId, message: 'Offer is pending', @@ -64,23 +71,14 @@ class DCService { 'dcOfferCreateBcCommand', 'dcOfferTaskCommand', 'dcOfferChooseCommand']; - const depositCommand = await this.chainDepositCommandIfNeeded( - tokenAmountPerHolder, - commandData, - commandSequence, - ); - if (depositCommand) { - await this.commandExecutor.add(depositCommand); - } else { - await this.commandExecutor.add({ - name: commandSequence[0], - sequence: commandSequence.slice(1), - delay: 0, - data: commandData, - transactional: false, - }); - } + await this.commandExecutor.add({ + name: commandSequence[0], + sequence: commandSequence.slice(1), + delay: 0, + data: commandData, + transactional: false, + }); return offer.id; } @@ -118,19 +116,17 @@ class DCService { } /** - * Creates commands needed for token deposit if there is a need for that - * @param tokenAmountPerHolder - * @param commandData - * @param commandSequence + * Has enough balance on profile for creating an offer + * @param tokenAmountPerHolder - Tokens per DH * @return {Promise<*>} */ - async chainDepositCommandIfNeeded(tokenAmountPerHolder, commandData, commandSequence) { + async hasProfileBalanceForOffer(tokenAmountPerHolder) { const profile = await this.blockchain.getProfile(this.config.erc725Identity); const profileStake = new BN(profile.stake, 10); const profileStakeReserved = new BN(profile.stakeReserved, 10); const offerStake = new BN(tokenAmountPerHolder, 10) - .mul(new BN(DEFAILT_NUMBER_OF_HOLDERS, 10)); + .mul(new BN(DEFAULT_NUMBER_OF_HOLDERS, 10)); let remainder = null; if (profileStake.sub(profileStakeReserved).lt(offerStake)) { @@ -144,32 +140,7 @@ class DCService { remainder = stakeRemainder; } } - - let depositCommand = null; - if (remainder) { - if (!this.config.deposit_on_demand) { - const message = 'Not enough tokens. Deposit on demand feature is disabled. Please, enable it in your configuration.'; - this.logger.warn(message); - throw new Error(message); - } - - // deposit tokens - depositCommand = { - name: 'profileApprovalIncreaseCommand', - sequence: [ - 'depositTokensCommand', - ], - delay: 0, - data: { - amount: remainder.toString(), - }, - transactional: false, - }; - - Object.assign(depositCommand.data, commandData); - depositCommand.sequence = depositCommand.sequence.concat(commandSequence); - } - return depositCommand; + return !remainder; } /** diff --git a/modules/service/dh-service.js b/modules/service/dh-service.js index b7c00ff6b..a63035749 100644 --- a/modules/service/dh-service.js +++ b/modules/service/dh-service.js @@ -155,6 +155,10 @@ class DHService { tokenAmountPerHolder, ); + if (remainder) { + throw new Error('Not enough tokens. To take additional jobs please complete any finished jobs or deposit more tokens to your profile.'); + } + const data = { offerId, dcNodeId, @@ -164,34 +168,12 @@ class DHService { tokenAmountPerHolder, }; - if (remainder) { - if (!this.config.deposit_on_demand) { - throw new Error('Not enough tokens. Deposit on demand feature is disabled. Please, enable it in your configuration.'); - } - - bid.deposit = remainder.toString(); - await bid.save({ fields: ['deposit'] }); - - this.logger.warn(`Not enough tokens for offer ${offerId}. Minimum amount of tokens will be deposited automatically.`); - - Object.assign(data, { - amount: remainder.toString(), - }); - await this.commandExecutor.add({ - name: 'profileApprovalIncreaseCommand', - sequence: ['depositTokensCommand', 'dhOfferHandleCommand'], - delay: 15000, - data, - transactional: false, - }); - } else { - await this.commandExecutor.add({ - name: 'dhOfferHandleCommand', - delay: 15000, - data, - transactional: false, - }); - } + await this.commandExecutor.add({ + name: 'dhOfferHandleCommand', + delay: 15000, + data, + transactional: false, + }); } /** @@ -246,33 +228,9 @@ class DHService { remainder = stakeRemainder; } } - - if (remainder) { - const depositSum = remainder.add(currentDeposits); - const canDeposit = await this._canDeposit(depositSum); - if (!canDeposit) { - throw new Error('Not enough tokens. Insufficient funds.'); - } - } return remainder; } - /** - * Can deposit or not? - * @param amount {BN} amount to be deposited in mTRAC - * @return {Promise} - * @private - */ - async _canDeposit(amount) { - const walletBalance = await Utilities.getTracTokenBalance( - this.web3, - this.config.node_wallet, - this.blockchain.getTokenContractAddress(), - ); - const walletBalanceBN = new BN(this.web3.utils.toWei(parseFloat(walletBalance).toString(), 'ether'), 10); - return amount.lt(walletBalanceBN); - } - /** * Handle one read request (checks whether node satisfies query) * @param msgId - Message ID @@ -385,8 +343,7 @@ class DHService { new BN((await this.blockchain.getProfile(this.config.node_wallet)).balance, 10); if (profileBalance.lt(condition)) { - await this.blockchain.increaseBiddingApproval(condition.sub(profileBalance)); - await this.blockchain.depositTokens(condition.sub(profileBalance)); + throw new Error('Not enough funds to handle data read request'); } /* diff --git a/modules/service/profile-service.js b/modules/service/profile-service.js index 23145a4b3..a04e0ddba 100644 --- a/modules/service/profile-service.js +++ b/modules/service/profile-service.js @@ -55,7 +55,14 @@ class ProfileService { const profileMinStake = await this.blockchain.getProfileMinimumStake(); this.logger.info(`Minimum stake for profile registration is ${profileMinStake}`); - await this.blockchain.increaseProfileApproval(new BN(profileMinStake, 10)); + let initialTokenAmount = null; + if (this.config.initial_deposit_amount) { + initialTokenAmount = new BN(this.config.initial_deposit_amount, 10); + } else { + initialTokenAmount = new BN(profileMinStake, 10); + } + + await this.blockchain.increaseProfileApproval(initialTokenAmount); // set empty identity if there is none const identity = this.config.erc725Identity ? this.config.erc725Identity : new BN(0, 16); @@ -64,7 +71,7 @@ class ProfileService { await this.blockchain.createProfile( this.config.management_wallet, this.config.identity, - new BN(profileMinStake, 10), identityExists, identity, + initialTokenAmount, identityExists, identity, ); } else { this.logger.important('Management wallet not set. Creating profile with operating wallet only.' + @@ -72,7 +79,7 @@ class ProfileService { await this.blockchain.createProfile( this.config.node_wallet, this.config.identity, - new BN(profileMinStake, 10), identityExists, identity, + initialTokenAmount, identityExists, identity, ); } if (!identityExists) { @@ -167,48 +174,21 @@ class ProfileService { /** * Deposit token to profile * @param amount + * @deprecated * @returns {Promise} */ async depositTokens(amount) { - const walletBalance = await Utilities.getTracTokenBalance( - this.web3, - this.config.node_wallet, - this.blockchain.getTokenContractAddress(), - ); - - if (amount > parseFloat(walletBalance)) { - throw new Error(`Wallet balance: ${walletBalance} TRAC`); - } - - const mTRAC = this.web3.utils.toWei(amount.toString(), 'ether'); - await this.commandExecutor.add({ - name: 'profileApprovalIncreaseCommand', - sequence: [ - 'depositTokensCommand', - ], - delay: 0, - data: { - amount: mTRAC, - }, - transactional: false, - }); - this.logger.notify(`Deposit for amount ${amount} initiated.`); + throw new Error('OT Node does not support deposit functionality anymore'); } /** * Withdraw tokens from profile to identity * @param amount + * @deprecated * @return {Promise} */ async withdrawTokens(amount) { - const mTRAC = this.web3.utils.toWei(amount.toString(), 'ether'); - await this.commandExecutor.add({ - name: 'tokenWithdrawalStartCommand', - data: { - amount: mTRAC, - }, - }); - this.logger.info(`Token withdrawal started for amount ${amount}.`); + throw new Error('OT Node does not support withdrawal functionality anymore'); } /** diff --git a/modules/service/rest-api-service.js b/modules/service/rest-api-service.js index 1f90f7237..e194a56cf 100644 --- a/modules/service/rest-api-service.js +++ b/modules/service/rest-api-service.js @@ -400,40 +400,6 @@ class RestAPIService { }); }); - - server.post('/api/deposit', (req, res) => { - this.logger.api('POST: Deposit tokens request received.'); - - if (req.body !== null && typeof req.body.trac_amount === 'number' - && req.body.trac_amount > 0) { - const { trac_amount } = req.body; - emitter.emit('api-deposit-tokens', { - trac_amount, - response: res, - }); - } else { - res.status(400); - res.send({ message: 'Bad request' }); - } - }); - - - server.post('/api/withdraw', (req, res) => { - this.logger.api('POST: Withdraw tokens request received.'); - - if (req.body !== null && typeof req.body.trac_amount === 'number' - && req.body.trac_amount > 0) { - const { trac_amount } = req.body; - emitter.emit('api-withdraw-tokens', { - trac_amount, - response: res, - }); - } else { - res.status(400); - res.send({ message: 'Bad request' }); - } - }); - server.get('/api/import_info', async (req, res) => { await importController.dataSetInfo(req, res); }); diff --git a/test/bdd/features/endpoints.feature b/test/bdd/features/endpoints.feature index 89f1d2834..6d3840cc4 100644 --- a/test/bdd/features/endpoints.feature +++ b/test/bdd/features/endpoints.feature @@ -3,22 +3,6 @@ Feature: API endpoints features Given the blockchain is set up And 1 bootstrap is running - @first - Scenario: Smoke check /api/withdraw endpoint - Given I setup 1 node - And I start the node - And I use 1st node as DC - 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 - - @first - Scenario: Smoke check /api/deposit endpoint - Given I setup 1 node - And I start the node - And I use 1st node as DC - 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 - @first Scenario: Smoke check /api/consensus endpoint Given I setup 1 node diff --git a/test/bdd/steps/endpoints.js b/test/bdd/steps/endpoints.js index 6f03cc6bd..eaf481755 100644 --- a/test/bdd/steps/endpoints.js +++ b/test/bdd/steps/endpoints.js @@ -179,44 +179,6 @@ Given(/^the ([DV|DV2]+) purchases (last import|second last import) from the last .catch(error => done(error)); }); -Given(/^I attempt to withdraw (\d+) tokens from DC profile[s]*$/, { timeout: 420000 }, async function (tokenCount) { - // TODO expect tokenCount < profileBalance - expect(!!this.state.dc, 'DC node not defined. Use other step to define it.').to.be.equal(true); - - const { dc } = this.state; - const host = dc.state.node_rpc_url; - - const promises = []; - promises.push(new Promise((accept, reject) => { - dc.once('withdraw-initiated', () => accept()); - })); - promises.push(new Promise((accept, reject) => { - dc.once('withdraw-completed', () => accept()); - })); - promises.push(new Promise((accept, reject) => { - dc.once('withdraw-command-completed', () => accept()); - })); - await httpApiHelper.apiWithdraw(host, tokenCount); - return Promise.all(promises); -}); - -Given(/^I attempt to deposit (\d+) tokens from DC wallet[s]*$/, { timeout: 120000 }, async function (tokenCount) { - // TODO expect tokenCount < walletBalance - expect(!!this.state.dc, 'DC node not defined. Use other step to define it.').to.be.equal(true); - const { dc } = this.state; - const host = dc.state.node_rpc_url; - - const promises = []; - promises.push(new Promise((accept, reject) => { - dc.once('deposit-approved', () => accept()); - })); - promises.push(new Promise((accept, reject) => { - dc.once('deposit-command-completed', () => accept()); - })); - await httpApiHelper.apiDeposit(host, tokenCount); - return Promise.all(promises); -}); - Given(/^([DC|DH|DV]+) calls consensus endpoint for sender: "(\S+)"$/, async function (nodeType, senderId) { expect(nodeType, 'Node type can only be DC, DH, DV.').to.be.oneOf(['DC', 'DH', 'DV']); diff --git a/test/bdd/steps/lib/http-api-helper.js b/test/bdd/steps/lib/http-api-helper.js index b9a6cca97..3fe7b499e 100644 --- a/test/bdd/steps/lib/http-api-helper.js +++ b/test/bdd/steps/lib/http-api-helper.js @@ -366,78 +366,6 @@ async function apiReadNetwork(nodeRpcUrl, queryId, replyId, dataSetId) { }); } -/** - * @typedef {Object} WithdrawResponse - * @property {string} message informing that withdraw process was initiated. - */ - -/** - * Fetch /api/withdraw response - * - * @param {string} nodeRpcUrl URL in following format http://host:port - * @param {number} tokenCount - * @return {Promise.} - */ -async function apiWithdraw(nodeRpcUrl, tokenCount) { - return new Promise((accept, reject) => { - const jsonQuery = { - trac_amount: tokenCount, - }; - request( - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - uri: `${nodeRpcUrl}/api/withdraw`, - json: true, - body: jsonQuery, - }, - (err, res, body) => { - if (err) { - reject(err); - return; - } - accept(body); - }, - ); - }); -} - -/** - * @typedef {Object} DepositResponse - * @property {string} message informing that deposit process went fine. - */ - -/** - * Fetch /api/deposit response - * - * @param {string} nodeRpcUrl URL in following format http://host:port - * @param {number} tokenCount - * @return {Promise.} - */ -async function apiDeposit(nodeRpcUrl, tokenCount) { - return new Promise((accept, reject) => { - const jsonQuery = { - trac_amount: tokenCount, - }; - request( - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - uri: `${nodeRpcUrl}/api/deposit`, - json: true, - body: jsonQuery, - }, - (err, res, body) => { - if (err) { - reject(err); - return; - } - accept(body); - }, - ); - }); -} - /** * @typedef {Object} ConsensusResponse * @property {Object} events an array of events with side1 and/or side2 objects. @@ -546,8 +474,6 @@ module.exports = { apiQueryNetwork, apiQueryNetworkResponses, apiReadNetwork, - apiWithdraw, - apiDeposit, apiConsensus, apiTrail, apiNodeInfo, diff --git a/test/bdd/steps/network.js b/test/bdd/steps/network.js index 9ab5f3372..b6d928a31 100644 --- a/test/bdd/steps/network.js +++ b/test/bdd/steps/network.js @@ -83,6 +83,7 @@ Given(/^(\d+) bootstrap is running$/, { timeout: 80000 }, function (nodeCount, d bootstraps: ['https://localhost:5278/#ff62cb1f692431d901833d55b93c7d991b4087f1'], remoteWhitelist: ['localhost', '127.0.0.1'], }, + initial_deposit_amount: '10000000000000000000000', }, appDataBaseDir: this.parameters.appDataBaseDir, @@ -121,6 +122,7 @@ Given(/^I setup (\d+) node[s]*$/, { timeout: 120000 }, function (nodeCount, done }, local_network_only: true, dc_choose_time: 60000, // 1 minute + initial_deposit_amount: '10000000000000000000000', }; const newNode = new OtNode({ @@ -534,6 +536,7 @@ Given(/^I additionally setup (\d+) node[s]*$/, { timeout: 60000 }, function (nod rpc_node_port: 7545, }, local_network_only: true, + initial_deposit_amount: '10000000000000000000000', }, appDataBaseDir: this.parameters.appDataBaseDir, }); diff --git a/test/protocol/protocol.test.js b/test/protocol/protocol.test.js index 95f8d2ffa..3d84b311c 100644 --- a/test/protocol/protocol.test.js +++ b/test/protocol/protocol.test.js @@ -26,8 +26,6 @@ const sequelizeConfig = require('./../../config/sequelizeConfig').development; const CommandResolver = require('../../modules/command/command-resolver'); const CommandExecutor = require('../../modules/command/command-executor'); -const DepositTokenCommand = require('../../modules/command/common/deposit-tokens-command'); - const DCService = require('../../modules/service/dc-service'); // Thanks solc. At least this works! @@ -451,7 +449,6 @@ describe.skip('Protocol tests', () => { remoteControl: awilix.asClass(MockRemoteControl), commandExecutor: awilix.asClass(CommandExecutor).singleton(), commandResolver: awilix.asClass(CommandResolver).singleton(), - depositTokenCommand: awilix.asClass(DepositTokenCommand).singleton(), dcService: awilix.asClass(DCService).singleton(), notifyError: awilix.asFunction(() => (error) => { throw error; }), }); From 0b72e612fbb7a26453ec4e0e420c14cbc5fec1a4 Mon Sep 17 00:00:00 2001 From: Uros Kukic Date: Sat, 15 Dec 2018 00:39:51 +0100 Subject: [PATCH 12/48] Adjust gas sent when deploying contracts in integration tests --- test/bdd/steps/lib/local-blockchain.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bdd/steps/lib/local-blockchain.js b/test/bdd/steps/lib/local-blockchain.js index 2074ed302..054e6af3d 100644 --- a/test/bdd/steps/lib/local-blockchain.js +++ b/test/bdd/steps/lib/local-blockchain.js @@ -302,7 +302,7 @@ class LocalBlockchain { data: contractData, arguments: constructorArguments, }) - .send({ from: deployerAddress, gas: 6000000 }) + .send({ from: deployerAddress, gas: 6900000 }) .on('receipt', (receipt) => { deploymentReceipt = receipt; }) From 47c4ae520357f82431f96367b101fcb051ea007a Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Sat, 15 Dec 2018 00:54:34 +0100 Subject: [PATCH 13/48] Increase block gas limit to 7M --- test/bdd/steps/lib/local-blockchain.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/bdd/steps/lib/local-blockchain.js b/test/bdd/steps/lib/local-blockchain.js index 054e6af3d..b9cf6d96d 100644 --- a/test/bdd/steps/lib/local-blockchain.js +++ b/test/bdd/steps/lib/local-blockchain.js @@ -99,6 +99,7 @@ class LocalBlockchain { constructor(options = {}) { this.logger = options.logger || console; this.server = Ganache.server({ + gasLimit: 7000000, accounts: accountPrivateKeys.map(account => ({ secretKey: `0x${account}`, From dccefc634635a77bde3fc04b69c58b0e5ff0394a Mon Sep 17 00:00:00 2001 From: Vuk Djoric Date: Sat, 15 Dec 2018 00:58:14 +0100 Subject: [PATCH 14/48] Funds-check cleanup (#808) --- modules/Blockchain.js | 4 ---- modules/Blockchain/Ethereum/index.js | 33 ---------------------------- modules/EventEmitter.js | 10 --------- ot-node.js | 29 ------------------------ 4 files changed, 76 deletions(-) diff --git a/modules/Blockchain.js b/modules/Blockchain.js index e9a28f69f..c42c10865 100644 --- a/modules/Blockchain.js +++ b/modules/Blockchain.js @@ -430,10 +430,6 @@ class Blockchain { return this.blockchain.nodeHasApproval(nodeId); } - async hasEnoughFunds() { - return this.blockchain.hasEnoughFunds(); - } - /** * Token contract address getter * @return {any|*} diff --git a/modules/Blockchain/Ethereum/index.js b/modules/Blockchain/Ethereum/index.js index c7c59c5af..4311714f4 100644 --- a/modules/Blockchain/Ethereum/index.js +++ b/modules/Blockchain/Ethereum/index.js @@ -1000,39 +1000,6 @@ class Ethereum { return this.approvalContract.methods.nodeHasApproval(nodeId).call(); } - /** - * Check balances - * @returns {Promise} - */ - async hasEnoughFunds() { - this.log.trace('Checking balances'); - let enoughETH = true; - let enoughTRAC = true; - try { - const etherBalance = await Utilities.getBalanceInEthers( - this.web3, - this.config.wallet_address, - ); - this.log.info(`Balance of ETH: ${etherBalance}`); - if (etherBalance < 0.01) { - enoughETH = false; - } - - const tracBalance = await Utilities.getTracTokenBalance( - this.web3, - this.config.wallet_address, - this.tokenContractAddress, - ); - this.log.info(`Balance of TRAC: ${tracBalance}`); - if (tracBalance < 100) { - enoughTRAC = false; - } - } catch (error) { - throw new Error(error); - } - return enoughETH && enoughTRAC; - } - /** * Token contract address getter * @return {any|*} diff --git a/modules/EventEmitter.js b/modules/EventEmitter.js index 6989e5210..f95a848aa 100644 --- a/modules/EventEmitter.js +++ b/modules/EventEmitter.js @@ -502,13 +502,6 @@ class EventEmitter { }); this._on('api-create-offer', async (data) => { - if (!appState.enoughFunds) { - data.response.status(400); - data.response.send({ - message: 'Insufficient funds', - }); - return; - } const { dataSetId, holdingTimeInMinutes, @@ -665,9 +658,6 @@ class EventEmitter { }); this._on('eth-OfferCreated', async (eventData) => { - if (!appState.enoughFunds) { - return; - } let { dcNodeId, } = eventData; diff --git a/ot-node.js b/ot-node.js index abf811258..3e4c9cc45 100644 --- a/ot-node.js +++ b/ot-node.js @@ -379,35 +379,6 @@ class OTNode { emitter.initialize(); - // check does node_wallet has sufficient funds - 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); - } - }, 1800000); - // Connecting to graph database const graphStorage = container.resolve('graphStorage'); try { From 07b54b630bbed1b42b64a302920428549b664198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20Obradovi=C4=87?= Date: Sat, 15 Dec 2018 01:31:11 +0100 Subject: [PATCH 15/48] Update ERC725 identity to support management wallet (#809) Update ERC725 identity to support new management wallets. Add hotfix to upgrade old identities. Disable deposit and withdrawal. --- config/config.json | 10 +- modules/Blockchain.js | 64 ++- modules/Blockchain/Ethereum/abi/profile.json | 374 ++++++++++-------- .../Blockchain/Ethereum/contracts/ByteArr.sol | 2 + .../Ethereum/contracts/Identity.sol | 46 ++- .../Blockchain/Ethereum/contracts/Profile.sol | 35 +- modules/Blockchain/Ethereum/index.js | 94 +++-- .../Blockchain/Ethereum/test/offer.test.js | 1 + .../Blockchain/Ethereum/test/profile.test.js | 6 +- modules/DVService.js | 3 +- modules/EventEmitter.js | 34 -- .../command/common/deposit-tokens-command.js | 92 ----- .../profile-approval-increase-command.js | 66 ---- .../common/token-withdrawal-command.js | 69 ---- .../common/token-withdrawal-start-command.js | 58 --- .../token-withdrawal-wait-started-command.js | 99 ----- .../command/dc/dc-offer-finalize-command.js | 20 +- .../dc/dc-offer-mining-completed-command.js | 16 +- modules/migration/m1-payout-all-migration.js | 50 +++ modules/service/dc-service.js | 69 +--- modules/service/dh-service.js | 65 +-- modules/service/profile-service.js | 108 +++-- modules/service/rest-api-service.js | 34 -- ot-node.js | 73 ++-- test/bdd/features/endpoints.feature | 16 - test/bdd/steps/endpoints.js | 38 -- test/bdd/steps/lib/http-api-helper.js | 74 ---- test/bdd/steps/lib/local-blockchain.js | 3 +- test/bdd/steps/network.js | 3 + test/protocol/protocol.test.js | 3 - 30 files changed, 615 insertions(+), 1010 deletions(-) delete mode 100644 modules/command/common/deposit-tokens-command.js delete mode 100644 modules/command/common/profile-approval-increase-command.js delete mode 100644 modules/command/common/token-withdrawal-command.js delete mode 100644 modules/command/common/token-withdrawal-start-command.js delete mode 100644 modules/command/common/token-withdrawal-wait-started-command.js create mode 100644 modules/migration/m1-payout-all-migration.js diff --git a/config/config.json b/config/config.json index f278fe963..9b13d82e5 100644 --- a/config/config.json +++ b/config/config.json @@ -53,7 +53,7 @@ "blockchain": { "blockchain_title": "Ethereum", "network_id": "rinkeby", - "gas_limit": "1500000", + "gas_limit": "2000000", "gas_price": "20000000000", "hub_contract_address": "0xa13635b8D91BCdEC59067eE2Da7A17292578bB08", "rpc_node_host": "https://rinkeby.infura.io/1WRiEqAQ9l4SW6fGdiDt", @@ -153,7 +153,7 @@ "blockchain": { "blockchain_title": "Ethereum", "network_id": "rinkeby", - "gas_limit": "1500000", + "gas_limit": "2000000", "gas_price": "20000000000", "hub_contract_address": "0x0A0253150F35D9a766e450D6F749fFFD2B21eEC6", "rpc_node_host": "https://rinkeby.infura.io/1WRiEqAQ9l4SW6fGdiDt", @@ -253,7 +253,7 @@ "blockchain": { "blockchain_title": "Ethereum", "network_id": "rinkeby", - "gas_limit": "1500000", + "gas_limit": "2000000", "gas_price": "20000000000", "hub_contract_address": "0x6C314872A7e97A6F1dC7De2dc43D28500dd36f22", "rpc_node_host": "https://rinkeby.infura.io/1WRiEqAQ9l4SW6fGdiDt", @@ -353,7 +353,7 @@ "blockchain": { "blockchain_title": "Ethereum", "network_id": "rinkeby", - "gas_limit": "1500000", + "gas_limit": "2000000", "gas_price": "20000000000", "hub_contract_address": "0xE2726Bc7c82d45601eF68DB9EED92af18D0C4E0f", "rpc_node_host": "https://rinkeby.infura.io/1WRiEqAQ9l4SW6fGdiDt", @@ -456,7 +456,7 @@ "blockchain": { "blockchain_title": "Ethereum", "network_id": "mainnet", - "gas_limit": "1500000", + "gas_limit": "2000000", "gas_price": "20000000000", "hub_contract_address": "0xa287d7134fb40bef071c932286bd2cd01efccf30", "rpc_node_host": "https://mainnet.infura.io/7c072f60df864d22884e705c5bf3d83f", diff --git a/modules/Blockchain.js b/modules/Blockchain.js index 9525e1c89..c42c10865 100644 --- a/modules/Blockchain.js +++ b/modules/Blockchain.js @@ -61,14 +61,22 @@ class Blockchain { /** * Creates node profile on the Bidding contract + * @param managementWallet - Management wallet * @param profileNodeId - Network node ID * @param initialBalance - Initial profile balance * @param isSender725 - Is sender ERC 725? * @param blockchainIdentity - ERC 725 identity (empty if there is none) * @return {Promise} */ - createProfile(profileNodeId, initialBalance, isSender725, blockchainIdentity) { + createProfile( + managementWallet, + profileNodeId, + initialBalance, + isSender725, + blockchainIdentity, + ) { return this.blockchain.createProfile( + managementWallet, profileNodeId, initialBalance, isSender725, blockchainIdentity, ); @@ -204,13 +212,13 @@ class Blockchain { } /** - * Subscribe to a particular event - * @param event - * @param importId - * @param endMs - * @param endCallback - * @param filterFn - */ + * Subscribe to a particular event + * @param event + * @param importId + * @param endMs + * @param endCallback + * @param filterFn + */ subscribeToEvent(event, importId, endMs = 5 * 60 * 1000, endCallback, filterFn) { return this.blockchain .subscribeToEvent(event, importId, endMs, endCallback, filterFn); @@ -282,10 +290,10 @@ class Blockchain { } /** - * Gets status of the offer - * @param importId - * @return {Promise} - */ + * Gets status of the offer + * @param importId + * @return {Promise} + */ getOfferStatus(importId) { return this.blockchain.getOfferStatus(importId); } @@ -345,6 +353,7 @@ class Blockchain { async confirmPurchase(importId, dhWallet) { return this.blockchain.confirmPurchase(importId, dhWallet); } + async cancelPurchase(importId, correspondentWallet, senderIsDh) { return this.blockchain.cancelPurchase(importId, correspondentWallet, senderIsDh); } @@ -421,10 +430,6 @@ class Blockchain { return this.blockchain.nodeHasApproval(nodeId); } - async hasEnoughFunds() { - return this.blockchain.hasEnoughFunds(); - } - /** * Token contract address getter * @return {any|*} @@ -432,6 +437,33 @@ class Blockchain { getTokenContractAddress() { return this.blockchain.getTokenContractAddress(); } + + /** + * Returns purposes of the wallet. + * @param {string} - erc725Identity + * @param {string} - wallet + * @return {Promise<[]>} + */ + getWalletPurposes(erc725Identity, wallet) { + return this.blockchain.getWalletPurposes(erc725Identity, wallet); + } + + /** + * Transfers identity to new address. + * @param {string} - erc725identity + */ + transferProfile(erc725identity) { + return this.blockchain.transferProfile(erc725identity); + } + + /** + * Returns true if ERC725 contract is older version. + * @param {string} - address of ERC 725 identity. + * @return {Promise} + */ + async isErc725IdentityOld(address) { + return this.blockchain.isErc725IdentityOld(address); + } } module.exports = Blockchain; diff --git a/modules/Blockchain/Ethereum/abi/profile.json b/modules/Blockchain/Ethereum/abi/profile.json index 66478bcbe..d7737639e 100644 --- a/modules/Blockchain/Ethereum/abi/profile.json +++ b/modules/Blockchain/Ethereum/abi/profile.json @@ -3,23 +3,9 @@ "constant": false, "inputs": [ { - "name": "identity", + "name": "managementWallet", "type": "address" }, - { - "name": "amount", - "type": "uint256" - } - ], - "name": "startTokenWithdrawal", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ { "name": "profileNodeId", "type": "bytes32" @@ -43,62 +29,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "constant": true, - "inputs": [], - "name": "hub", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "withdrawalTime", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newMinimalStake", - "type": "uint256" - } - ], - "name": "setMinimalStake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "identity", - "type": "address" - } - ], - "name": "withdrawTokens", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, { "constant": false, "inputs": [ @@ -121,43 +51,7 @@ "constant": false, "inputs": [ { - "name": "identity", - "type": "address" - }, - { - "name": "newNodeId", - "type": "bytes32" - } - ], - "name": "setNodeId", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "minimalStake", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "sender", - "type": "address" - }, - { - "name": "receiver", + "name": "profile", "type": "address" }, { @@ -165,21 +59,7 @@ "type": "uint256" } ], - "name": "transferTokens", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newWithdrawalTime", - "type": "uint256" - } - ], - "name": "setWithdrawalTime", + "name": "releaseTokens", "outputs": [], "payable": false, "stateMutability": "nonpayable", @@ -219,44 +99,47 @@ "constant": false, "inputs": [ { - "name": "profile", - "type": "address" - }, - { - "name": "amount", + "name": "newMinimalStake", "type": "uint256" } ], - "name": "releaseTokens", + "name": "setMinimalStake", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { - "constant": true, - "inputs": [], - "name": "profileStorage", - "outputs": [ + "constant": false, + "inputs": [ { - "name": "", + "name": "identity", "type": "address" + }, + { + "name": "newNodeId", + "type": "bytes32" } ], + "name": "setNodeId", + "outputs": [], "payable": false, - "stateMutability": "view", + "stateMutability": "nonpayable", "type": "function" }, { + "constant": false, "inputs": [ { - "name": "hubAddress", - "type": "address" + "name": "newWithdrawalTime", + "type": "uint256" } ], + "name": "setWithdrawalTime", + "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "constructor" + "type": "function" }, { "anonymous": false, @@ -268,11 +151,11 @@ }, { "indexed": false, - "name": "initialBalance", + "name": "amount", "type": "uint256" } ], - "name": "ProfileCreated", + "name": "TokensReleased", "type": "event" }, { @@ -285,13 +168,36 @@ }, { "indexed": false, - "name": "newIdentity", - "type": "address" + "name": "amountWithdrawn", + "type": "uint256" + }, + { + "indexed": false, + "name": "newBalance", + "type": "uint256" } ], - "name": "IdentityCreated", + "name": "TokensWithdrawn", "type": "event" }, + { + "constant": false, + "inputs": [ + { + "name": "identity", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "startTokenWithdrawal", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "anonymous": false, "inputs": [ @@ -302,11 +208,11 @@ }, { "indexed": false, - "name": "amount", + "name": "amountReserved", "type": "uint256" } ], - "name": "TokenDeposit", + "name": "TokensReserved", "type": "event" }, { @@ -338,14 +244,9 @@ "indexed": false, "name": "profile", "type": "address" - }, - { - "indexed": false, - "name": "amountReserved", - "type": "uint256" } ], - "name": "TokensReserved", + "name": "TokenWithdrawalCancelled", "type": "event" }, { @@ -377,9 +278,14 @@ "indexed": false, "name": "profile", "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" } ], - "name": "TokenWithdrawalCancelled", + "name": "TokenDeposit", "type": "event" }, { @@ -387,21 +293,33 @@ "inputs": [ { "indexed": false, - "name": "profile", + "name": "oldIdentity", "type": "address" }, { "indexed": false, - "name": "amountWithdrawn", - "type": "uint256" + "name": "newIdentity", + "type": "address" + } + ], + "name": "IdentityTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "profile", + "type": "address" }, { "indexed": false, - "name": "newBalance", - "type": "uint256" + "name": "newIdentity", + "type": "address" } ], - "name": "TokensWithdrawn", + "name": "IdentityCreated", "type": "event" }, { @@ -414,11 +332,11 @@ }, { "indexed": false, - "name": "amount", + "name": "initialBalance", "type": "uint256" } ], - "name": "TokensReleased", + "name": "ProfileCreated", "type": "event" }, { @@ -442,5 +360,141 @@ ], "name": "TokensTransferred", "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "name": "oldIdentity", + "type": "address" + } + ], + "name": "transferProfile", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "sender", + "type": "address" + }, + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transferTokens", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "identity", + "type": "address" + } + ], + "name": "withdrawTokens", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "hubAddress", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "constant": true, + "inputs": [], + "name": "hub", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minimalStake", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "profileStorage", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "version", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "withdrawalTime", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" } ] \ No newline at end of file diff --git a/modules/Blockchain/Ethereum/contracts/ByteArr.sol b/modules/Blockchain/Ethereum/contracts/ByteArr.sol index a53b7ccb7..ba7a66450 100644 --- a/modules/Blockchain/Ethereum/contracts/ByteArr.sol +++ b/modules/Blockchain/Ethereum/contracts/ByteArr.sol @@ -24,6 +24,7 @@ library ByteArr { self[index] = self[self.length-1]; delete self[self.length-1]; + self.length = self.length - 1; return self; } @@ -33,6 +34,7 @@ library ByteArr { self[index] = self[self.length-1]; delete self[self.length-1]; + self.length = self.length - 1; return self; } diff --git a/modules/Blockchain/Ethereum/contracts/Identity.sol b/modules/Blockchain/Ethereum/contracts/Identity.sol index 7aa47ce46..76def1de0 100644 --- a/modules/Blockchain/Ethereum/contracts/Identity.sol +++ b/modules/Blockchain/Ethereum/contracts/Identity.sol @@ -22,16 +22,42 @@ contract Identity is ERC725 { mapping (uint256 => bytes32[]) keysByPurpose; mapping (uint256 => Execution) executions; - constructor(address sender) public { - bytes32 _key = keccak256(abi.encodePacked(sender)); - keys[_key].key = _key; - keys[_key].purposes = [1,2,3,4]; - keys[_key].keyType = 1; - keysByPurpose[1].push(_key); - emit KeyAdded(_key, keys[_key].purposes, 1); + constructor(address operational, address management) public { + require(operational != address(0) && management != address(0)); + + bytes32 _management_key = keccak256(abi.encodePacked(management)); + + keys[_management_key].key = _management_key; + + keys[_management_key].keyType = 1; + + keys[_management_key].purposes = [1,2,3,4]; + + keysByPurpose[1].push(_management_key); + keysByPurpose[2].push(_management_key); + keysByPurpose[3].push(_management_key); + keysByPurpose[4].push(_management_key); + emit KeyAdded(_management_key, keys[_management_key].purposes, 1); + + if(operational != management){ + bytes32 _operational_key = keccak256(abi.encodePacked(operational)); + + keys[_operational_key].key = _operational_key; + + keys[_operational_key].keyType = 1; + + keys[_operational_key].purposes = [2,4]; + + keysByPurpose[2].push(_operational_key); + keysByPurpose[4].push(_operational_key); + + emit KeyAdded(_operational_key, keys[_operational_key].purposes, 1); + } } function addKey(bytes32 _key, uint256[] _purposes, uint256 _type) external returns (bool success) { + require(keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1)); + require(_key != bytes32(0)); require(keys[_key].key != _key); keys[_key].key = _key; @@ -99,7 +125,13 @@ contract Identity is ERC725 { } function removeKey(bytes32 _key) external returns (bool success) { + require(keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1)); + require(_key != bytes32(0)); + require(keys[_key].key == _key); + + require(!(keysByPurpose[1].length == 1 && keyHasPurpose(_key, 1)), "Cannot delete only management key!"); + emit KeyRemoved(keys[_key].key, keys[_key].purposes, keys[_key].keyType); for (uint i = 0; i < keys[_key].purposes.length; i++) { diff --git a/modules/Blockchain/Ethereum/contracts/Profile.sol b/modules/Blockchain/Ethereum/contracts/Profile.sol index 7ac6cfdc8..8a6c03fe5 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -12,6 +12,8 @@ contract Profile { Hub public hub; ProfileStorage public profileStorage; + uint256 public version = 100; + uint256 public minimalStake = 10**21; uint256 public withdrawalTime = 5 minutes; @@ -26,9 +28,10 @@ contract Profile { "Function can only be called by Holding contract!"); _; } - + event ProfileCreated(address profile, uint256 initialBalance); event IdentityCreated(address profile, address newIdentity); + event IdentityTransferred(address oldIdentity, address newIdentity); event TokenDeposit(address profile, uint256 amount); event TokensDeposited(address profile, uint256 amountDeposited, uint256 newBalance); @@ -41,7 +44,7 @@ contract Profile { event TokensReleased(address profile, uint256 amount); event TokensTransferred(address sender, address receiver, uint256 amount); - function createProfile(bytes32 profileNodeId, uint256 initialBalance, bool senderHas725, address identity) public { + function createProfile(address managementWallet, bytes32 profileNodeId, uint256 initialBalance, bool senderHas725, address identity) public { 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!"); @@ -50,7 +53,7 @@ contract Profile { tokenContract.transferFrom(msg.sender, address(profileStorage), initialBalance); if(!senderHas725) { - Identity newIdentity = new Identity(msg.sender); + Identity newIdentity = new Identity(msg.sender, managementWallet); emit IdentityCreated(msg.sender, address(newIdentity)); profileStorage.setStake(address(newIdentity), initialBalance); @@ -60,7 +63,7 @@ contract Profile { } else { // Verify sender - require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2)); + require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2), "Sender does not have action permission for identity!"); profileStorage.setStake(identity, initialBalance); profileStorage.setNodeId(identity, profileNodeId); @@ -75,9 +78,27 @@ contract Profile { } } + function transferProfile(address oldIdentity) public returns(address){ + require(ERC725(oldIdentity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); + + Identity newIdentity = new Identity(msg.sender, msg.sender); + emit IdentityCreated(msg.sender, address(newIdentity)); + + profileStorage.setStake(address(newIdentity), profileStorage.getStake(oldIdentity)); + profileStorage.setStakeReserved(address(newIdentity), profileStorage.getStakeReserved(oldIdentity)); + profileStorage.setNodeId(address(newIdentity), profileStorage.getNodeId(oldIdentity)); + + profileStorage.setStake(oldIdentity, 0); + profileStorage.setStakeReserved(oldIdentity, 0); + profileStorage.setNodeId(oldIdentity, bytes32(0)); + + emit IdentityTransferred(oldIdentity, newIdentity); + return address(newIdentity); + } + function depositTokens(address identity, uint256 amount) public { // Verify sender - require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2)); + require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); ERC20 tokenContract = ERC20(hub.tokenAddress()); require(tokenContract.allowance(msg.sender, this) >= amount, "Sender allowance must be equal to or higher than chosen amount"); @@ -92,7 +113,7 @@ contract Profile { function startTokenWithdrawal(address identity, uint256 amount) public { // Verify sender - require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2)); + require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); require(profileStorage.getWithdrawalPending(identity) == false, "Withrdrawal process already pending!"); @@ -114,7 +135,7 @@ contract Profile { function withdrawTokens(address identity) public { // Verify sender - require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2), "Sender does not have action permission for identity!"); + require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); require(profileStorage.getWithdrawalPending(identity) == true, "Cannot withdraw tokens before starting token withdrawal!"); require(profileStorage.getWithdrawalTimestamp(identity) < block.timestamp, "Cannot withdraw tokens before withdrawal timestamp!"); diff --git a/modules/Blockchain/Ethereum/index.js b/modules/Blockchain/Ethereum/index.js index 79300326a..4311714f4 100644 --- a/modules/Blockchain/Ethereum/index.js +++ b/modules/Blockchain/Ethereum/index.js @@ -4,6 +4,10 @@ const Utilities = require('../../Utilities'); const Models = require('../../../models'); const Op = require('sequelize/lib/operators'); const uuidv4 = require('uuid/v4'); +const ethereumAbi = require('ethereumjs-abi'); +const md5 = require('md5'); + +const oldErc725codeMd5 = '4fbb9cd89fa6b148d1f0c3b96fdadcfc'; class Ethereum { /** @@ -111,6 +115,10 @@ class Ethereum { this.holdingStorageContractAddress, ); + // ERC725 identity contract data. Every user has own instance. + const erc725IdentityAbiFile = fs.readFileSync('./modules/Blockchain/Ethereum/abi/erc725.json'); + this.erc725IdentityContractAbi = JSON.parse(erc725IdentityAbiFile); + this.contractsByName = { HOLDING_CONTRACT: this.holdingContract, PROFILE_CONTRACT: this.profileContract, @@ -245,22 +253,30 @@ class Ethereum { /** * Creates node profile on the Bidding contract + * @param managementWallet - Management wallet * @param profileNodeId - Network node ID * @param initialBalance - Initial profile balance * @param isSender725 - Is sender ERC 725? * @param blockchainIdentity - ERC 725 identity (empty if there is none) * @return {Promise} */ - createProfile(profileNodeId, initialBalance, isSender725, blockchainIdentity) { + createProfile( + managementWallet, + profileNodeId, + initialBalance, + isSender725, + blockchainIdentity, + ) { const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), gasPrice: this.web3.utils.toHex(this.config.gas_price), to: this.profileContractAddress, }; - this.log.trace(`CreateProfile(${profileNodeId}, ${initialBalance}, ${isSender725})`); + this.log.trace(`CreateProfile(${managementWallet}, ${profileNodeId}, ${initialBalance}, ${isSender725})`); return this.transactions.queueTransaction( this.profileContractAbi, 'createProfile', [ + managementWallet, Utilities.normalizeHex(profileNodeId), initialBalance, isSender725, blockchainIdentity, ], options, @@ -985,44 +1001,54 @@ class Ethereum { } /** - * Check balances - * @returns {Promise} + * Token contract address getter + * @return {any|*} */ - async hasEnoughFunds() { - this.log.trace('Checking balances'); - let enoughETH = true; - let enoughTRAC = true; - try { - const etherBalance = await Utilities.getBalanceInEthers( - this.web3, - this.config.wallet_address, - ); - this.log.info(`Balance of ETH: ${etherBalance}`); - if (etherBalance < 0.01) { - enoughETH = false; - } + getTokenContractAddress() { + return this.tokenContractAddress; + } - const tracBalance = await Utilities.getTracTokenBalance( - this.web3, - this.config.wallet_address, - this.tokenContractAddress, - ); - this.log.info(`Balance of TRAC: ${tracBalance}`); - if (tracBalance < 100) { - enoughTRAC = false; - } - } catch (error) { - throw new Error(error); - } - return enoughETH && enoughTRAC; + /** + * Returns purposes of the wallet. + * @param {string} - erc725Identity + * @param {string} - wallet + * @return {Promise<[]>} + */ + getWalletPurposes(erc725Identity, wallet) { + const erc725IdentityContract = new this.web3.eth.Contract( + this.erc725IdentityContractAbi, + erc725Identity, + ); + + const key = ethereumAbi.soliditySHA3(['address'], [wallet]).toString('hex'); + return erc725IdentityContract.methods.getKeyPurposes(Utilities.normalizeHex(key)).call(); } /** - * Token contract address getter - * @return {any|*} + * Transfers identity to new address. + * @param {string} - erc725identity */ - getTokenContractAddress() { - return this.tokenContractAddress; + transferProfile(erc725identity) { + const options = { + gasLimit: this.web3.utils.toHex(this.config.gas_limit), + gasPrice: this.web3.utils.toHex(this.config.gas_price), + to: this.profileContractAddress, + }; + + this.log.trace(`transferProfile (${erc725identity})`); + return this.transactions.queueTransaction( + this.profileContractAbi, 'transferProfile', + [erc725identity], options, + ); + } + + /** + * Returns true if ERC725 contract is older version. + * @param {string} - address of ERC 725 identity. + * @return {Promise} + */ + async isErc725IdentityOld(address) { + return oldErc725codeMd5 === md5(await this.web3.eth.getCode(address)); } } diff --git a/modules/Blockchain/Ethereum/test/offer.test.js b/modules/Blockchain/Ethereum/test/offer.test.js index c7699fa4e..63835205e 100644 --- a/modules/Blockchain/Ethereum/test/offer.test.js +++ b/modules/Blockchain/Ethereum/test/offer.test.js @@ -101,6 +101,7 @@ contract('Offer testing', async (accounts) => { for (i = 0; i < accounts.length; i += 1) { // eslint-disable-next-line no-await-in-loop res = await profile.createProfile( + accounts[i], '0x4cad6896887d99d70db8ce035d331ba2ade1a5e1161f38ff7fda76cf7c308cde', tokensToDeposit, false, diff --git a/modules/Blockchain/Ethereum/test/profile.test.js b/modules/Blockchain/Ethereum/test/profile.test.js index 94c3b1deb..b52a661a3 100644 --- a/modules/Blockchain/Ethereum/test/profile.test.js +++ b/modules/Blockchain/Ethereum/test/profile.test.js @@ -51,7 +51,7 @@ contract('Profile contract testing', async (accounts) => { var identities = []; for (var i = 0; i < accounts.length; i += 1) { // eslint-disable-next-line no-await-in-loop - identities[i] = await Identity.new(accounts[i], { from: accounts[i] }); + identities[i] = await Identity.new(accounts[i], accounts[i], { from: accounts[i] }); } var initialBalances = []; @@ -73,6 +73,7 @@ contract('Profile contract testing', async (accounts) => { promises = []; for (i = 0; i < accounts.length; i += 1) { promises[i] = profile.createProfile( + accounts[i], nodeId, amountToDeposit, true, @@ -154,6 +155,7 @@ contract('Profile contract testing', async (accounts) => { for (i = 0; i < accounts.length; i += 1) { // eslint-disable-next-line no-await-in-loop const res = await profile.createProfile( + accounts[i], nodeId, amountToDeposit, false, @@ -210,7 +212,7 @@ contract('Profile contract testing', async (accounts) => { }); // eslint-disable-next-line no-undef - it('Should deposit tokens to profile', async () => { + it.skip('Should deposit tokens to profile', async () => { // Get contracts used in hook const trac = await TracToken.deployed(); const profile = await Profile.deployed(); diff --git a/modules/DVService.js b/modules/DVService.js index fc395fafd..c878f6032 100644 --- a/modules/DVService.js +++ b/modules/DVService.js @@ -150,8 +150,7 @@ class DVService { .add(stakeAmount); if (profileBalance.lt(condition)) { - await this.blockchain.increaseBiddingApproval(condition.sub(profileBalance)); - await this.blockchain.depositTokens(condition.sub(profileBalance)); + throw new Error('Not enough funds to handle data read response'); } // Sign escrow. diff --git a/modules/EventEmitter.js b/modules/EventEmitter.js index 69aa39055..f95a848aa 100644 --- a/modules/EventEmitter.js +++ b/modules/EventEmitter.js @@ -502,13 +502,6 @@ class EventEmitter { }); this._on('api-create-offer', async (data) => { - if (!appState.enoughFunds) { - data.response.status(400); - data.response.send({ - message: 'Insufficient funds', - }); - return; - } const { dataSetId, holdingTimeInMinutes, @@ -599,30 +592,6 @@ class EventEmitter { } }); - this._on('api-deposit-tokens', async (data) => { - const { trac_amount } = data; - - try { - logger.info(`Deposit ${trac_amount} TRAC to profile triggered`); - - await profileService.depositTokens(trac_amount); - remoteControl.tokenDepositSucceeded(`${trac_amount} TRAC deposited to your profile`); - - data.response.status(200); - data.response.send({ - message: `Successfully deposited ${trac_amount} TRAC to profile`, - }); - } catch (error) { - logger.error(`Failed to deposit tokens. ${error}.`); - notifyError(error); - data.response.status(400); - data.response.send({ - message: `Failed to deposit tokens. ${error}.`, - }); - remoteControl.tokensDepositFailed(`Failed to deposit tokens. ${error}.`); - } - }); - this._on('api-withdraw-tokens', async (data) => { const { trac_amount } = data; @@ -689,9 +658,6 @@ class EventEmitter { }); this._on('eth-OfferCreated', async (eventData) => { - if (!appState.enoughFunds) { - return; - } let { dcNodeId, } = eventData; diff --git a/modules/command/common/deposit-tokens-command.js b/modules/command/common/deposit-tokens-command.js deleted file mode 100644 index 5294138e0..000000000 --- a/modules/command/common/deposit-tokens-command.js +++ /dev/null @@ -1,92 +0,0 @@ -const BN = require('../../../node_modules/bn.js/lib/bn'); - -const Command = require('../command'); -const Utilities = require('../../Utilities'); - -/** - * Deposits tokens on blockchain - */ -class DepositTokensCommand extends Command { - constructor(ctx) { - super(ctx); - this.web3 = ctx.web3; - this.logger = ctx.logger; - this.config = ctx.config; - this.blockchain = ctx.blockchain; - } - - /** - * Executes command and produces one or more events - * @param command - */ - async execute(command) { - const { amount } = command.data; - this.logger.notify(`Deposit amount: ${amount} mTRAC.`); - - const blockchainIdentity = Utilities.normalizeHex(this.config.erc725Identity); - await this._printBalances(blockchainIdentity, 'Old'); - await this.blockchain.depositTokens(blockchainIdentity, amount); - await this._printBalances(blockchainIdentity, 'New'); - return this.continueSequence(this.pack(command.data), command.sequence); - } - - /** - * Print balances - * @param blockchainIdentity - * @param timeFrame string to describe before or after withdrawal operation - * @return {Promise} - * @private - */ - async _printBalances(blockchainIdentity, timeFrame) { - const balance = await this.blockchain.getProfileBalance(this.config.node_wallet); - const balanceInTRAC = this.web3.utils.fromWei(balance, 'ether'); - this.logger.info(`${timeFrame} wallet balance: ${balanceInTRAC} TRAC`); - - const profile = await this.blockchain.getProfile(blockchainIdentity); - const profileBalance = profile.stake; - const profileBalanceInTRAC = this.web3.utils.fromWei(profileBalance, 'ether'); - this.logger.info(`${timeFrame} profile balance: ${profileBalanceInTRAC} TRAC`); - } - - /** - * Pack data for DB - * @param data - */ - pack(data) { - Object.assign(data, { - amount: data.amount.toString(), - }); - return data; - } - - /** - * Unpack data from database - * @param data - * @returns {Promise<*>} - */ - unpack(data) { - const parsed = data; - Object.assign(parsed, { - amount: new BN(data.amount, 10), - }); - return parsed; - } - - /** - * Builds default command - * @param map - * @returns {{add, data: *, delay: *, deadline: *}} - */ - default(map) { - const command = { - name: 'depositTokensCommand', - retries: 3, - delay: 0, - transactional: false, - }; - Object.assign(command, map); - return command; - } -} - -module.exports = DepositTokensCommand; diff --git a/modules/command/common/profile-approval-increase-command.js b/modules/command/common/profile-approval-increase-command.js deleted file mode 100644 index 4de3cca9c..000000000 --- a/modules/command/common/profile-approval-increase-command.js +++ /dev/null @@ -1,66 +0,0 @@ -const Command = require('../command'); -const BN = require('../../../node_modules/bn.js/lib/bn'); - -/** - * Increases approval for Profile contract on blockchain - */ -class ProfileApprovalIncreaseCommand extends Command { - constructor(ctx) { - super(ctx); - this.logger = ctx.logger; - this.blockchain = ctx.blockchain; - } - - /** - * Executes command and produces one or more events - * @param command - */ - async execute(command) { - const { amount } = command.data; - this.logger.notify(`Giving approval to profile contract for amount: ${amount} mTRAC.`); - - await this.blockchain.increaseProfileApproval(amount); - return this.continueSequence(this.pack(command.data), command.sequence); - } - - /** - * Pack data for DB - * @param data - */ - pack(data) { - Object.assign(data, { - amount: data.amount.toString(), - }); - return data; - } - - /** - * Unpack data from database - * @param data - * @returns {Promise<*>} - */ - unpack(data) { - const parsed = data; - Object.assign(parsed, { - amount: new BN(data.amount, 10), - }); - return parsed; - } - - /** - * Builds default command - * @param map - * @returns {{add, data: *, delay: *, deadline: *}} - */ - default(map) { - const command = { - name: 'profileApprovalIncreaseCommand', - delay: 0, - transactional: false, - }; - Object.assign(command, map); - return command; - } -} - -module.exports = ProfileApprovalIncreaseCommand; diff --git a/modules/command/common/token-withdrawal-command.js b/modules/command/common/token-withdrawal-command.js deleted file mode 100644 index d18dad3a4..000000000 --- a/modules/command/common/token-withdrawal-command.js +++ /dev/null @@ -1,69 +0,0 @@ -const Command = require('../command'); -const Utilities = require('../../Utilities'); - -/** - * Starts token withdrawal operation - */ -class TokenWithdrawalCommand extends Command { - constructor(ctx) { - super(ctx); - this.config = ctx.config; - this.logger = ctx.logger; - this.web3 = ctx.web3; - this.blockchain = ctx.blockchain; - this.remoteControl = ctx.remoteControl; - } - - /** - * Executes command and produces one or more events - * @param command - */ - async execute(command) { - const { - amount, - } = command.data; - - const blockchainIdentity = Utilities.normalizeHex(this.config.erc725Identity); - await this._printBalances(blockchainIdentity, 'Old'); - await this.blockchain.withdrawTokens(blockchainIdentity); - this.logger.important(`Token withdrawal for amount ${amount} completed.`); - await this._printBalances(blockchainIdentity, 'New'); - return Command.empty(); - } - - /** - * Print balances - * @param blockchainIdentity - * @param timeFrame string to describe before or after withdrawal operation - * @return {Promise} - * @private - */ - async _printBalances(blockchainIdentity, timeFrame) { - const balance = await this.blockchain.getProfileBalance(this.config.node_wallet); - const balanceInTRAC = this.web3.utils.fromWei(balance, 'ether'); - this.logger.info(`${timeFrame} wallet balance: ${balanceInTRAC} TRAC`); - - const profile = await this.blockchain.getProfile(blockchainIdentity); - const profileBalance = profile.stake; - const profileBalanceInTRAC = this.web3.utils.fromWei(profileBalance, 'ether'); - this.logger.info(`${timeFrame} profile balance: ${profileBalanceInTRAC} TRAC`); - } - - /** - * Builds default command - * @param map - * @returns {{add, data: *, delay: *, deadline: *}} - */ - default(map) { - const command = { - name: 'tokenWithdrawalCommand', - delay: 30000, - retries: 3, - transactional: false, - }; - Object.assign(command, map); - return command; - } -} - -module.exports = TokenWithdrawalCommand; diff --git a/modules/command/common/token-withdrawal-start-command.js b/modules/command/common/token-withdrawal-start-command.js deleted file mode 100644 index 48f49c5cd..000000000 --- a/modules/command/common/token-withdrawal-start-command.js +++ /dev/null @@ -1,58 +0,0 @@ -const BN = require('../../../node_modules/bn.js/lib/bn'); -const Command = require('../command'); -const Utilities = require('../../Utilities'); - -/** - * Starts token withdrawal operation - */ -class TokenWithdrawalStartCommand extends Command { - constructor(ctx) { - super(ctx); - this.config = ctx.config; - this.logger = ctx.logger; - this.blockchain = ctx.blockchain; - this.remoteControl = ctx.remoteControl; - } - - /** - * Executes command and produces one or more events - * @param command - */ - async execute(command) { - const { - amount, - } = command.data; - - await this.blockchain.startTokenWithdrawal( - Utilities.normalizeHex(this.config.erc725Identity), - new BN(amount, 10), - ); - return { - commands: [ - { - name: 'tokenWithdrawalWaitStartedCommand', - period: 5000, - deadline_at: Date.now() + (5 * 60 * 1000), - data: command.data, - }, - ], - }; - } - - /** - * Builds default command - * @param map - * @returns {{add, data: *, delay: *, deadline: *}} - */ - default(map) { - const command = { - name: 'tokenWithdrawalStartCommand', - delay: 0, - transactional: false, - }; - Object.assign(command, map); - return command; - } -} - -module.exports = TokenWithdrawalStartCommand; diff --git a/modules/command/common/token-withdrawal-wait-started-command.js b/modules/command/common/token-withdrawal-wait-started-command.js deleted file mode 100644 index f76bf3516..000000000 --- a/modules/command/common/token-withdrawal-wait-started-command.js +++ /dev/null @@ -1,99 +0,0 @@ -const BN = require('../../../node_modules/bn.js/lib/bn'); - -const Command = require('../command'); -const Models = require('../../../models/index'); - -/** - * Repeatable command that checks whether offer is ready or not - */ -class TokenWithdrawalWaitStartedCommand extends Command { - constructor(ctx) { - super(ctx); - this.logger = ctx.logger; - this.config = ctx.config; - } - - /** - * Executes command and produces one or more events - * @param command - */ - async execute(command) { - const { - amount, - } = command.data; - - const events = await Models.events.findAll({ - where: { - event: 'WithdrawalInitiated', - finished: 0, - }, - }); - if (events) { - const event = events.find((e) => { - const { - profile: eventProfile, - } = JSON.parse(e.data); - return eventProfile.toLowerCase() - .includes(this.config.erc725Identity.toLowerCase()); - }); - if (event) { - event.finished = true; - await event.save({ fields: ['finished'] }); - - const { - amount: eAmount, - withdrawalDelayInSeconds: eWithdrawalDelayInSeconds, - } = JSON.parse(event.data); - this.logger.important(`Token withdrawal for amount ${amount} initiated.`); - - const amountBN = new BN(amount, 10); - const eAmountBN = new BN(eAmount, 10); - if (!amountBN.eq(eAmountBN)) { - this.logger.warn(`Not enough tokens for withdrawal [${amount}]. All the tokens will be withdrawn [${eAmount}]`); - } - const { data } = command; - Object.assign(data, { - amount: eAmount, - }); - return { - commands: [ - { - name: 'tokenWithdrawalCommand', - delay: eWithdrawalDelayInSeconds * 1000, - data, - }, - ], - }; - } - } - return Command.repeat(); - } - - /** - * Execute strategy when event is too late - * @param command - */ - async expired(command) { - // TODO implement - return Command.empty(); - } - - /** - * Builds default AddCommand - * @param map - * @returns {{add, data: *, delay: *, deadline: *}} - */ - default(map) { - const command = { - name: 'tokenWithdrawalWaitStartedCommand', - delay: 0, - period: 5000, - deadline_at: Date.now() + (5 * 60 * 1000), - transactional: false, - }; - Object.assign(command, map); - return command; - } -} - -module.exports = TokenWithdrawalWaitStartedCommand; diff --git a/modules/command/dc/dc-offer-finalize-command.js b/modules/command/dc/dc-offer-finalize-command.js index ae60b6fba..c998b77ec 100644 --- a/modules/command/dc/dc-offer-finalize-command.js +++ b/modules/command/dc/dc-offer-finalize-command.js @@ -105,22 +105,16 @@ class DCOfferFinalizeCommand extends Command { }; } - const depositToken = await this.dcService.chainDepositCommandIfNeeded( - offer.token_amount_per_holder, - command.data, - ['dcOfferFinalizeCommand'], - ); - if (depositToken) { - this.logger.warn(`Failed to finalize offer ${offerId} because DC didn't have enough funds. Trying again...`); - return { - commands: [depositToken], - }; + let errorMessage = err.message; + const hasFunds = await this.dcService + .hasProfileBalanceForOffer(offer.token_amount_per_holder); + if (!hasFunds) { + errorMessage = 'Not enough tokens. To replicate data please deposit more tokens to your profile.'; } - - this.logger.error(`Offer ${offerId} has not been finalized.`); + this.logger.error(`Offer ${offerId} has not been finalized. ${errorMessage}`); offer.status = 'FAILED'; - offer.message = `Offer for ${offerId} has not been finalized. ${err.message}`; + offer.message = `Offer for ${offerId} has not been finalized. ${errorMessage}`; await offer.save({ fields: ['status', 'message'] }); await this.replicationService.cleanup(offer.id); diff --git a/modules/command/dc/dc-offer-mining-completed-command.js b/modules/command/dc/dc-offer-mining-completed-command.js index 5fb9fad43..794d59855 100644 --- a/modules/command/dc/dc-offer-mining-completed-command.js +++ b/modules/command/dc/dc-offer-mining-completed-command.js @@ -59,18 +59,14 @@ class DcOfferMiningCompletedCommand extends Command { offer.message = 'Found a solution for DHs provided'; await offer.save({ fields: ['status', 'message'] }); + const hasFunds = await this.dcService + .hasProfileBalanceForOffer(offer.token_amount_per_holder); + if (!hasFunds) { + throw new Error('Not enough tokens. To replicate data please deposit more tokens to your profile'); + } + const commandData = { offerId, solution }; const commandSequence = ['dcOfferFinalizeCommand']; - const depositCommand = await this.dcService.chainDepositCommandIfNeeded( - offer.token_amount_per_holder, - commandData, - commandSequence, - ); - if (depositCommand) { - return { - commands: [depositCommand], - }; - } return { commands: [ { diff --git a/modules/migration/m1-payout-all-migration.js b/modules/migration/m1-payout-all-migration.js new file mode 100644 index 000000000..c225f465e --- /dev/null +++ b/modules/migration/m1-payout-all-migration.js @@ -0,0 +1,50 @@ +const models = require('../../models'); + +/** + * Runs all pending payout commands + */ +class M1PayoutAllMigration { + constructor(ctx) { + this.logger = ctx.logger; + this.payOutHandler = ctx.dhPayOutCommand; + } + + /** + * Run migration + */ + async run() { + /* get all pending payouts */ + const pendingPayOuts = await models.commands.findAll({ + where: { + status: 'PENDING', + name: 'dhPayOutCommand', + }, + }); + + for (const pendingPayOut of pendingPayOuts) { + const data = this.payOutHandler.unpack(pendingPayOut.data); + + let retries = 3; + while (retries > 0) { + try { + // eslint-disable-next-line + await this.payOutHandler.execute({ data }); // run payout + pendingPayOut.status = 'COMPLETED'; + pendingPayOut.save({ + fields: ['status'], + }); + break; + } catch (e) { + retries -= 1; + if (retries > 0) { + this.logger.error(`Failed to run payout migration. Retrying... ${e}`); + } else { + this.logger.error(`Failed to run payout migration. Stop retrying... ${e}`); + } + } + } + } + } +} + +module.exports = M1PayoutAllMigration; diff --git a/modules/service/dc-service.js b/modules/service/dc-service.js index 36ac3a330..557fc1f70 100644 --- a/modules/service/dc-service.js +++ b/modules/service/dc-service.js @@ -4,7 +4,7 @@ const Encryption = require('../Encryption'); const models = require('../../models'); -const DEFAILT_NUMBER_OF_HOLDERS = 3; +const DEFAULT_NUMBER_OF_HOLDERS = 3; class DCService { constructor(ctx) { @@ -31,6 +31,13 @@ class DCService { dataSetId, dataRootHash, holdingTimeInMinutes, tokenAmountPerHolder, dataSizeInBytes, litigationIntervalInMinutes, ) { + const hasFunds = await this.hasProfileBalanceForOffer(tokenAmountPerHolder); + if (!hasFunds) { + const message = 'Not enough tokens. To replicate data please deposit more tokens to your profile.'; + this.logger.warn(message); + throw new Error(message); + } + const offer = await models.offers.create({ data_set_id: dataSetId, message: 'Offer is pending', @@ -64,23 +71,14 @@ class DCService { 'dcOfferCreateBcCommand', 'dcOfferTaskCommand', 'dcOfferChooseCommand']; - const depositCommand = await this.chainDepositCommandIfNeeded( - tokenAmountPerHolder, - commandData, - commandSequence, - ); - if (depositCommand) { - await this.commandExecutor.add(depositCommand); - } else { - await this.commandExecutor.add({ - name: commandSequence[0], - sequence: commandSequence.slice(1), - delay: 0, - data: commandData, - transactional: false, - }); - } + await this.commandExecutor.add({ + name: commandSequence[0], + sequence: commandSequence.slice(1), + delay: 0, + data: commandData, + transactional: false, + }); return offer.id; } @@ -118,19 +116,17 @@ class DCService { } /** - * Creates commands needed for token deposit if there is a need for that - * @param tokenAmountPerHolder - * @param commandData - * @param commandSequence + * Has enough balance on profile for creating an offer + * @param tokenAmountPerHolder - Tokens per DH * @return {Promise<*>} */ - async chainDepositCommandIfNeeded(tokenAmountPerHolder, commandData, commandSequence) { + async hasProfileBalanceForOffer(tokenAmountPerHolder) { const profile = await this.blockchain.getProfile(this.config.erc725Identity); const profileStake = new BN(profile.stake, 10); const profileStakeReserved = new BN(profile.stakeReserved, 10); const offerStake = new BN(tokenAmountPerHolder, 10) - .mul(new BN(DEFAILT_NUMBER_OF_HOLDERS, 10)); + .mul(new BN(DEFAULT_NUMBER_OF_HOLDERS, 10)); let remainder = null; if (profileStake.sub(profileStakeReserved).lt(offerStake)) { @@ -144,32 +140,7 @@ class DCService { remainder = stakeRemainder; } } - - let depositCommand = null; - if (remainder) { - if (!this.config.deposit_on_demand) { - const message = 'Not enough tokens. To replicate data please deposit more tokens to your profile.'; - this.logger.warn(message); - throw new Error(message); - } - - // deposit tokens - depositCommand = { - name: 'profileApprovalIncreaseCommand', - sequence: [ - 'depositTokensCommand', - ], - delay: 0, - data: { - amount: remainder.toString(), - }, - transactional: false, - }; - - Object.assign(depositCommand.data, commandData); - depositCommand.sequence = depositCommand.sequence.concat(commandSequence); - } - return depositCommand; + return !remainder; } /** diff --git a/modules/service/dh-service.js b/modules/service/dh-service.js index f36ecc2dc..a63035749 100644 --- a/modules/service/dh-service.js +++ b/modules/service/dh-service.js @@ -155,6 +155,10 @@ class DHService { tokenAmountPerHolder, ); + if (remainder) { + throw new Error('Not enough tokens. To take additional jobs please complete any finished jobs or deposit more tokens to your profile.'); + } + const data = { offerId, dcNodeId, @@ -164,34 +168,12 @@ class DHService { tokenAmountPerHolder, }; - if (remainder) { - if (!this.config.deposit_on_demand) { - throw new Error('Not enough tokens. To take additional jobs please complete any finished jobs or deposit more tokens to your profile.'); - } - - bid.deposit = remainder.toString(); - await bid.save({ fields: ['deposit'] }); - - this.logger.warn(`Not enough tokens for offer ${offerId}. Minimum amount of tokens will be deposited automatically.`); - - Object.assign(data, { - amount: remainder.toString(), - }); - await this.commandExecutor.add({ - name: 'profileApprovalIncreaseCommand', - sequence: ['depositTokensCommand', 'dhOfferHandleCommand'], - delay: 15000, - data, - transactional: false, - }); - } else { - await this.commandExecutor.add({ - name: 'dhOfferHandleCommand', - delay: 15000, - data, - transactional: false, - }); - } + await this.commandExecutor.add({ + name: 'dhOfferHandleCommand', + delay: 15000, + data, + transactional: false, + }); } /** @@ -246,33 +228,9 @@ class DHService { remainder = stakeRemainder; } } - - if (remainder) { - const depositSum = remainder.add(currentDeposits); - const canDeposit = await this._canDeposit(depositSum); - if (!canDeposit) { - throw new Error('Not enough tokens. Insufficient funds.'); - } - } return remainder; } - /** - * Can deposit or not? - * @param amount {BN} amount to be deposited in mTRAC - * @return {Promise} - * @private - */ - async _canDeposit(amount) { - const walletBalance = await Utilities.getTracTokenBalance( - this.web3, - this.config.node_wallet, - this.blockchain.getTokenContractAddress(), - ); - const walletBalanceBN = new BN(this.web3.utils.toWei(parseFloat(walletBalance).toString(), 'ether'), 10); - return amount.lt(walletBalanceBN); - } - /** * Handle one read request (checks whether node satisfies query) * @param msgId - Message ID @@ -385,8 +343,7 @@ class DHService { new BN((await this.blockchain.getProfile(this.config.node_wallet)).balance, 10); if (profileBalance.lt(condition)) { - await this.blockchain.increaseBiddingApproval(condition.sub(profileBalance)); - await this.blockchain.depositTokens(condition.sub(profileBalance)); + throw new Error('Not enough funds to handle data read request'); } /* diff --git a/modules/service/profile-service.js b/modules/service/profile-service.js index 63f00e075..a04e0ddba 100644 --- a/modules/service/profile-service.js +++ b/modules/service/profile-service.js @@ -32,20 +32,56 @@ class ProfileService { if (identityExists && await this.isProfileCreated()) { this.logger.notify(`Profile has already been created for node ${this.config.identity}`); + let walletToCheck; + if (this.config.management_wallet) { + walletToCheck = this.config.management_wallet; + } else { + this.logger.important('Management wallet not set. Please set one.'); + walletToCheck = this.config.node_wallet; + } + + // Check financial wallet permissions. + const permissions = await this.blockchain.getWalletPurposes( + this.config.erc725Identity, + walletToCheck, + ); + + if (!permissions.includes('1')) { + throw Error(`Management wallet ${walletToCheck} doesn't have enough permissions.`); + } return; } const profileMinStake = await this.blockchain.getProfileMinimumStake(); this.logger.info(`Minimum stake for profile registration is ${profileMinStake}`); - await this.blockchain.increaseProfileApproval(new BN(profileMinStake, 10)); + let initialTokenAmount = null; + if (this.config.initial_deposit_amount) { + initialTokenAmount = new BN(this.config.initial_deposit_amount, 10); + } else { + initialTokenAmount = new BN(profileMinStake, 10); + } + + await this.blockchain.increaseProfileApproval(initialTokenAmount); // set empty identity if there is none const identity = this.config.erc725Identity ? this.config.erc725Identity : new BN(0, 16); - await this.blockchain.createProfile( - this.config.identity, - new BN(profileMinStake, 10), identityExists, identity, - ); + + if (this.config.management_wallet) { + await this.blockchain.createProfile( + this.config.management_wallet, + this.config.identity, + initialTokenAmount, identityExists, identity, + ); + } else { + this.logger.important('Management wallet not set. Creating profile with operating wallet only.' + + ' Please set management one.'); + await this.blockchain.createProfile( + this.config.node_wallet, + this.config.identity, + initialTokenAmount, identityExists, identity, + ); + } if (!identityExists) { const event = await this.blockchain.subscribeToEvent('IdentityCreated', null, 5 * 60 * 1000, null, eventData => Utilities.compareHexStrings(eventData.profile, this.config.node_wallet)); if (event) { @@ -138,48 +174,48 @@ class ProfileService { /** * Deposit token to profile * @param amount + * @deprecated * @returns {Promise} */ async depositTokens(amount) { - const walletBalance = await Utilities.getTracTokenBalance( - this.web3, - this.config.node_wallet, - this.blockchain.getTokenContractAddress(), - ); - - if (amount > parseFloat(walletBalance)) { - throw new Error(`Wallet balance: ${walletBalance} TRAC`); - } - - const mTRAC = this.web3.utils.toWei(amount.toString(), 'ether'); - await this.commandExecutor.add({ - name: 'profileApprovalIncreaseCommand', - sequence: [ - 'depositTokensCommand', - ], - delay: 0, - data: { - amount: mTRAC, - }, - transactional: false, - }); - this.logger.notify(`Deposit for amount ${amount} initiated.`); + throw new Error('OT Node does not support deposit functionality anymore'); } /** * Withdraw tokens from profile to identity * @param amount + * @deprecated * @return {Promise} */ async withdrawTokens(amount) { - const mTRAC = this.web3.utils.toWei(amount.toString(), 'ether'); - await this.commandExecutor.add({ - name: 'tokenWithdrawalStartCommand', - data: { - amount: mTRAC, - }, - }); - this.logger.info(`Token withdrawal started for amount ${amount}.`); + throw new Error('OT Node does not support withdrawal functionality anymore'); + } + + /** + * Check for ERC725 identity version and executes upgrade of the profile. + * @return {Promise} + */ + async upgradeProfile() { + if (await this.blockchain.isErc725IdentityOld(this.config.erc725Identity)) { + this.logger.important('Old profile detected. Upgrading to new one.'); + try { + const result = await this.blockchain.transferProfile(this.config.erc725Identity); + const newErc725Identity = + Utilities.normalizeHex(result.logs[1].data.substr( + result.logs[1].data.length - 40, + 40, + )); + + this.logger.important('**************************************************************************'); + this.logger.important(`Your ERC725 profile has been upgraded and now has the new address: ${newErc725Identity}`); + this.logger.important('Please backup your ERC725 identity file.'); + this.logger.important('**************************************************************************'); + this.config.erc725Identity = newErc725Identity; + this._saveIdentity(newErc725Identity); + } catch (transferError) { + throw Error(`Failed to transfer profile. ${transferError}. ${transferError.stack}`); + } + } } } diff --git a/modules/service/rest-api-service.js b/modules/service/rest-api-service.js index 1f90f7237..e194a56cf 100644 --- a/modules/service/rest-api-service.js +++ b/modules/service/rest-api-service.js @@ -400,40 +400,6 @@ class RestAPIService { }); }); - - server.post('/api/deposit', (req, res) => { - this.logger.api('POST: Deposit tokens request received.'); - - if (req.body !== null && typeof req.body.trac_amount === 'number' - && req.body.trac_amount > 0) { - const { trac_amount } = req.body; - emitter.emit('api-deposit-tokens', { - trac_amount, - response: res, - }); - } else { - res.status(400); - res.send({ message: 'Bad request' }); - } - }); - - - server.post('/api/withdraw', (req, res) => { - this.logger.api('POST: Withdraw tokens request received.'); - - if (req.body !== null && typeof req.body.trac_amount === 'number' - && req.body.trac_amount > 0) { - const { trac_amount } = req.body; - emitter.emit('api-withdraw-tokens', { - trac_amount, - response: res, - }); - } else { - res.status(400); - res.send({ message: 'Bad request' }); - } - }); - server.get('/api/import_info', async (req, res) => { await importController.dataSetInfo(req, res); }); diff --git a/ot-node.js b/ot-node.js index a8690545c..3e4c9cc45 100644 --- a/ot-node.js +++ b/ot-node.js @@ -327,7 +327,7 @@ class OTNode { context = container.cradle; - container.loadModules(['modules/command/**/*.js', 'modules/controller/**/*.js', 'modules/service/**/*.js', 'modules/Blockchain/plugin/hyperledger/*.js'], { + container.loadModules(['modules/command/**/*.js', 'modules/controller/**/*.js', 'modules/service/**/*.js', 'modules/Blockchain/plugin/hyperledger/*.js', 'modules/migration/*.js'], { formatName: 'camelCase', resolverOptions: { lifetime: awilix.Lifetime.SINGLETON, @@ -379,35 +379,6 @@ class OTNode { emitter.initialize(); - // check does node_wallet has sufficient funds - 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); - } - }, 1800000); - // Connecting to graph database const graphStorage = container.resolve('graphStorage'); try { @@ -443,6 +414,7 @@ class OTNode { try { await profileService.initProfile(); + await profileService.upgradeProfile(); } catch (e) { log.error('Failed to create profile'); console.log(e); @@ -475,6 +447,8 @@ class OTNode { await remoteControl.connect(); } + await this._runMigration(container); + const commandExecutor = container.resolve('commandExecutor'); await commandExecutor.init(); await commandExecutor.replay(); @@ -482,6 +456,43 @@ class OTNode { appState.started = true; } + /** + * Run one time migration + * Note: implement migration service + * @param container + * @deprecated + * @private + */ + async _runMigration(container) { + const config = container.resolve('config'); + + const migrationsStartedMills = Date.now(); + log.info('Initializing code migrations...'); + + const m1PayoutAllMigrationFilename = '0_m1PayoutAllMigrationFile'; + const migrationDir = path.join(config.appDataPath, 'migrations'); + const migrationFilePath = path.join(migrationDir, m1PayoutAllMigrationFilename); + if (!fs.existsSync(migrationFilePath)) { + const m1PayoutAllMigration = container.resolve('m1PayoutAllMigration'); + + try { + await m1PayoutAllMigration.run(); + log.warn(`One-time payout migration completed. Lasted ${Date.now() - migrationsStartedMills} millisecond(s)`); + + await Utilities.writeContentsToFile( + migrationDir, m1PayoutAllMigrationFilename, + JSON.stringify({ + status: 'COMPLETED', + }), + ); + } catch (e) { + log.error(`Failed to run code migrations. Lasted ${Date.now() - migrationsStartedMills} millisecond(s). ${e.message}`); + } + } + + log.info(`Code migrations completed. Lasted ${Date.now() - migrationsStartedMills}`); + } + /** * Starts bootstrap node * @return {Promise} @@ -491,7 +502,7 @@ class OTNode { injectionMode: awilix.InjectionMode.PROXY, }); - container.loadModules(['modules/Blockchain/plugin/hyperledger/*.js'], { + container.loadModules(['modules/Blockchain/plugin/hyperledger/*.js', 'modules/migration/*.js'], { formatName: 'camelCase', resolverOptions: { lifetime: awilix.Lifetime.SINGLETON, diff --git a/test/bdd/features/endpoints.feature b/test/bdd/features/endpoints.feature index 89f1d2834..6d3840cc4 100644 --- a/test/bdd/features/endpoints.feature +++ b/test/bdd/features/endpoints.feature @@ -3,22 +3,6 @@ Feature: API endpoints features Given the blockchain is set up And 1 bootstrap is running - @first - Scenario: Smoke check /api/withdraw endpoint - Given I setup 1 node - And I start the node - And I use 1st node as DC - 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 - - @first - Scenario: Smoke check /api/deposit endpoint - Given I setup 1 node - And I start the node - And I use 1st node as DC - 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 - @first Scenario: Smoke check /api/consensus endpoint Given I setup 1 node diff --git a/test/bdd/steps/endpoints.js b/test/bdd/steps/endpoints.js index 6f03cc6bd..eaf481755 100644 --- a/test/bdd/steps/endpoints.js +++ b/test/bdd/steps/endpoints.js @@ -179,44 +179,6 @@ Given(/^the ([DV|DV2]+) purchases (last import|second last import) from the last .catch(error => done(error)); }); -Given(/^I attempt to withdraw (\d+) tokens from DC profile[s]*$/, { timeout: 420000 }, async function (tokenCount) { - // TODO expect tokenCount < profileBalance - expect(!!this.state.dc, 'DC node not defined. Use other step to define it.').to.be.equal(true); - - const { dc } = this.state; - const host = dc.state.node_rpc_url; - - const promises = []; - promises.push(new Promise((accept, reject) => { - dc.once('withdraw-initiated', () => accept()); - })); - promises.push(new Promise((accept, reject) => { - dc.once('withdraw-completed', () => accept()); - })); - promises.push(new Promise((accept, reject) => { - dc.once('withdraw-command-completed', () => accept()); - })); - await httpApiHelper.apiWithdraw(host, tokenCount); - return Promise.all(promises); -}); - -Given(/^I attempt to deposit (\d+) tokens from DC wallet[s]*$/, { timeout: 120000 }, async function (tokenCount) { - // TODO expect tokenCount < walletBalance - expect(!!this.state.dc, 'DC node not defined. Use other step to define it.').to.be.equal(true); - const { dc } = this.state; - const host = dc.state.node_rpc_url; - - const promises = []; - promises.push(new Promise((accept, reject) => { - dc.once('deposit-approved', () => accept()); - })); - promises.push(new Promise((accept, reject) => { - dc.once('deposit-command-completed', () => accept()); - })); - await httpApiHelper.apiDeposit(host, tokenCount); - return Promise.all(promises); -}); - Given(/^([DC|DH|DV]+) calls consensus endpoint for sender: "(\S+)"$/, async function (nodeType, senderId) { expect(nodeType, 'Node type can only be DC, DH, DV.').to.be.oneOf(['DC', 'DH', 'DV']); diff --git a/test/bdd/steps/lib/http-api-helper.js b/test/bdd/steps/lib/http-api-helper.js index b9a6cca97..3fe7b499e 100644 --- a/test/bdd/steps/lib/http-api-helper.js +++ b/test/bdd/steps/lib/http-api-helper.js @@ -366,78 +366,6 @@ async function apiReadNetwork(nodeRpcUrl, queryId, replyId, dataSetId) { }); } -/** - * @typedef {Object} WithdrawResponse - * @property {string} message informing that withdraw process was initiated. - */ - -/** - * Fetch /api/withdraw response - * - * @param {string} nodeRpcUrl URL in following format http://host:port - * @param {number} tokenCount - * @return {Promise.} - */ -async function apiWithdraw(nodeRpcUrl, tokenCount) { - return new Promise((accept, reject) => { - const jsonQuery = { - trac_amount: tokenCount, - }; - request( - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - uri: `${nodeRpcUrl}/api/withdraw`, - json: true, - body: jsonQuery, - }, - (err, res, body) => { - if (err) { - reject(err); - return; - } - accept(body); - }, - ); - }); -} - -/** - * @typedef {Object} DepositResponse - * @property {string} message informing that deposit process went fine. - */ - -/** - * Fetch /api/deposit response - * - * @param {string} nodeRpcUrl URL in following format http://host:port - * @param {number} tokenCount - * @return {Promise.} - */ -async function apiDeposit(nodeRpcUrl, tokenCount) { - return new Promise((accept, reject) => { - const jsonQuery = { - trac_amount: tokenCount, - }; - request( - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - uri: `${nodeRpcUrl}/api/deposit`, - json: true, - body: jsonQuery, - }, - (err, res, body) => { - if (err) { - reject(err); - return; - } - accept(body); - }, - ); - }); -} - /** * @typedef {Object} ConsensusResponse * @property {Object} events an array of events with side1 and/or side2 objects. @@ -546,8 +474,6 @@ module.exports = { apiQueryNetwork, apiQueryNetworkResponses, apiReadNetwork, - apiWithdraw, - apiDeposit, apiConsensus, apiTrail, apiNodeInfo, diff --git a/test/bdd/steps/lib/local-blockchain.js b/test/bdd/steps/lib/local-blockchain.js index 2074ed302..b9cf6d96d 100644 --- a/test/bdd/steps/lib/local-blockchain.js +++ b/test/bdd/steps/lib/local-blockchain.js @@ -99,6 +99,7 @@ class LocalBlockchain { constructor(options = {}) { this.logger = options.logger || console; this.server = Ganache.server({ + gasLimit: 7000000, accounts: accountPrivateKeys.map(account => ({ secretKey: `0x${account}`, @@ -302,7 +303,7 @@ class LocalBlockchain { data: contractData, arguments: constructorArguments, }) - .send({ from: deployerAddress, gas: 6000000 }) + .send({ from: deployerAddress, gas: 6900000 }) .on('receipt', (receipt) => { deploymentReceipt = receipt; }) diff --git a/test/bdd/steps/network.js b/test/bdd/steps/network.js index 9ab5f3372..b6d928a31 100644 --- a/test/bdd/steps/network.js +++ b/test/bdd/steps/network.js @@ -83,6 +83,7 @@ Given(/^(\d+) bootstrap is running$/, { timeout: 80000 }, function (nodeCount, d bootstraps: ['https://localhost:5278/#ff62cb1f692431d901833d55b93c7d991b4087f1'], remoteWhitelist: ['localhost', '127.0.0.1'], }, + initial_deposit_amount: '10000000000000000000000', }, appDataBaseDir: this.parameters.appDataBaseDir, @@ -121,6 +122,7 @@ Given(/^I setup (\d+) node[s]*$/, { timeout: 120000 }, function (nodeCount, done }, local_network_only: true, dc_choose_time: 60000, // 1 minute + initial_deposit_amount: '10000000000000000000000', }; const newNode = new OtNode({ @@ -534,6 +536,7 @@ Given(/^I additionally setup (\d+) node[s]*$/, { timeout: 60000 }, function (nod rpc_node_port: 7545, }, local_network_only: true, + initial_deposit_amount: '10000000000000000000000', }, appDataBaseDir: this.parameters.appDataBaseDir, }); diff --git a/test/protocol/protocol.test.js b/test/protocol/protocol.test.js index 95f8d2ffa..3d84b311c 100644 --- a/test/protocol/protocol.test.js +++ b/test/protocol/protocol.test.js @@ -26,8 +26,6 @@ const sequelizeConfig = require('./../../config/sequelizeConfig').development; const CommandResolver = require('../../modules/command/command-resolver'); const CommandExecutor = require('../../modules/command/command-executor'); -const DepositTokenCommand = require('../../modules/command/common/deposit-tokens-command'); - const DCService = require('../../modules/service/dc-service'); // Thanks solc. At least this works! @@ -451,7 +449,6 @@ describe.skip('Protocol tests', () => { remoteControl: awilix.asClass(MockRemoteControl), commandExecutor: awilix.asClass(CommandExecutor).singleton(), commandResolver: awilix.asClass(CommandResolver).singleton(), - depositTokenCommand: awilix.asClass(DepositTokenCommand).singleton(), dcService: awilix.asClass(DCService).singleton(), notifyError: awilix.asFunction(() => (error) => { throw error; }), }); From f94b0eb4536afc9e935fbf7dfd391c083a47c511 Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Sat, 15 Dec 2018 01:47:47 +0100 Subject: [PATCH 16/48] Update Profile and Holding contracts to latest version (#810) * Prepare script for profile contract deployment * Remove caching of contract addresses from Holding and Profile contracts * Enable migrations on rinkeby * Move reserveTokens function from Profile to Holding SC to balance gas requirements --- .../Blockchain/Ethereum/contracts/Holding.sol | 74 +++++++++++++---- .../Blockchain/Ethereum/contracts/Profile.sol | 81 +++++-------------- .../Ethereum/migrations/2_total_migration.js | 12 ++- .../Blockchain/Ethereum/test/profile.test.js | 2 +- 4 files changed, 91 insertions(+), 78 deletions(-) diff --git a/modules/Blockchain/Ethereum/contracts/Holding.sol b/modules/Blockchain/Ethereum/contracts/Holding.sol index ee3c2acce..4af3499c1 100644 --- a/modules/Blockchain/Ethereum/contracts/Holding.sol +++ b/modules/Blockchain/Ethereum/contracts/Holding.sol @@ -12,17 +12,11 @@ contract Holding is Ownable { using SafeMath for uint256; Hub public hub; - HoldingStorage public holdingStorage; - ProfileStorage public profileStorage; - Profile public profile; uint256 public difficultyOverride; constructor(address hubAddress) public{ hub = Hub(hubAddress); - holdingStorage = HoldingStorage(hub.holdingStorageAddress()); - profileStorage = ProfileStorage(hub.profileStorageAddress()); - profile = Profile(hub.profileAddress()); difficultyOverride = 0; } @@ -48,10 +42,10 @@ contract Holding is Ownable { require(litigationIntervalInMinutes > 0, "Litigation time cannot be zero"); // Writing data root hash if it wasn't previously set - if(holdingStorage.fingerprint(bytes32(dataSetId)) == bytes32(0)){ - holdingStorage.setFingerprint(bytes32(dataSetId), bytes32(dataRootHash)); + if(HoldingStorage(hub.holdingStorageAddress()).fingerprint(bytes32(dataSetId)) == bytes32(0)){ + HoldingStorage(hub.holdingStorageAddress()).setFingerprint(bytes32(dataSetId), bytes32(dataRootHash)); } else { - require(bytes32(dataRootHash) == holdingStorage.fingerprint(bytes32(dataSetId)), + require(bytes32(dataRootHash) == HoldingStorage(hub.holdingStorageAddress()).fingerprint(bytes32(dataSetId)), "Cannot create offer with different data root hash!"); } @@ -59,20 +53,19 @@ contract Holding is Ownable { // We consider a pair of dataSet and identity unique within one block, hence the formula for offerId bytes32 offerId = keccak256(abi.encodePacked(bytes32(dataSetId), identity, blockhash(block.number - 1))); - //We calculate the task for the data creator to solve //Calculating task difficulty uint256 difficulty; if(difficultyOverride != 0) difficulty = difficultyOverride; else { - if(logs2(profileStorage.activeNodes()) <= 4) difficulty = 1; + if(logs2(ProfileStorage(hub.profileStorageAddress()).activeNodes()) <= 4) difficulty = 1; else { - difficulty = 4 + (((logs2(profileStorage.activeNodes()) - 4) * 10000) / 13219); + difficulty = 4 + (((logs2(ProfileStorage(hub.profileStorageAddress()).activeNodes()) - 4) * 10000) / 13219); } } // Writing variables into storage - holdingStorage.setOfferParameters( + HoldingStorage(hub.holdingStorageAddress()).setOfferParameters( offerId, identity, bytes32(dataSetId), @@ -82,7 +75,7 @@ contract Holding is Ownable { difficulty ); - holdingStorage.setOfferLitigationHashes( + HoldingStorage(hub.holdingStorageAddress()).setOfferLitigationHashes( offerId, bytes32(redLitigationHash), bytes32(greenLitigationHash), @@ -97,6 +90,8 @@ contract Holding is Ownable { bytes confirmation1, bytes confirmation2, bytes confirmation3, uint8[] encryptionType, address[] holderIdentity) public { + HoldingStorage holdingStorage = HoldingStorage(hub.holdingStorageAddress()); + // Verify sender require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2)); require(identity == holdingStorage.getOfferCreator(bytes32(offerId)), "Offer can only be finalized by its creator!"); @@ -111,7 +106,7 @@ contract Holding is Ownable { == holdingStorage.getOfferTask(bytes32(offerId)), "Submitted identities do not answer the task correctly!"); // Secure funds from all parties - profile.reserveTokens( + reserveTokens( identity, holderIdentity[0], holderIdentity[1], @@ -125,12 +120,61 @@ contract Holding is Ownable { emit OfferFinalized(bytes32(offerId), holderIdentity[0], holderIdentity[1], holderIdentity[2]); } + function reserveTokens(address payer, address identity1, address identity2, address identity3, uint256 amount) + internal { + ProfileStorage profileStorage = ProfileStorage(hub.profileStorageAddress()); + + if(profileStorage.getWithdrawalPending(payer)) { + profileStorage.setWithdrawalPending(payer,false); + } + if(profileStorage.getWithdrawalPending(identity1)) { + profileStorage.setWithdrawalPending(identity1,false); + } + if(profileStorage.getWithdrawalPending(identity2)) { + profileStorage.setWithdrawalPending(identity2,false); + } + if(profileStorage.getWithdrawalPending(identity3)) { + profileStorage.setWithdrawalPending(identity3,false); + } + + uint256 minimalStake = Profile(hub.profileAddress()).minimalStake(); + + require(minimalStake <= profileStorage.getStake(payer).sub(profileStorage.getStakeReserved(payer)), + "Data creator does not have enough stake to take new jobs!"); + require(minimalStake <= profileStorage.getStake(identity1).sub(profileStorage.getStakeReserved(identity1)), + "First profile does not have enough stake to take new jobs!"); + require(minimalStake <= profileStorage.getStake(identity2).sub(profileStorage.getStakeReserved(identity2)), + "Second profile does not have enough stake to take new jobs!"); + require(minimalStake <= profileStorage.getStake(identity3).sub(profileStorage.getStakeReserved(identity3)), + "Third profile does not have enough stake to take new jobs!"); + + require(profileStorage.getStake(payer).sub(profileStorage.getStakeReserved(payer)) >= amount.mul(3), + "Data creator does not have enough stake for reserving!"); + require(profileStorage.getStake(identity1).sub(profileStorage.getStakeReserved(identity1)) >= amount, + "First profile does not have enough stake for reserving!"); + require(profileStorage.getStake(identity2).sub(profileStorage.getStakeReserved(identity2)) >= amount, + "Second profile does not have enough stake for reserving!"); + require(profileStorage.getStake(identity3).sub(profileStorage.getStakeReserved(identity3)) >= amount, + "Third profile does not have enough stake for reserving!"); + + + profileStorage.increaseStakesReserved( + payer, + identity1, + identity2, + identity3, + amount + ); + } + function payOut(address identity, uint256 offerId) public { // Verify sender require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2)); require(Approval(hub.approvalAddress()).identityHasApproval(identity), "Identity does not have approval for using the contract"); + HoldingStorage holdingStorage = HoldingStorage(hub.holdingStorageAddress()); + // Verify holder uint256 amountToTransfer = holdingStorage.getHolderStakedAmount(bytes32(offerId), identity); require(amountToTransfer > 0, "Sender is not holding this data set!"); diff --git a/modules/Blockchain/Ethereum/contracts/Profile.sol b/modules/Blockchain/Ethereum/contracts/Profile.sol index 8a6c03fe5..05dc9768c 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -10,7 +10,6 @@ import {ERC20} from './TracToken.sol'; contract Profile { using SafeMath for uint256; Hub public hub; - ProfileStorage public profileStorage; uint256 public version = 100; @@ -20,7 +19,6 @@ contract Profile { constructor(address hubAddress) public { require(hubAddress != address(0)); hub = Hub(hubAddress); - profileStorage = ProfileStorage(hub.profileStorageAddress()); } modifier onlyHolding(){ @@ -50,14 +48,14 @@ contract Profile { 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); + tokenContract.transferFrom(msg.sender, hub.profileStorageAddress(), initialBalance); if(!senderHas725) { Identity newIdentity = new Identity(msg.sender, managementWallet); emit IdentityCreated(msg.sender, address(newIdentity)); - profileStorage.setStake(address(newIdentity), initialBalance); - profileStorage.setNodeId(address(newIdentity), profileNodeId); + ProfileStorage(hub.profileStorageAddress()).setStake(address(newIdentity), initialBalance); + ProfileStorage(hub.profileStorageAddress()).setNodeId(address(newIdentity), profileNodeId); emit ProfileCreated(address(newIdentity), initialBalance); } @@ -65,16 +63,16 @@ contract Profile { // Verify sender require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2), "Sender does not have action permission for identity!"); - profileStorage.setStake(identity, initialBalance); - profileStorage.setNodeId(identity, profileNodeId); + ProfileStorage(hub.profileStorageAddress()).setStake(identity, initialBalance); + ProfileStorage(hub.profileStorageAddress()).setNodeId(identity, profileNodeId); emit ProfileCreated(identity, initialBalance); } if(initialBalance > minimalStake) { - uint256 activeNodes = profileStorage.activeNodes(); + uint256 activeNodes = ProfileStorage(hub.profileStorageAddress()).activeNodes(); activeNodes += 1; - profileStorage.setActiveNodes(activeNodes); + ProfileStorage(hub.profileStorageAddress()).setActiveNodes(activeNodes); } } @@ -84,6 +82,8 @@ contract Profile { Identity newIdentity = new Identity(msg.sender, msg.sender); emit IdentityCreated(msg.sender, address(newIdentity)); + ProfileStorage profileStorage = ProfileStorage(hub.profileStorageAddress()); + profileStorage.setStake(address(newIdentity), profileStorage.getStake(oldIdentity)); profileStorage.setStakeReserved(address(newIdentity), profileStorage.getStakeReserved(oldIdentity)); profileStorage.setNodeId(address(newIdentity), profileStorage.getNodeId(oldIdentity)); @@ -99,6 +99,8 @@ contract Profile { function depositTokens(address identity, uint256 amount) public { // Verify sender require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); + + ProfileStorage profileStorage = ProfileStorage(hub.profileStorageAddress()); ERC20 tokenContract = ERC20(hub.tokenAddress()); require(tokenContract.allowance(msg.sender, this) >= amount, "Sender allowance must be equal to or higher than chosen amount"); @@ -115,6 +117,8 @@ contract Profile { // Verify sender require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); + ProfileStorage profileStorage = ProfileStorage(hub.profileStorageAddress()); + require(profileStorage.getWithdrawalPending(identity) == false, "Withrdrawal process already pending!"); uint256 availableBalance = profileStorage.getStake(identity).sub(profileStorage.getStakeReserved(identity)); @@ -137,6 +141,8 @@ contract Profile { // Verify sender require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); + ProfileStorage profileStorage = ProfileStorage(hub.profileStorageAddress()); + require(profileStorage.getWithdrawalPending(identity) == true, "Cannot withdraw tokens before starting token withdrawal!"); require(profileStorage.getWithdrawalTimestamp(identity) < block.timestamp, "Cannot withdraw tokens before withdrawal timestamp!"); @@ -164,62 +170,13 @@ contract Profile { "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)) { - profileStorage.setWithdrawalPending(payer,false); - emit TokenWithdrawalCancelled(payer); - } - if(profileStorage.getWithdrawalPending(identity1)) { - profileStorage.setWithdrawalPending(identity1,false); - emit TokenWithdrawalCancelled(identity1); - } - if(profileStorage.getWithdrawalPending(identity2)) { - profileStorage.setWithdrawalPending(identity2,false); - emit TokenWithdrawalCancelled(identity2); - } - if(profileStorage.getWithdrawalPending(identity3)) { - profileStorage.setWithdrawalPending(identity3,false); - emit TokenWithdrawalCancelled(identity3); - } - - require(minimalStake <= profileStorage.getStake(payer).sub(profileStorage.getStakeReserved(payer)), - "Data creator does not have enough stake to take new jobs!"); - require(minimalStake <= profileStorage.getStake(identity1).sub(profileStorage.getStakeReserved(identity1)), - "First profile does not have enough stake to take new jobs!"); - require(minimalStake <= profileStorage.getStake(identity2).sub(profileStorage.getStakeReserved(identity2)), - "Second profile does not have enough stake to take new jobs!"); - require(minimalStake <= profileStorage.getStake(identity3).sub(profileStorage.getStakeReserved(identity3)), - "Third profile does not have enough stake to take new jobs!"); - - require(profileStorage.getStake(payer).sub(profileStorage.getStakeReserved(payer)) >= amount.mul(3), - "Data creator does not have enough stake for reserving!"); - require(profileStorage.getStake(identity1).sub(profileStorage.getStakeReserved(identity1)) >= amount, - "First profile does not have enough stake for reserving!"); - require(profileStorage.getStake(identity2).sub(profileStorage.getStakeReserved(identity2)) >= amount, - "Second profile does not have enough stake for reserving!"); - require(profileStorage.getStake(identity3).sub(profileStorage.getStakeReserved(identity3)) >= amount, - "Third profile does not have enough stake for reserving!"); - - - profileStorage.increaseStakesReserved( - payer, - identity1, - identity2, - identity3, - amount - ); - emit TokensReserved(payer, amount.mul(3)); - emit TokensReserved(identity1, amount); - emit TokensReserved(identity2, amount); - emit TokensReserved(identity3, amount); + ProfileStorage(hub.profileStorageAddress()).setNodeId(identity, newNodeId); } function releaseTokens(address profile, uint256 amount) public onlyHolding { + ProfileStorage profileStorage = ProfileStorage(hub.profileStorageAddress()); + require(profileStorage.getStakeReserved(profile) >= amount, "Cannot release more tokens than there are reserved"); profileStorage.setStakeReserved(profile, profileStorage.getStakeReserved(profile).sub(amount)); @@ -229,6 +186,8 @@ contract Profile { function transferTokens(address sender, address receiver, uint256 amount) public onlyHolding { + ProfileStorage profileStorage = ProfileStorage(hub.profileStorageAddress()); + require(profileStorage.getStake(sender) >= amount, "Sender does not have enough tokens to transfer!"); require(profileStorage.getStakeReserved(sender) >= amount, "Sender does not have enough tokens reserved to transfer!"); diff --git a/modules/Blockchain/Ethereum/migrations/2_total_migration.js b/modules/Blockchain/Ethereum/migrations/2_total_migration.js index 8ec514077..44e986185 100644 --- a/modules/Blockchain/Ethereum/migrations/2_total_migration.js +++ b/modules/Blockchain/Ethereum/migrations/2_total_migration.js @@ -197,7 +197,7 @@ module.exports = async (deployer, network, accounts) => { ); await hub.setHoldingStorageAddress(holdingStorage.address); - profile = await deployer.deploy(Profile, hub.address, { gas: 6000000, from: accounts[0] }); + profile = await deployer.deploy(Profile, hub.address, { gas: 7000000, from: accounts[0] }); await hub.setProfileAddress(profile.address); holding = await deployer.deploy(Holding, hub.address, { gas: 6000000, from: accounts[0] }); @@ -217,6 +217,7 @@ module.exports = async (deployer, network, accounts) => { break; case 'live': + /* await deployer.deploy(Hub, { gas: 6000000, from: accounts[0] }) .then((result) => { hub = result; @@ -256,6 +257,15 @@ module.exports = async (deployer, network, accounts) => { console.log(`\t ProfileStorage contract address: \t${profileStorage.address}`); console.log(`\t HoldingStorage contract address: \t${holdingStorage.address}`); + */ + + hub = await Hub.at('0xa287d7134fb40bef071c932286bd2cd01efccf30'); + console.log(JSON.stringify(hub)); + // profile = await deployer.deploy( + // Profile, + // hub.address, + // { gas: 6000000, gasPrice: 8000000000 }, + // ); break; default: console.warn('Please use one of the following network identifiers: ganache, mock, test, or rinkeby'); diff --git a/modules/Blockchain/Ethereum/test/profile.test.js b/modules/Blockchain/Ethereum/test/profile.test.js index b52a661a3..7b434eae4 100644 --- a/modules/Blockchain/Ethereum/test/profile.test.js +++ b/modules/Blockchain/Ethereum/test/profile.test.js @@ -311,7 +311,7 @@ contract('Profile contract testing', async (accounts) => { await hub.setHoldingAddress(accounts[0]); const amountToReserve = new BN(100); - await profile.reserveTokens( + await profileStorage.increaseStakesReserved( identities[0], identities[1], identities[2], From e8f2e4296258c23be1788a854f8149c2d72d1f26 Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Sat, 15 Dec 2018 01:52:34 +0100 Subject: [PATCH 17/48] Version 2.0.34 --- 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 7b2c24fce..375a1562e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.33", + "version": "2.0.34", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 528f80d94..f478fb50d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.33", + "version": "2.0.34", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From 053389f3830b5bdcbc7b4ed78a23eeb60262ed8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20Obradovi=C4=87?= Date: Sat, 15 Dec 2018 03:15:12 +0100 Subject: [PATCH 18/48] Identity update (#811) * Add versioning to ERC725 identity * Detect old contract * fix dc funds checks --- modules/Blockchain/Ethereum/abi/erc725.json | 614 +++++++++--------- .../Blockchain/Ethereum/contracts/ERC725.sol | 2 + .../Ethereum/contracts/Identity.sol | 2 + modules/Blockchain/Ethereum/index.js | 18 +- modules/service/dc-service.js | 16 +- 5 files changed, 340 insertions(+), 312 deletions(-) diff --git a/modules/Blockchain/Ethereum/abi/erc725.json b/modules/Blockchain/Ethereum/abi/erc725.json index 64843ffaa..e20df6e55 100644 --- a/modules/Blockchain/Ethereum/abi/erc725.json +++ b/modules/Blockchain/Ethereum/abi/erc725.json @@ -1,301 +1,315 @@ [ - { - "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 + { + "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": "otVersion", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "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": "_key", + "type": "bytes32" + } + ], + "name": "removeKey", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_purpose", + "type": "uint256" + } + ], + "name": "getKeysByPurpose", + "outputs": [ + { + "name": "keys", + "type": "bytes32[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "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": "_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": true, + "inputs": [ + { + "name": "_key", + "type": "bytes32" + }, + { + "name": "_purpose", + "type": "uint256" + } + ], + "name": "keyHasPurpose", + "outputs": [ + { + "name": "result", + "type": "bool" + } + ], + "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" + }, + { + "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" + } +] \ No newline at end of file diff --git a/modules/Blockchain/Ethereum/contracts/ERC725.sol b/modules/Blockchain/Ethereum/contracts/ERC725.sol index 5947ae2d1..eba56ef70 100644 --- a/modules/Blockchain/Ethereum/contracts/ERC725.sol +++ b/modules/Blockchain/Ethereum/contracts/ERC725.sol @@ -14,6 +14,8 @@ contract ERC725 { bytes32 key; } + uint256 public otVersion; + // Events event KeyAdded(bytes32 indexed key, uint256[] purposes, uint256 indexed keyType); event KeyRemoved(bytes32 indexed key, uint256[] purposes, uint256 indexed keyType); diff --git a/modules/Blockchain/Ethereum/contracts/Identity.sol b/modules/Blockchain/Ethereum/contracts/Identity.sol index 76def1de0..401935cca 100644 --- a/modules/Blockchain/Ethereum/contracts/Identity.sol +++ b/modules/Blockchain/Ethereum/contracts/Identity.sol @@ -25,6 +25,8 @@ contract Identity is ERC725 { constructor(address operational, address management) public { require(operational != address(0) && management != address(0)); + otVersion = 1; + bytes32 _management_key = keccak256(abi.encodePacked(management)); keys[_management_key].key = _management_key; diff --git a/modules/Blockchain/Ethereum/index.js b/modules/Blockchain/Ethereum/index.js index 4311714f4..e0f0df360 100644 --- a/modules/Blockchain/Ethereum/index.js +++ b/modules/Blockchain/Ethereum/index.js @@ -5,9 +5,6 @@ const Models = require('../../../models'); const Op = require('sequelize/lib/operators'); const uuidv4 = require('uuid/v4'); const ethereumAbi = require('ethereumjs-abi'); -const md5 = require('md5'); - -const oldErc725codeMd5 = '4fbb9cd89fa6b148d1f0c3b96fdadcfc'; class Ethereum { /** @@ -1048,7 +1045,20 @@ class Ethereum { * @return {Promise} */ async isErc725IdentityOld(address) { - return oldErc725codeMd5 === md5(await this.web3.eth.getCode(address)); + const erc725IdentityContract = new this.web3.eth.Contract( + this.erc725IdentityContractAbi, + address, + ); + + try { + await erc725IdentityContract.methods.otVersion().call(); + return false; + } catch (error) { + if (error.toString().includes('Couldn\'t decode uint256 from ABI: 0x')) { + return true; + } + throw error; + } } } diff --git a/modules/service/dc-service.js b/modules/service/dc-service.js index 557fc1f70..4087e647e 100644 --- a/modules/service/dc-service.js +++ b/modules/service/dc-service.js @@ -31,13 +31,6 @@ class DCService { dataSetId, dataRootHash, holdingTimeInMinutes, tokenAmountPerHolder, dataSizeInBytes, litigationIntervalInMinutes, ) { - const hasFunds = await this.hasProfileBalanceForOffer(tokenAmountPerHolder); - if (!hasFunds) { - const message = 'Not enough tokens. To replicate data please deposit more tokens to your profile.'; - this.logger.warn(message); - throw new Error(message); - } - const offer = await models.offers.create({ data_set_id: dataSetId, message: 'Offer is pending', @@ -56,6 +49,13 @@ class DCService { litigationIntervalInMinutes = new BN(this.config.dc_litigation_interval_in_minutes, 10); } + const hasFunds = await this.hasProfileBalanceForOffer(tokenAmountPerHolder); + if (!hasFunds) { + const message = 'Not enough tokens. To replicate data please deposit more tokens to your profile.'; + this.logger.warn(message); + throw new Error(message); + } + const commandData = { internalOfferId: offer.id, dataSetId, @@ -134,7 +134,7 @@ class DCService { } const profileMinStake = new BN(await this.blockchain.getProfileMinimumStake(), 10); - if (profileStake.sub(profileStakeReserved).lt(profileMinStake)) { + if (profileStake.sub(profileStakeReserved).sub(offerStake).lt(profileMinStake)) { const stakeRemainder = profileMinStake.sub(profileStake.sub(profileStakeReserved)); if (!remainder || (remainder && remainder.lt(stakeRemainder))) { remainder = stakeRemainder; From 85629684bba9a44e5ae118bfa8b6de4180c8d851 Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Sat, 15 Dec 2018 03:16:43 +0100 Subject: [PATCH 19/48] Version 2.0.35 --- 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 375a1562e..d531399cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.34", + "version": "2.0.35", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f478fb50d..010cd74a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.34", + "version": "2.0.35", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From 9f2c47c3094c985cfb7d40e92081c7fc9dec3f7a Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Sat, 15 Dec 2018 04:35:02 +0100 Subject: [PATCH 20/48] payout api (#815) --- modules/EventEmitter.js | 28 +++++++++++++++++++ .../command/dh/dh-offer-finalized-command.js | 1 + modules/service/rest-api-service.js | 21 ++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/modules/EventEmitter.js b/modules/EventEmitter.js index f95a848aa..be3aab070 100644 --- a/modules/EventEmitter.js +++ b/modules/EventEmitter.js @@ -96,6 +96,7 @@ class EventEmitter { dcService, dvController, notifyError, + commandExecutor, } = this.ctx; this._on('api-network-query-responses', async (data) => { @@ -501,6 +502,33 @@ class EventEmitter { } }); + this._on('api-payout', async (data) => { + const { offerId } = data; + + logger.info(`Payout called for offer ${offerId}.`); + const bid = await Models.bids.findOne({ where: { offer_id: offerId } }); + if (bid) { + await commandExecutor.add({ + name: 'dhPayOutCommand', + delay: 0, + data: { + offerId, + }, + }); + + data.response.status(200); + data.response.send({ + message: `Payout for offer ${offerId} called. It should be completed shortly.`, + }); + } else { + logger.error(`There is no offer for ID ${offerId}`); + data.response.status(404); + data.response.send({ + message: 'Offer not found', + }); + } + }); + this._on('api-create-offer', async (data) => { const { dataSetId, diff --git a/modules/command/dh/dh-offer-finalized-command.js b/modules/command/dh/dh-offer-finalized-command.js index 945fa1258..cc4b2fb55 100644 --- a/modules/command/dh/dh-offer-finalized-command.js +++ b/modules/command/dh/dh-offer-finalized-command.js @@ -58,6 +58,7 @@ class DhOfferFinalizedCommand extends Command { { name: 'dhPayOutCommand', delay: scheduledTime, + retries: 3, transactional: false, data: { offerId, diff --git a/modules/service/rest-api-service.js b/modules/service/rest-api-service.js index e194a56cf..f6bad1282 100644 --- a/modules/service/rest-api-service.js +++ b/modules/service/rest-api-service.js @@ -435,6 +435,27 @@ class RestAPIService { const { type } = req.body; emitter.emit(type, req, res); }); + + /** + * Payout route + * @param Query params: data_set_id + */ + server.get('/api/payout', (req, res) => { + this.logger.api('GET: Payout request received.'); + + if (!req.query.offer_id) { + res.status(400); + res.send({ + message: 'Param offer_id is required.', + }); + return; + } + + emitter.emit('api-payout', { + offerId: req.query.offer_id, + response: res, + }); + }); } /** From 315434c9f09a7385f1679a059033691269dbe6db Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Sat, 15 Dec 2018 04:35:53 +0100 Subject: [PATCH 21/48] Cover reputation and withdrawals in transferProfile function (#813) --- modules/Blockchain/Ethereum/contracts/Profile.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/Blockchain/Ethereum/contracts/Profile.sol b/modules/Blockchain/Ethereum/contracts/Profile.sol index 43569c49b..cec9527d1 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -87,10 +87,17 @@ contract Profile { profileStorage.setStake(address(newIdentity), profileStorage.getStake(oldIdentity)); profileStorage.setStakeReserved(address(newIdentity), profileStorage.getStakeReserved(oldIdentity)); profileStorage.setNodeId(address(newIdentity), profileStorage.getNodeId(oldIdentity)); + profileStorage.setReputation(address(newIdentity), profileStorage.getReputation(oldIdentity)); + + if(profileStorage.getWithdrawalPending(oldIdentity)){ + emit TokenWithdrawalCancelled(oldIdentity); + profileStorage.setWithdrawalPending(oldIdentity, false); + } profileStorage.setStake(oldIdentity, 0); profileStorage.setStakeReserved(oldIdentity, 0); profileStorage.setNodeId(oldIdentity, bytes32(0)); + profileStorage.setReputation(oldIdentity, 0); emit IdentityTransferred(oldIdentity, newIdentity); return address(newIdentity); From 0c8e74be6e4eb24e3a5d85307ba793a98c6b73e6 Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Sat, 15 Dec 2018 04:36:24 +0100 Subject: [PATCH 22/48] Improve contract resilience (#812) * Added require checks * Added require checks * inor fix --- modules/Blockchain/Ethereum/contracts/Holding.sol | 1 + modules/Blockchain/Ethereum/contracts/HoldingStorage.sol | 7 +++++++ modules/Blockchain/Ethereum/contracts/Profile.sol | 9 ++++++--- modules/Blockchain/Ethereum/contracts/ProfileStorage.sol | 1 + 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/modules/Blockchain/Ethereum/contracts/Holding.sol b/modules/Blockchain/Ethereum/contracts/Holding.sol index 4af3499c1..78c48074a 100644 --- a/modules/Blockchain/Ethereum/contracts/Holding.sol +++ b/modules/Blockchain/Ethereum/contracts/Holding.sol @@ -16,6 +16,7 @@ contract Holding is Ownable { uint256 public difficultyOverride; constructor(address hubAddress) public{ + require(hubAddress!=address(0)); hub = Hub(hubAddress); difficultyOverride = 0; } diff --git a/modules/Blockchain/Ethereum/contracts/HoldingStorage.sol b/modules/Blockchain/Ethereum/contracts/HoldingStorage.sol index ea25aa965..ad1214396 100644 --- a/modules/Blockchain/Ethereum/contracts/HoldingStorage.sol +++ b/modules/Blockchain/Ethereum/contracts/HoldingStorage.sol @@ -177,31 +177,38 @@ contract HoldingStorage { } function setHolderStakedAmount (bytes32 offerId, address identity, uint256 stakedAmount) public onlyContracts { + require(identity!=address(0)); holder[offerId][identity].stakedAmount = stakedAmount; } function setHolderPaidAmount (bytes32 offerId, address identity, uint256 paidAmount) public onlyContracts { + require(identity!=address(0)); holder[offerId][identity].paidAmount = paidAmount; } function setHolderLitigationEncryptionType(bytes32 offerId, address identity, uint256 litigationEncryptionType) public onlyContracts { + require(identity!=address(0)); holder[offerId][identity].litigationEncryptionType = litigationEncryptionType; } function getHolderStakedAmount (bytes32 offerId, address identity) public view returns(uint256 stakedAmount) { + require(identity!=address(0)); return holder[offerId][identity].stakedAmount; } function getHolderPaidAmount (bytes32 offerId, address identity) public view returns(uint256 paidAmount) { + require(identity!=address(0)); return holder[offerId][identity].paidAmount; } function getHolderLitigationEncryptionType(bytes32 offerId, address identity) public view returns(uint256 litigationEncryptionType) { + require(identity!=address(0)); return holder[offerId][identity].litigationEncryptionType; } function setHubAddress(address newHubAddress) public onlyContracts { + require(newHubAddress!=address(0)); hub = Hub(newHubAddress); } } diff --git a/modules/Blockchain/Ethereum/contracts/Profile.sol b/modules/Blockchain/Ethereum/contracts/Profile.sol index cec9527d1..35b93e279 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -34,15 +34,16 @@ contract Profile { event TokensDeposited(address profile, uint256 amountDeposited, uint256 newBalance); event TokensReserved(address profile, uint256 amountReserved); - + event WithdrawalInitiated(address profile, uint256 amount, uint256 withdrawalDelayInSeconds); event TokenWithdrawalCancelled(address profile); event TokensWithdrawn(address profile, uint256 amountWithdrawn, uint256 newBalance); event TokensReleased(address profile, uint256 amount); event TokensTransferred(address sender, address receiver, uint256 amount); - + function createProfile(address managementWallet, bytes32 profileNodeId, uint256 initialBalance, bool senderHas725, address identity) public { + require(managementWallet!=address(0) && identity!=address(0)); 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!"); @@ -182,6 +183,7 @@ contract Profile { function releaseTokens(address profile, uint256 amount) public onlyHolding { + require(profile!=address(0)); ProfileStorage profileStorage = ProfileStorage(hub.profileStorageAddress()); require(profileStorage.getStakeReserved(profile) >= amount, "Cannot release more tokens than there are reserved"); @@ -193,6 +195,7 @@ contract Profile { function transferTokens(address sender, address receiver, uint256 amount) public onlyHolding { + require(sender!=address(0) && receiver!=address(0)); ProfileStorage profileStorage = ProfileStorage(hub.profileStorageAddress()); require(profileStorage.getStake(sender) >= amount, "Sender does not have enough tokens to transfer!"); @@ -216,4 +219,4 @@ contract Profile { require (msg.sender == hub.owner(), "Function can only be called by hub owner!"); if(withdrawalTime != newWithdrawalTime) withdrawalTime = newWithdrawalTime; } -} +} \ No newline at end of file diff --git a/modules/Blockchain/Ethereum/contracts/ProfileStorage.sol b/modules/Blockchain/Ethereum/contracts/ProfileStorage.sol index 84e060f4e..2d919bf26 100644 --- a/modules/Blockchain/Ethereum/contracts/ProfileStorage.sol +++ b/modules/Blockchain/Ethereum/contracts/ProfileStorage.sol @@ -100,6 +100,7 @@ contract ProfileStorage { address identity3, uint256 amount) public onlyContracts { + require(identity1!=address(0) && identity2!=address(0) && identity3!=address(0)); profile[payer].stakeReserved += (amount * 3); profile[identity1].stakeReserved += amount; profile[identity2].stakeReserved += amount; From 7848dbdc87aa5ef00d9422a7e01549d9f024d148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20Obradovi=C4=87?= Date: Sat, 15 Dec 2018 04:36:41 +0100 Subject: [PATCH 23/48] Run payout (#814) * Run payout * fix --- modules/migration/m1-payout-all-migration.js | 26 ++++++++++++++++---- ot-node.js | 13 ++++------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/modules/migration/m1-payout-all-migration.js b/modules/migration/m1-payout-all-migration.js index c225f465e..f2b2aa097 100644 --- a/modules/migration/m1-payout-all-migration.js +++ b/modules/migration/m1-payout-all-migration.js @@ -1,12 +1,15 @@ const models = require('../../models'); +const Models = require('../../models/index'); +const Utilities = require('../Utilities'); /** * Runs all pending payout commands */ class M1PayoutAllMigration { - constructor(ctx) { - this.logger = ctx.logger; - this.payOutHandler = ctx.dhPayOutCommand; + constructor({ logger, blockchain, config }) { + this.logger = logger; + this.config = config; + this.blockchain = blockchain; } /** @@ -22,13 +25,13 @@ class M1PayoutAllMigration { }); for (const pendingPayOut of pendingPayOuts) { - const data = this.payOutHandler.unpack(pendingPayOut.data); + const { data } = pendingPayOut; let retries = 3; while (retries > 0) { try { // eslint-disable-next-line - await this.payOutHandler.execute({ data }); // run payout + await this._payOut(data.offerId); pendingPayOut.status = 'COMPLETED'; pendingPayOut.save({ fields: ['status'], @@ -45,6 +48,19 @@ class M1PayoutAllMigration { } } } + + async _payOut(offerId) { + const bid = await Models.bids.findOne({ + where: { offer_id: offerId, status: 'CHOSEN' }, + }); + if (!bid) { + this.logger.important(`There is no successful bid for offer ${offerId}. Cannot execute payout.`); + return; + } + const blockchainIdentity = Utilities.normalizeHex(this.config.erc725Identity); + await this.blockchain.payOut(blockchainIdentity, offerId); + this.logger.important(`Payout for offer ${offerId} successfully completed.`); + } } module.exports = M1PayoutAllMigration; diff --git a/ot-node.js b/ot-node.js index 3e4c9cc45..0700b6da7 100644 --- a/ot-node.js +++ b/ot-node.js @@ -42,6 +42,7 @@ const ReplicationService = require('./modules/service/replication-service'); const ImportController = require('./modules/controller/import-controller'); const APIUtilities = require('./modules/utility/api-utilities'); const RestAPIService = require('./modules/service/rest-api-service'); +const M1PayoutAllMigration = require('./modules/migration/m1-payout-all-migration'); const pjson = require('./package.json'); const configjson = require('./config/config.json'); @@ -414,6 +415,7 @@ class OTNode { try { await profileService.initProfile(); + await this._runMigration(blockchain); await profileService.upgradeProfile(); } catch (e) { log.error('Failed to create profile'); @@ -447,8 +449,6 @@ class OTNode { await remoteControl.connect(); } - await this._runMigration(container); - const commandExecutor = container.resolve('commandExecutor'); await commandExecutor.init(); await commandExecutor.replay(); @@ -459,13 +459,10 @@ class OTNode { /** * Run one time migration * Note: implement migration service - * @param container * @deprecated * @private */ - async _runMigration(container) { - const config = container.resolve('config'); - + async _runMigration(blockchain) { const migrationsStartedMills = Date.now(); log.info('Initializing code migrations...'); @@ -473,10 +470,10 @@ class OTNode { const migrationDir = path.join(config.appDataPath, 'migrations'); const migrationFilePath = path.join(migrationDir, m1PayoutAllMigrationFilename); if (!fs.existsSync(migrationFilePath)) { - const m1PayoutAllMigration = container.resolve('m1PayoutAllMigration'); + const migration = new M1PayoutAllMigration({ logger: log, blockchain, config }); try { - await m1PayoutAllMigration.run(); + await migration.run(); log.warn(`One-time payout migration completed. Lasted ${Date.now() - migrationsStartedMills} millisecond(s)`); await Utilities.writeContentsToFile( From d82e38411db06eca123a4ae8743d54688ebf4800 Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Sat, 15 Dec 2018 04:37:49 +0100 Subject: [PATCH 24/48] Version 2.0.36 --- 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 d531399cc..7ec607381 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.35", + "version": "2.0.36", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 010cd74a3..5b99801d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.35", + "version": "2.0.36", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From 828090321b48c1bcb6e0099dc6c3d25f4f5680c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20Obradovi=C4=87?= Date: Sun, 16 Dec 2018 15:00:09 +0100 Subject: [PATCH 25/48] Require management wallet (#816) --- modules/Blockchain/Ethereum/contracts/Profile.sol | 2 +- ot-node.js | 5 +++++ test/bdd/steps/erc725identity.js | 7 ++++++- test/bdd/steps/lib/local-blockchain.js | 4 ++-- test/bdd/steps/network.js | 3 +++ testnet/supervisord.conf | 4 +++- 6 files changed, 20 insertions(+), 5 deletions(-) diff --git a/modules/Blockchain/Ethereum/contracts/Profile.sol b/modules/Blockchain/Ethereum/contracts/Profile.sol index 35b93e279..4b633134c 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -43,7 +43,7 @@ contract Profile { event TokensTransferred(address sender, address receiver, uint256 amount); function createProfile(address managementWallet, bytes32 profileNodeId, uint256 initialBalance, bool senderHas725, address identity) public { - require(managementWallet!=address(0) && identity!=address(0)); + require(managementWallet!=address(0)); 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!"); diff --git a/ot-node.js b/ot-node.js index 0700b6da7..2aaced1d4 100644 --- a/ot-node.js +++ b/ot-node.js @@ -79,6 +79,11 @@ try { console.error('Please provide valid wallet.'); process.abort(); } + + if (!config.management_wallet) { + console.error('Please provide a valid management wallet.'); + process.abort(); + } } catch (error) { console.error(`Failed to read configuration. ${error}.`); console.error(error.stack); diff --git a/test/bdd/steps/erc725identity.js b/test/bdd/steps/erc725identity.js index 4b0637a98..dd4e2e93a 100644 --- a/test/bdd/steps/erc725identity.js +++ b/test/bdd/steps/erc725identity.js @@ -19,9 +19,14 @@ Given(/^I manually create ERC725 identity for (\d+)[st|nd|rd|th]+ node$/, async const node = this.state.nodes[nodeIndex - 1]; const nodeWallet = node.options.nodeConfiguration.node_wallet; const nodeWalletKey = node.options.nodeConfiguration.node_private_key; + const nodeManagementWallet = node.options.nodeConfiguration.management_wallet; const identityContractInstance = - await this.state.localBlockchain.createIdentity(nodeWallet, nodeWalletKey); + await this.state.localBlockchain.createIdentity( + nodeWallet, + nodeWalletKey, + nodeManagementWallet, + ); expect(identityContractInstance._address).to.not.be.undefined; this.state.manualStuff.erc725Identity = identityContractInstance._address; }); diff --git a/test/bdd/steps/lib/local-blockchain.js b/test/bdd/steps/lib/local-blockchain.js index b9cf6d96d..820b85aeb 100644 --- a/test/bdd/steps/lib/local-blockchain.js +++ b/test/bdd/steps/lib/local-blockchain.js @@ -363,10 +363,10 @@ class LocalBlockchain { return this.web3.eth.getBalance(wallet); } - async createIdentity(wallet, walletKey) { + async createIdentity(wallet, walletKey, managementWallet) { const [, identityInstance] = await this.deployContract( this.web3, this.identityContract, this.identityContractData, - [wallet], wallet, + [wallet, managementWallet], wallet, ); return identityInstance; } diff --git a/test/bdd/steps/network.js b/test/bdd/steps/network.js index b6d928a31..e5492c6c5 100644 --- a/test/bdd/steps/network.js +++ b/test/bdd/steps/network.js @@ -68,6 +68,7 @@ Given(/^(\d+) bootstrap is running$/, { timeout: 80000 }, function (nodeCount, d nodeConfiguration: { node_wallet: LocalBlockchain.wallets()[walletCount - 1].address, node_private_key: LocalBlockchain.wallets()[walletCount - 1].privateKey, + management_wallet: LocalBlockchain.wallets()[walletCount - 1].address, is_bootstrap_node: true, local_network_only: true, database: { @@ -104,6 +105,7 @@ Given(/^I setup (\d+) node[s]*$/, { timeout: 120000 }, function (nodeCount, done const nodeConfiguration = { node_wallet: LocalBlockchain.wallets()[i].address, node_private_key: LocalBlockchain.wallets()[i].privateKey, + management_wallet: LocalBlockchain.wallets()[i].address, node_port: 6000 + i, node_rpc_port: 9000 + i, node_remote_control_port: 4000 + i, @@ -519,6 +521,7 @@ Given(/^I additionally setup (\d+) node[s]*$/, { timeout: 60000 }, function (nod nodeConfiguration: { node_wallet: LocalBlockchain.wallets()[i].address, node_private_key: LocalBlockchain.wallets()[i].privateKey, + management_wallet: LocalBlockchain.wallets()[i].address, node_port: 6000 + i, node_rpc_port: 9000 + i, node_remote_control_port: 4000 + i, diff --git a/testnet/supervisord.conf b/testnet/supervisord.conf index 1d99051c8..f30180912 100644 --- a/testnet/supervisord.conf +++ b/testnet/supervisord.conf @@ -6,12 +6,14 @@ logfile_maxbytes=0 [program:otnode] command=bash -c "node /ot-node/testnet/register-node.js --configDir=/ot-node/data/ | tee -a complete-node.log" redirect_stderr=true -autorestart=true +autorestart=unexpected +exitcodes=0 stdout_logfile=/dev/fd/1 stdout_logfile_maxbytes=0 [program:arango] command=arangod +autorestart=true stdout_logfile=/dev/null stdout_logfile_maxbytes=0 From 1eb41ee97e41c98424d6f54c44fc2697e23a7158 Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Sun, 16 Dec 2018 15:53:02 +0100 Subject: [PATCH 26/48] Payout migration refactored (#817) --- modules/Blockchain.js | 14 ++++ modules/Blockchain/Ethereum/index.js | 27 ++++++- .../command/dc/dc-offer-finalize-command.js | 2 +- modules/migration/m1-payout-all-migration.js | 70 ++++++++++--------- modules/service/dc-service.js | 2 +- ot-node.js | 10 ++- 6 files changed, 82 insertions(+), 43 deletions(-) diff --git a/modules/Blockchain.js b/modules/Blockchain.js index c42c10865..0f0c17fec 100644 --- a/modules/Blockchain.js +++ b/modules/Blockchain.js @@ -464,6 +464,20 @@ class Blockchain { async isErc725IdentityOld(address) { return this.blockchain.isErc725IdentityOld(address); } + + /** + * PayOut for multiple offers. + * @returns {Promise} + */ + payOutMultiple( + blockchainIdentity, + offerIds, + ) { + return this.blockchain.payOutMultiple( + blockchainIdentity, + offerIds, + ); + } } module.exports = Blockchain; diff --git a/modules/Blockchain/Ethereum/index.js b/modules/Blockchain/Ethereum/index.js index e0f0df360..81816ae84 100644 --- a/modules/Blockchain/Ethereum/index.js +++ b/modules/Blockchain/Ethereum/index.js @@ -269,7 +269,7 @@ class Ethereum { gasPrice: this.web3.utils.toHex(this.config.gas_price), to: this.profileContractAddress, }; - this.log.trace(`CreateProfile(${managementWallet}, ${profileNodeId}, ${initialBalance}, ${isSender725})`); + this.log.trace(`CreateProfile(${managementWallet}, ${profileNodeId}, ${initialBalance}, ${isSender725}, ${blockchainIdentity})`); return this.transactions.queueTransaction( this.profileContractAbi, 'createProfile', [ @@ -1060,6 +1060,31 @@ class Ethereum { throw error; } } + + /** + * PayOut for multiple offers. + * @returns {Promise} + */ + payOutMultiple( + blockchainIdentity, + offerIds, + ) { + const gasLimit = (offerIds.length * 100000) + 5000; + const options = { + gasLimit, + gasPrice: this.web3.utils.toHex(this.config.gas_price), + to: this.holdingContractAddress, + }; + this.log.trace(`payOutMultiple (identity=${blockchainIdentity}, offerIds=${offerIds}`); + return this.transactions.queueTransaction( + this.holdingContractAbi, 'payOutMultiple', + [ + blockchainIdentity, + offerIds, + ], + options, + ); + } } module.exports = Ethereum; diff --git a/modules/command/dc/dc-offer-finalize-command.js b/modules/command/dc/dc-offer-finalize-command.js index c998b77ec..c2822d8ee 100644 --- a/modules/command/dc/dc-offer-finalize-command.js +++ b/modules/command/dc/dc-offer-finalize-command.js @@ -109,7 +109,7 @@ class DCOfferFinalizeCommand extends Command { const hasFunds = await this.dcService .hasProfileBalanceForOffer(offer.token_amount_per_holder); if (!hasFunds) { - errorMessage = 'Not enough tokens. To replicate data please deposit more tokens to your profile.'; + errorMessage = 'Not enough tokens. To replicate data please deposit more tokens to your profile'; } this.logger.error(`Offer ${offerId} has not been finalized. ${errorMessage}`); diff --git a/modules/migration/m1-payout-all-migration.js b/modules/migration/m1-payout-all-migration.js index f2b2aa097..56c945ec8 100644 --- a/modules/migration/m1-payout-all-migration.js +++ b/modules/migration/m1-payout-all-migration.js @@ -1,15 +1,17 @@ const models = require('../../models'); -const Models = require('../../models/index'); const Utilities = require('../Utilities'); /** * Runs all pending payout commands */ class M1PayoutAllMigration { - constructor({ logger, blockchain, config }) { + constructor({ + logger, blockchain, config, notifyError, + }) { this.logger = logger; this.config = config; this.blockchain = blockchain; + this.notifyError = notifyError; } /** @@ -24,42 +26,42 @@ class M1PayoutAllMigration { }, }); - for (const pendingPayOut of pendingPayOuts) { - const { data } = pendingPayOut; + const offerIds = pendingPayOuts.map(payoutCommand => payoutCommand.data.offerId); - let retries = 3; - while (retries > 0) { - try { - // eslint-disable-next-line - await this._payOut(data.offerId); - pendingPayOut.status = 'COMPLETED'; - pendingPayOut.save({ - fields: ['status'], - }); - break; - } catch (e) { - retries -= 1; - if (retries > 0) { - this.logger.error(`Failed to run payout migration. Retrying... ${e}`); - } else { - this.logger.error(`Failed to run payout migration. Stop retrying... ${e}`); - } - } - } + if (offerIds.length === 0) { + this.logger.trace('No pending offers.'); + return; } - } - async _payOut(offerId) { - const bid = await Models.bids.findOne({ - where: { offer_id: offerId, status: 'CHOSEN' }, - }); - if (!bid) { - this.logger.important(`There is no successful bid for offer ${offerId}. Cannot execute payout.`); - return; + const offerLimit = 60; + if (offerIds.length > offerLimit) { + const message = `Failed to complete payout for more that ${offerLimit}. Please contact support.`; + this.logger.error(message); + throw new Error(message); + } + + const erc725Identity = Utilities.normalizeHex(this.config.erc725Identity); + + let message; + try { + await this.blockchain.payOutMultiple(erc725Identity, offerIds); + this.logger.warn(`Payout successfully completed for ${offerIds.length} offer(s).`); + } catch (e) { + message = `Failed to complete payout for offers [${offerIds}]. Please make sure that you have enough ETH. ${e.message}`; + this.logger.error(message); + throw new Error(message); + } + + try { + await models.commands.update( + { status: 'COMPLETED' }, + { where: { status: 'PENDING', name: 'dhPayOutCommand' } }, + ); + } catch (e) { + message = `Failed to set status COMPLETED for payout commands. Possible invalid future payout commands. Offers affected ${offerIds}`; + this.logger.warn(message); + this.notifyError(new Error(message)); } - const blockchainIdentity = Utilities.normalizeHex(this.config.erc725Identity); - await this.blockchain.payOut(blockchainIdentity, offerId); - this.logger.important(`Payout for offer ${offerId} successfully completed.`); } } diff --git a/modules/service/dc-service.js b/modules/service/dc-service.js index 4087e647e..29b08cd05 100644 --- a/modules/service/dc-service.js +++ b/modules/service/dc-service.js @@ -51,7 +51,7 @@ class DCService { const hasFunds = await this.hasProfileBalanceForOffer(tokenAmountPerHolder); if (!hasFunds) { - const message = 'Not enough tokens. To replicate data please deposit more tokens to your profile.'; + const message = 'Not enough tokens. To replicate data please deposit more tokens to your profile'; this.logger.warn(message); throw new Error(message); } diff --git a/ot-node.js b/ot-node.js index 2aaced1d4..d334c19ce 100644 --- a/ot-node.js +++ b/ot-node.js @@ -481,14 +481,12 @@ class OTNode { await migration.run(); log.warn(`One-time payout migration completed. Lasted ${Date.now() - migrationsStartedMills} millisecond(s)`); - await Utilities.writeContentsToFile( - migrationDir, m1PayoutAllMigrationFilename, - JSON.stringify({ - status: 'COMPLETED', - }), - ); + await Utilities.writeContentsToFile(migrationDir, m1PayoutAllMigrationFilename, 'PROCESSED'); } catch (e) { log.error(`Failed to run code migrations. Lasted ${Date.now() - migrationsStartedMills} millisecond(s). ${e.message}`); + console.log(e); + notifyBugsnag(e); + process.exit(1); } } From 366c4964d79827bc671af96a03270a48941a4519 Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Sun, 16 Dec 2018 16:05:37 +0100 Subject: [PATCH 27/48] Multiple payOut function (#818) * Add payout multiple function * Improve payOutMultiple function * Add tests for payOutMultiple function --- modules/Blockchain/Ethereum/abi/holding.json | 60 +++++----------- .../Blockchain/Ethereum/contracts/Holding.sol | 27 ++++++- .../Blockchain/Ethereum/test/offer.test.js | 72 ++++++++++++++++++- 3 files changed, 115 insertions(+), 44 deletions(-) diff --git a/modules/Blockchain/Ethereum/abi/holding.json b/modules/Blockchain/Ethereum/abi/holding.json index 121054a8e..78fdb12da 100644 --- a/modules/Blockchain/Ethereum/abi/holding.json +++ b/modules/Blockchain/Ethereum/abi/holding.json @@ -99,6 +99,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "identity", + "type": "address" + }, + { + "name": "offerIds", + "type": "uint256[]" + } + ], + "name": "payOutMultiple", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": true, "inputs": [], @@ -169,20 +187,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "constant": true, - "inputs": [], - "name": "profile", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, { "constant": false, "inputs": [ @@ -197,34 +201,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "constant": true, - "inputs": [], - "name": "profileStorage", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "holdingStorage", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/modules/Blockchain/Ethereum/contracts/Holding.sol b/modules/Blockchain/Ethereum/contracts/Holding.sol index 78c48074a..ac9f3e6f0 100644 --- a/modules/Blockchain/Ethereum/contracts/Holding.sol +++ b/modules/Blockchain/Ethereum/contracts/Holding.sol @@ -171,7 +171,7 @@ contract Holding is Ownable { function payOut(address identity, uint256 offerId) public { // Verify sender - require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2)); + require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2) || ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have proper permission to call this function!"); require(Approval(hub.approvalAddress()).identityHasApproval(identity), "Identity does not have approval for using the contract"); HoldingStorage holdingStorage = HoldingStorage(hub.holdingStorageAddress()); @@ -189,6 +189,31 @@ contract Holding is Ownable { Profile(hub.profileAddress()).releaseTokens(identity, amountToTransfer); Profile(hub.profileAddress()).transferTokens(holdingStorage.getOfferCreator(bytes32(offerId)), identity, amountToTransfer); } + + function payOutMultiple(address identity, bytes32[] offerIds) + public { + require(identity != address(0), "Identity cannot be zero!"); + require(ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 2) || ERC725(identity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have proper permission to call this function!"); + require(Approval(hub.approvalAddress()).identityHasApproval(identity), "Identity does not have approval for using the contract"); + + HoldingStorage holdingStorage = HoldingStorage(hub.holdingStorageAddress()); + + + for (uint i = 0; i < offerIds.length; i = i + 1){ + // Verify holder + uint256 amountToTransfer = holdingStorage.getHolderStakedAmount(offerIds[i], identity); + if (amountToTransfer == 0) continue; + + // Verify that holding time expired + require(holdingStorage.getOfferStartTime(offerIds[i]) + + holdingStorage.getOfferHoldingTimeInMinutes(offerIds[i]).mul(60) < block.timestamp, + "Holding time not yet expired!"); + + // Release tokens staked by holder and transfer tokens from data creator to holder + Profile(hub.profileAddress()).releaseTokens(identity, amountToTransfer); + Profile(hub.profileAddress()).transferTokens(holdingStorage.getOfferCreator(offerIds[i]), identity, amountToTransfer); + } + } function ecrecovery(bytes32 hash, bytes sig) internal pure returns (address) { bytes32 r; diff --git a/modules/Blockchain/Ethereum/test/offer.test.js b/modules/Blockchain/Ethereum/test/offer.test.js index 63835205e..c05dc9456 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(21))); +var tokensToDeposit = (new BN(100)).mul(new BN(10).pow(new BN(21))); // Offer variables const dataSetId = '0x8cad6896887d99d70db8ce035d331ba2ade1a5e1161f38ff7fda76cf7c308cde'; @@ -45,6 +45,7 @@ var privateKeys = []; var identities = []; // Contracts used in test +var hub; var trac; var profile; var holding; @@ -57,6 +58,7 @@ contract('Offer testing', async (accounts) => { // eslint-disable-next-line no-undef before(async () => { // Get contracts used in hook + hub = await Hub.deployed(); trac = await TracToken.deployed(); profile = await Profile.deployed(); holding = await Holding.deployed(); @@ -294,6 +296,74 @@ contract('Offer testing', async (accounts) => { assert((new BN(0)).eq(res.stakeReserved), 'Reserved stake amount incorrect for DC'); }); + // eslint-disable-next-line no-undef + it('Should test payOutMultiple function', async () => { + // Set up multiple offers to for a single holder + const numOffers = new BN(1); + const DH_index = 4; + const DH_identity = identities[DH_index]; + const DH_account = accounts[DH_index]; + const dcProfile = await profileStorage.profile.call(DC_identity); + const dhProfile = await profileStorage.profile.call(DH_identity); + + await profileStorage.setStakeReserved(DC_identity, dcProfile.stakeReserved + .add(tokenAmountPerHolder.mul(numOffers))); + await profileStorage.setStakeReserved(DH_identity, dhProfile.stakeReserved + .add(tokenAmountPerHolder.mul(numOffers))); + + const initialStakeDH = await profileStorage.getStake.call(DH_identity); + const initialStakeDC = await profileStorage.getStake.call(DC_identity); + const initialStakeReservedDH = await profileStorage.getStakeReserved.call(DH_identity); + const initialStakeReservedDC = await profileStorage.getStakeReserved.call(DC_identity); + + const promises = []; + const offerIds = []; + for (let i = 0; i < numOffers; i += 1) { + offerIds[i] = `0x00000000000000000000000000000000000000000000000000000000000000${i < 10 ? '0' : ''}${i}`; + promises.push(holdingStorage.setHolderStakedAmount( + offerIds[i], + DH_identity, + tokenAmountPerHolder, + )); + promises.push(holdingStorage.setOfferCreator( + offerIds[i], + DC_identity, + )); + } + await Promise.all(promises); + + const res = await holding.payOutMultiple( + DH_identity, + offerIds, + { from: DH_account, gasLimit: 5000000 }, + ); + + const finalStakeDH = await profileStorage.getStake.call(DH_identity); + const finalStakeDC = await profileStorage.getStake.call(DC_identity); + const finalStakeReservedDH = await profileStorage.getStakeReserved.call(DH_identity); + const finalStakeReservedDC = await profileStorage.getStakeReserved.call(DC_identity); + + assert( + initialStakeDH.add(tokenAmountPerHolder.mul(numOffers)).eq(finalStakeDH), + `Stake reserved amount incorrect for DH, got ${finalStakeDH.toString()} but expected ${initialStakeDH.add(tokenAmountPerHolder.mul(numOffers)).toString()}`, + ); + assert( + initialStakeDC.sub(tokenAmountPerHolder.mul(numOffers)).eq(finalStakeDC), + `Stake reserved amount incorrect for DH, got ${finalStakeDC.toString()} but expected ${initialStakeDC.sub(tokenAmountPerHolder.mul(numOffers)).toString()}`, + ); + + assert( + initialStakeReservedDH + .sub(tokenAmountPerHolder.mul(numOffers)).eq(finalStakeReservedDH), + `Stake reserved amount incorrect for DH, got ${finalStakeReservedDH.toString()} but expected ${initialStakeReservedDH.sub(tokenAmountPerHolder.mul(numOffers)).toString()}`, + ); + assert( + initialStakeReservedDC + .sub(tokenAmountPerHolder.mul(numOffers)).eq(finalStakeReservedDC), + `Stake reserved amount incorrect for DH, got ${finalStakeReservedDC.toString()} but expected ${initialStakeReservedDC.sub(tokenAmountPerHolder.mul(numOffers)).toString()}`, + ); + }); + // eslint-disable-next-line no-undef it('Should test difficulty override', async () => { let res = await holding.difficultyOverride.call(); From 2e8a7b6ebab60c13a40e75cb78d24dadb40a788a Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Sun, 16 Dec 2018 18:50:33 +0100 Subject: [PATCH 28/48] Change log message (#819) * change message --- modules/migration/m1-payout-all-migration.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/migration/m1-payout-all-migration.js b/modules/migration/m1-payout-all-migration.js index 56c945ec8..49a950ea7 100644 --- a/modules/migration/m1-payout-all-migration.js +++ b/modules/migration/m1-payout-all-migration.js @@ -27,9 +27,8 @@ class M1PayoutAllMigration { }); const offerIds = pendingPayOuts.map(payoutCommand => payoutCommand.data.offerId); - if (offerIds.length === 0) { - this.logger.trace('No pending offers.'); + this.logger.warn('No pending payouts.'); return; } @@ -45,7 +44,9 @@ class M1PayoutAllMigration { let message; try { await this.blockchain.payOutMultiple(erc725Identity, offerIds); - this.logger.warn(`Payout successfully completed for ${offerIds.length} offer(s).`); + for (const offerId of offerIds) { + this.logger.warn(`Payout successfully completed for offer ${offerId}.`); + } } catch (e) { message = `Failed to complete payout for offers [${offerIds}]. Please make sure that you have enough ETH. ${e.message}`; this.logger.error(message); From 07392a4436412d31a6fae54c807cf75cfe1955b1 Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Sun, 16 Dec 2018 18:54:49 +0100 Subject: [PATCH 29/48] Update ABI files (#820) * Update holding contract ABI file * Update profile contract ABI file * fix --- modules/Blockchain/Ethereum/abi/holding.json | 36 +- modules/Blockchain/Ethereum/abi/profile.json | 388 ++++++++----------- modules/Blockchain/Ethereum/index.js | 2 +- 3 files changed, 191 insertions(+), 235 deletions(-) diff --git a/modules/Blockchain/Ethereum/abi/holding.json b/modules/Blockchain/Ethereum/abi/holding.json index 78fdb12da..cb1ba0337 100644 --- a/modules/Blockchain/Ethereum/abi/holding.json +++ b/modules/Blockchain/Ethereum/abi/holding.json @@ -99,24 +99,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "constant": false, - "inputs": [ - { - "name": "identity", - "type": "address" - }, - { - "name": "offerIds", - "type": "uint256[]" - } - ], - "name": "payOutMultiple", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, { "constant": true, "inputs": [], @@ -187,6 +169,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "identity", + "type": "address" + }, + { + "name": "offerIds", + "type": "bytes32[]" + } + ], + "name": "payOutMultiple", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": false, "inputs": [ diff --git a/modules/Blockchain/Ethereum/abi/profile.json b/modules/Blockchain/Ethereum/abi/profile.json index d7737639e..4c237b43f 100644 --- a/modules/Blockchain/Ethereum/abi/profile.json +++ b/modules/Blockchain/Ethereum/abi/profile.json @@ -3,32 +3,109 @@ "constant": false, "inputs": [ { - "name": "managementWallet", + "name": "identity", "type": "address" }, { - "name": "profileNodeId", - "type": "bytes32" - }, + "name": "amount", + "type": "uint256" + } + ], + "name": "startTokenWithdrawal", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ { - "name": "initialBalance", + "name": "oldIdentity", + "type": "address" + } + ], + "name": "transferProfile", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "hub", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "withdrawalTime", + "outputs": [ + { + "name": "", "type": "uint256" - }, + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ { - "name": "senderHas725", - "type": "bool" - }, + "name": "newMinimalStake", + "type": "uint256" + } + ], + "name": "setMinimalStake", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ { "name": "identity", "type": "address" } ], - "name": "createProfile", + "name": "withdrawTokens", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, + { + "constant": true, + "inputs": [], + "name": "version", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, { "constant": false, "inputs": [ @@ -51,15 +128,27 @@ "constant": false, "inputs": [ { - "name": "profile", + "name": "managementWallet", "type": "address" }, { - "name": "amount", + "name": "profileNodeId", + "type": "bytes32" + }, + { + "name": "initialBalance", "type": "uint256" + }, + { + "name": "senderHas725", + "type": "bool" + }, + { + "name": "identity", + "type": "address" } ], - "name": "releaseTokens", + "name": "createProfile", "outputs": [], "payable": false, "stateMutability": "nonpayable", @@ -69,19 +158,43 @@ "constant": false, "inputs": [ { - "name": "payer", + "name": "identity", "type": "address" }, { - "name": "identity1", - "type": "address" - }, + "name": "newNodeId", + "type": "bytes32" + } + ], + "name": "setNodeId", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minimalStake", + "outputs": [ { - "name": "identity2", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "sender", "type": "address" }, { - "name": "identity3", + "name": "receiver", "type": "address" }, { @@ -89,7 +202,7 @@ "type": "uint256" } ], - "name": "reserveTokens", + "name": "transferTokens", "outputs": [], "payable": false, "stateMutability": "nonpayable", @@ -99,11 +212,11 @@ "constant": false, "inputs": [ { - "name": "newMinimalStake", + "name": "newWithdrawalTime", "type": "uint256" } ], - "name": "setMinimalStake", + "name": "setWithdrawalTime", "outputs": [], "payable": false, "stateMutability": "nonpayable", @@ -113,33 +226,30 @@ "constant": false, "inputs": [ { - "name": "identity", + "name": "profile", "type": "address" }, { - "name": "newNodeId", - "type": "bytes32" + "name": "amount", + "type": "uint256" } ], - "name": "setNodeId", + "name": "releaseTokens", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { - "constant": false, "inputs": [ { - "name": "newWithdrawalTime", - "type": "uint256" + "name": "hubAddress", + "type": "address" } ], - "name": "setWithdrawalTime", - "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "function" + "type": "constructor" }, { "anonymous": false, @@ -151,11 +261,11 @@ }, { "indexed": false, - "name": "amount", + "name": "initialBalance", "type": "uint256" } ], - "name": "TokensReleased", + "name": "ProfileCreated", "type": "event" }, { @@ -168,35 +278,29 @@ }, { "indexed": false, - "name": "amountWithdrawn", - "type": "uint256" - }, - { - "indexed": false, - "name": "newBalance", - "type": "uint256" + "name": "newIdentity", + "type": "address" } ], - "name": "TokensWithdrawn", + "name": "IdentityCreated", "type": "event" }, { - "constant": false, + "anonymous": false, "inputs": [ { - "name": "identity", + "indexed": false, + "name": "oldIdentity", "type": "address" }, { - "name": "amount", - "type": "uint256" + "indexed": false, + "name": "newIdentity", + "type": "address" } ], - "name": "startTokenWithdrawal", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" + "name": "IdentityTransferred", + "type": "event" }, { "anonymous": false, @@ -208,11 +312,11 @@ }, { "indexed": false, - "name": "amountReserved", + "name": "amount", "type": "uint256" } ], - "name": "TokensReserved", + "name": "TokenDeposit", "type": "event" }, { @@ -244,9 +348,14 @@ "indexed": false, "name": "profile", "type": "address" + }, + { + "indexed": false, + "name": "amountReserved", + "type": "uint256" } ], - "name": "TokenWithdrawalCancelled", + "name": "TokensReserved", "type": "event" }, { @@ -278,14 +387,9 @@ "indexed": false, "name": "profile", "type": "address" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" } ], - "name": "TokenDeposit", + "name": "TokenWithdrawalCancelled", "type": "event" }, { @@ -293,33 +397,21 @@ "inputs": [ { "indexed": false, - "name": "oldIdentity", + "name": "profile", "type": "address" }, { "indexed": false, - "name": "newIdentity", - "type": "address" - } - ], - "name": "IdentityTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "profile", - "type": "address" + "name": "amountWithdrawn", + "type": "uint256" }, { "indexed": false, - "name": "newIdentity", - "type": "address" + "name": "newBalance", + "type": "uint256" } ], - "name": "IdentityCreated", + "name": "TokensWithdrawn", "type": "event" }, { @@ -332,11 +424,11 @@ }, { "indexed": false, - "name": "initialBalance", + "name": "amount", "type": "uint256" } ], - "name": "ProfileCreated", + "name": "TokensReleased", "type": "event" }, { @@ -360,141 +452,5 @@ ], "name": "TokensTransferred", "type": "event" - }, - { - "constant": false, - "inputs": [ - { - "name": "oldIdentity", - "type": "address" - } - ], - "name": "transferProfile", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "sender", - "type": "address" - }, - { - "name": "receiver", - "type": "address" - }, - { - "name": "amount", - "type": "uint256" - } - ], - "name": "transferTokens", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "identity", - "type": "address" - } - ], - "name": "withdrawTokens", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "name": "hubAddress", - "type": "address" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "constant": true, - "inputs": [], - "name": "hub", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "minimalStake", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "profileStorage", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "version", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "withdrawalTime", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "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 81816ae84..1a1c208a6 100644 --- a/modules/Blockchain/Ethereum/index.js +++ b/modules/Blockchain/Ethereum/index.js @@ -1069,7 +1069,7 @@ class Ethereum { blockchainIdentity, offerIds, ) { - const gasLimit = (offerIds.length * 100000) + 5000; + const gasLimit = offerIds.length * 200000; const options = { gasLimit, gasPrice: this.web3.utils.toHex(this.config.gas_price), From 4830667eeb87fe06a1bad1823ca6e3dd5b87855e Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Sun, 16 Dec 2018 18:55:05 +0100 Subject: [PATCH 30/48] Add gas changes in offer tests (#821) --- modules/Blockchain/Ethereum/test/offer.test.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/Blockchain/Ethereum/test/offer.test.js b/modules/Blockchain/Ethereum/test/offer.test.js index c05dc9456..f4b102fe9 100644 --- a/modules/Blockchain/Ethereum/test/offer.test.js +++ b/modules/Blockchain/Ethereum/test/offer.test.js @@ -279,11 +279,14 @@ contract('Offer testing', async (accounts) => { res = await profileStorage.profile.call(DC_identity); const initialStakeDC = res.stake; - for (i = 0; i < 3; i += 1) { + for (i = 0; i < 2; i += 1) { // eslint-disable-next-line no-await-in-loop await holding.payOut(identities[i], offerId, { from: accounts[i] }); } - + const array = []; + array.push(offerId); + res = await holding.payOutMultiple(identities[2], array, { from: accounts[2], gas: 200000 }); + console.log(`\tGasUsed: ${res.receipt.gasUsed}`); for (i = 0; i < 3; i += 1) { // eslint-disable-next-line no-await-in-loop @@ -299,7 +302,7 @@ contract('Offer testing', async (accounts) => { // eslint-disable-next-line no-undef it('Should test payOutMultiple function', async () => { // Set up multiple offers to for a single holder - const numOffers = new BN(1); + const numOffers = new BN(20); const DH_index = 4; const DH_identity = identities[DH_index]; const DH_account = accounts[DH_index]; @@ -335,7 +338,7 @@ contract('Offer testing', async (accounts) => { const res = await holding.payOutMultiple( DH_identity, offerIds, - { from: DH_account, gasLimit: 5000000 }, + { from: DH_account, gas: 4000000 }, ); const finalStakeDH = await profileStorage.getStake.call(DH_identity); From fcf895d226ac0f72e85184cd4230f4b72891106f Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Sun, 16 Dec 2018 18:56:09 +0100 Subject: [PATCH 31/48] Version 2.0.37 --- 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 7ec607381..a32de684d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.36", + "version": "2.0.37", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5b99801d3..99750e9be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.36", + "version": "2.0.37", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From d5de2fbc342ede67da95483a983c89d5529a7937 Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Sun, 16 Dec 2018 20:07:09 +0100 Subject: [PATCH 32/48] add batches (#823) --- modules/migration/m1-payout-all-migration.js | 64 ++++++++++++-------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/modules/migration/m1-payout-all-migration.js b/modules/migration/m1-payout-all-migration.js index 49a950ea7..0858e1c8c 100644 --- a/modules/migration/m1-payout-all-migration.js +++ b/modules/migration/m1-payout-all-migration.js @@ -1,6 +1,8 @@ const models = require('../../models'); const Utilities = require('../Utilities'); +const BATCH_SIZE = 15; + /** * Runs all pending payout commands */ @@ -19,49 +21,63 @@ class M1PayoutAllMigration { */ async run() { /* get all pending payouts */ - const pendingPayOuts = await models.commands.findAll({ + let pendingPayOuts = await models.commands.findAll({ where: { status: 'PENDING', name: 'dhPayOutCommand', }, }); - const offerIds = pendingPayOuts.map(payoutCommand => payoutCommand.data.offerId); - if (offerIds.length === 0) { + if (pendingPayOuts.length === 0) { this.logger.warn('No pending payouts.'); return; } const offerLimit = 60; - if (offerIds.length > offerLimit) { + if (pendingPayOuts.length > offerLimit) { const message = `Failed to complete payout for more that ${offerLimit}. Please contact support.`; this.logger.error(message); throw new Error(message); } const erc725Identity = Utilities.normalizeHex(this.config.erc725Identity); + while (pendingPayOuts.length > 0) { + const tempPending = pendingPayOuts.slice(0, BATCH_SIZE); + pendingPayOuts = pendingPayOuts.slice(BATCH_SIZE); - let message; - try { - await this.blockchain.payOutMultiple(erc725Identity, offerIds); - for (const offerId of offerIds) { - this.logger.warn(`Payout successfully completed for offer ${offerId}.`); - } - } catch (e) { - message = `Failed to complete payout for offers [${offerIds}]. Please make sure that you have enough ETH. ${e.message}`; - this.logger.error(message); - throw new Error(message); - } + const offerIds = tempPending.map(payoutCommand => payoutCommand.data.offerId); + const commandIds = tempPending.map(payoutCommand => payoutCommand.id); + + let message; + try { + // eslint-disable-next-line + await this.blockchain.payOutMultiple(erc725Identity, offerIds); + for (const offerId of offerIds) { + this.logger.warn(`Payout successfully completed for offer ${offerId}.`); + } - try { - await models.commands.update( - { status: 'COMPLETED' }, - { where: { status: 'PENDING', name: 'dhPayOutCommand' } }, - ); - } catch (e) { - message = `Failed to set status COMPLETED for payout commands. Possible invalid future payout commands. Offers affected ${offerIds}`; - this.logger.warn(message); - this.notifyError(new Error(message)); + try { + // eslint-disable-next-line + await models.commands.update( + { status: 'COMPLETED' }, + { + where: { + status: 'PENDING', + name: 'dhPayOutCommand', + id: { [models.Sequelize.Op.in]: commandIds }, + }, + }, + ); + } catch (e) { + message = `Failed to set status COMPLETED for payout commands. Possible invalid future payout commands. Offers affected ${offerIds}`; + this.logger.warn(message); + this.notifyError(new Error(message)); + } + } catch (e) { + message = `Failed to complete payout for offers [${offerIds}]. Please make sure that you have enough ETH. ${e.message}`; + this.logger.error(message); + throw new Error(message); + } } } } From 6f73feece75c549c9a29c0adb200e7f2448c57b0 Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Sun, 16 Dec 2018 20:07:21 +0100 Subject: [PATCH 33/48] Fix lint in truffle tests (#824) --- modules/Blockchain/Ethereum/test/offer.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/Blockchain/Ethereum/test/offer.test.js b/modules/Blockchain/Ethereum/test/offer.test.js index f4b102fe9..55c860579 100644 --- a/modules/Blockchain/Ethereum/test/offer.test.js +++ b/modules/Blockchain/Ethereum/test/offer.test.js @@ -285,7 +285,11 @@ contract('Offer testing', async (accounts) => { } const array = []; array.push(offerId); - res = await holding.payOutMultiple(identities[2], array, { from: accounts[2], gas: 200000 }); + res = await holding.payOutMultiple( + identities[2], + array, + { from: accounts[2], gas: 200000 }, + ); console.log(`\tGasUsed: ${res.receipt.gasUsed}`); for (i = 0; i < 3; i += 1) { From 318a3ef336c0a2d505df10736d35ebc5916394c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20Obradovi=C4=87?= Date: Sun, 16 Dec 2018 20:07:56 +0100 Subject: [PATCH 34/48] Exit from docker in case of common errors (#822) * Exit from docker in case of common errors * Use word identity instead profile --- modules/service/profile-service.js | 2 +- testnet/supervisord.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/service/profile-service.js b/modules/service/profile-service.js index a04e0ddba..6dbb1edd6 100644 --- a/modules/service/profile-service.js +++ b/modules/service/profile-service.js @@ -207,7 +207,7 @@ class ProfileService { )); this.logger.important('**************************************************************************'); - this.logger.important(`Your ERC725 profile has been upgraded and now has the new address: ${newErc725Identity}`); + this.logger.important(`Your ERC725 identity has been upgraded and now has the new address: ${newErc725Identity}`); this.logger.important('Please backup your ERC725 identity file.'); this.logger.important('**************************************************************************'); this.config.erc725Identity = newErc725Identity; diff --git a/testnet/supervisord.conf b/testnet/supervisord.conf index f30180912..f42c664d2 100644 --- a/testnet/supervisord.conf +++ b/testnet/supervisord.conf @@ -7,7 +7,7 @@ logfile_maxbytes=0 command=bash -c "node /ot-node/testnet/register-node.js --configDir=/ot-node/data/ | tee -a complete-node.log" redirect_stderr=true autorestart=unexpected -exitcodes=0 +exitcodes=0,-1,1,134 stdout_logfile=/dev/fd/1 stdout_logfile_maxbytes=0 From dd4c98b15c1b92c00d0a8b9212c45a061216aea0 Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Sun, 16 Dec 2018 20:10:28 +0100 Subject: [PATCH 35/48] Version 2.0.38 --- 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 a32de684d..952c9e751 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.37", + "version": "2.0.38", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 99750e9be..f72b5b2e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.37", + "version": "2.0.38", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From 4f7ad9e76ff422e65d7ec51ba646d2a3a1a845bf Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Sun, 16 Dec 2018 23:13:17 +0100 Subject: [PATCH 36/48] Enable withdraw while completing new offers (#825) * Set update deployment script to work on rinkeby * Enable withdrawal and finalizing offers if all parties have enough tokens * Fix lint in truffle migration file * Add token deposit truffle test --- .../Blockchain/Ethereum/contracts/Holding.sol | 13 +++++---- .../Ethereum/migrations/2_total_migration.js | 27 +++++++++---------- .../Blockchain/Ethereum/test/profile.test.js | 2 +- modules/Blockchain/Ethereum/truffle.js | 11 +++++--- 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/modules/Blockchain/Ethereum/contracts/Holding.sol b/modules/Blockchain/Ethereum/contracts/Holding.sol index ac9f3e6f0..f663fcf1d 100644 --- a/modules/Blockchain/Ethereum/contracts/Holding.sol +++ b/modules/Blockchain/Ethereum/contracts/Holding.sol @@ -125,16 +125,16 @@ contract Holding is Ownable { internal { ProfileStorage profileStorage = ProfileStorage(hub.profileStorageAddress()); - if(profileStorage.getWithdrawalPending(payer)) { + if(profileStorage.getWithdrawalPending(payer) && profileStorage.getWithdrawalAmount(payer).add(amount.mul(3)) > profileStorage.getStake(payer) - profileStorage.getStakeReserved(payer)) { profileStorage.setWithdrawalPending(payer,false); } - if(profileStorage.getWithdrawalPending(identity1)) { + if(profileStorage.getWithdrawalPending(identity1) && profileStorage.getWithdrawalAmount(identity1).add(amount) > profileStorage.getStake(identity1) - profileStorage.getStakeReserved(identity1)) { profileStorage.setWithdrawalPending(identity1,false); } - if(profileStorage.getWithdrawalPending(identity2)) { + if(profileStorage.getWithdrawalPending(identity2) && profileStorage.getWithdrawalAmount(identity2).add(amount) > profileStorage.getStake(identity2) - profileStorage.getStakeReserved(identity2)) { profileStorage.setWithdrawalPending(identity2,false); } - if(profileStorage.getWithdrawalPending(identity3)) { + if(profileStorage.getWithdrawalPending(identity3) && profileStorage.getWithdrawalAmount(identity3).add(amount) > profileStorage.getStake(identity3) - profileStorage.getStakeReserved(identity3)) { profileStorage.setWithdrawalPending(identity3,false); } @@ -188,6 +188,8 @@ contract Holding is Ownable { // Release tokens staked by holder and transfer tokens from data creator to holder Profile(hub.profileAddress()).releaseTokens(identity, amountToTransfer); Profile(hub.profileAddress()).transferTokens(holdingStorage.getOfferCreator(bytes32(offerId)), identity, amountToTransfer); + + holdingStorage.setHolderPaidAmount(bytes32(offerId), identity, amountToTransfer); } function payOutMultiple(address identity, bytes32[] offerIds) @@ -198,7 +200,6 @@ contract Holding is Ownable { HoldingStorage holdingStorage = HoldingStorage(hub.holdingStorageAddress()); - for (uint i = 0; i < offerIds.length; i = i + 1){ // Verify holder uint256 amountToTransfer = holdingStorage.getHolderStakedAmount(offerIds[i], identity); @@ -212,6 +213,8 @@ contract Holding is Ownable { // Release tokens staked by holder and transfer tokens from data creator to holder Profile(hub.profileAddress()).releaseTokens(identity, amountToTransfer); Profile(hub.profileAddress()).transferTokens(holdingStorage.getOfferCreator(offerIds[i]), identity, amountToTransfer); + + holdingStorage.setHolderPaidAmount(bytes32(offerIds[i]), identity, amountToTransfer); } } diff --git a/modules/Blockchain/Ethereum/migrations/2_total_migration.js b/modules/Blockchain/Ethereum/migrations/2_total_migration.js index 44e986185..080dc17e6 100644 --- a/modules/Blockchain/Ethereum/migrations/2_total_migration.js +++ b/modules/Blockchain/Ethereum/migrations/2_total_migration.js @@ -147,31 +147,28 @@ module.exports = async (deployer, network, accounts) => { console.log(`\t Escrow contract address: \t${holding.address}`); break; case 'update': - hub = await Hub.deployed(); + hub = await Hub.at('0x54985ef4EF2d3d04df7B026DA98d9f356b418626'); - token = await deployer.deploy(TracToken, accounts[0], accounts[1], accounts[2]); - await hub.setTokenAddress(token.address); + // token = await deployer.deploy(TracToken, accounts[0], accounts[1], accounts[2]); + // await hub.setTokenAddress(token.address); - profile = await deployer.deploy(Profile, hub.address, { gas: 9000000, from: accounts[0] }); + profile = await deployer.deploy(Profile, hub.address, { gas: 6500000, from: accounts[0] }); await hub.setProfileAddress(profile.address); holding = await deployer.deploy(Holding, hub.address, { gas: 6000000, from: accounts[0] }); await hub.setHoldingAddress(holding.address); - reading = await deployer.deploy(Reading, hub.address, { gas: 6000000, from: accounts[0] }); - await hub.setReadingAddress(reading.address); - - for (let i = 0; i < 10; i += 1) { - amounts.push(amountToMint); - recepients.push(accounts[i]); - } - await token.mintMany(recepients, amounts, { from: accounts[0] }); - await token.finishMinting({ from: accounts[0] }); + // for (let i = 0; i < 10; i += 1) { + // amounts.push(amountToMint); + // recepients.push(accounts[i]); + // } + // await token.mintMany(recepients, amounts, { from: accounts[0] }); + // await token.finishMinting({ from: accounts[0] }); console.log('\n\n \t Contract adressess on ganache:'); console.log(`\t Hub contract address: \t\t\t${hub.address}`); - console.log(`\t Approval contract address: \t\t${approval.address}`); - console.log(`\t Token contract address: \t\t${token.address}`); + // console.log(`\t Approval contract address: \t\t${approval.address}`); + // console.log(`\t Token contract address: \t\t${token.address}`); console.log(`\t Profile contract address: \t\t${profile.address}`); console.log(`\t Holding contract address: \t\t${holding.address}`); break; diff --git a/modules/Blockchain/Ethereum/test/profile.test.js b/modules/Blockchain/Ethereum/test/profile.test.js index 7b434eae4..bfcb32c55 100644 --- a/modules/Blockchain/Ethereum/test/profile.test.js +++ b/modules/Blockchain/Ethereum/test/profile.test.js @@ -212,7 +212,7 @@ contract('Profile contract testing', async (accounts) => { }); // eslint-disable-next-line no-undef - it.skip('Should deposit tokens to profile', async () => { + it('Should deposit tokens to profile', async () => { // Get contracts used in hook const trac = await TracToken.deployed(); const profile = await Profile.deployed(); diff --git a/modules/Blockchain/Ethereum/truffle.js b/modules/Blockchain/Ethereum/truffle.js index cdc36e0c2..1cff99f4f 100644 --- a/modules/Blockchain/Ethereum/truffle.js +++ b/modules/Blockchain/Ethereum/truffle.js @@ -31,10 +31,13 @@ module.exports = { }, update: { - host: 'localhost', - port: 7545, - gas: 6000000, - network_id: '5777', + host: 'localhost', // Connect to geth on the specified + port: 8545, + provider: () => new HDWalletProvider(mnemonic, `https://rinkeby.infura.io/${process.env.RINKEBY_ACCESS_KEY}`), + network_id: 4, + gas: 6000000, // Gas limit used for deploys + websockets: true, + skipDryRun: true, }, test: { From f6d27d2f18ce685901b345c983ece75f592649bd Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Mon, 17 Dec 2018 00:34:34 +0100 Subject: [PATCH 37/48] Version 2.0.39 --- 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 952c9e751..6b76bb467 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.38", + "version": "2.0.39", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f72b5b2e9..c30a0f337 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.38", + "version": "2.0.39", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From a84972998774bbe2a7f4feeaa0d66e9edb469002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20Obradovi=C4=87?= Date: Mon, 17 Dec 2018 01:43:56 +0100 Subject: [PATCH 38/48] Do not check for permissions for mng wallet (#826) --- modules/service/profile-service.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/modules/service/profile-service.js b/modules/service/profile-service.js index 6dbb1edd6..1541c9209 100644 --- a/modules/service/profile-service.js +++ b/modules/service/profile-service.js @@ -32,23 +32,6 @@ class ProfileService { if (identityExists && await this.isProfileCreated()) { this.logger.notify(`Profile has already been created for node ${this.config.identity}`); - let walletToCheck; - if (this.config.management_wallet) { - walletToCheck = this.config.management_wallet; - } else { - this.logger.important('Management wallet not set. Please set one.'); - walletToCheck = this.config.node_wallet; - } - - // Check financial wallet permissions. - const permissions = await this.blockchain.getWalletPurposes( - this.config.erc725Identity, - walletToCheck, - ); - - if (!permissions.includes('1')) { - throw Error(`Management wallet ${walletToCheck} doesn't have enough permissions.`); - } return; } From 11c9c7f398674673c2e5d30331a50eb3b038dc67 Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Mon, 17 Dec 2018 01:44:23 +0100 Subject: [PATCH 39/48] Add management key in profile transfer (#827) * Add management key in profile transfer * Update transferProfile node implementation --- modules/Blockchain.js | 5 ++- modules/Blockchain/Ethereum/abi/profile.json | 42 ++++++++++--------- .../Blockchain/Ethereum/contracts/Profile.sol | 5 ++- modules/Blockchain/Ethereum/index.js | 7 ++-- modules/service/profile-service.js | 5 ++- 5 files changed, 37 insertions(+), 27 deletions(-) diff --git a/modules/Blockchain.js b/modules/Blockchain.js index 0f0c17fec..20abffc4b 100644 --- a/modules/Blockchain.js +++ b/modules/Blockchain.js @@ -451,9 +451,10 @@ class Blockchain { /** * Transfers identity to new address. * @param {string} - erc725identity + * @param {string} - managementWallet */ - transferProfile(erc725identity) { - return this.blockchain.transferProfile(erc725identity); + transferProfile(erc725identity, managementWallet) { + return this.blockchain.transferProfile(erc725identity, managementWallet); } /** diff --git a/modules/Blockchain/Ethereum/abi/profile.json b/modules/Blockchain/Ethereum/abi/profile.json index 4c237b43f..41a1669d7 100644 --- a/modules/Blockchain/Ethereum/abi/profile.json +++ b/modules/Blockchain/Ethereum/abi/profile.json @@ -17,25 +17,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "constant": false, - "inputs": [ - { - "name": "oldIdentity", - "type": "address" - } - ], - "name": "transferProfile", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, { "constant": true, "inputs": [], @@ -92,6 +73,29 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "oldIdentity", + "type": "address" + }, + { + "name": "managementWallet", + "type": "address" + } + ], + "name": "transferProfile", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": true, "inputs": [], diff --git a/modules/Blockchain/Ethereum/contracts/Profile.sol b/modules/Blockchain/Ethereum/contracts/Profile.sol index 4b633134c..c50ac6970 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -77,10 +77,11 @@ contract Profile { } } - function transferProfile(address oldIdentity) public returns(address){ + function transferProfile(address oldIdentity, address managementWallet) public returns(address){ require(ERC725(oldIdentity).keyHasPurpose(keccak256(abi.encodePacked(msg.sender)), 1), "Sender does not have management permission for identity!"); + require(managementWallet != address(0)); - Identity newIdentity = new Identity(msg.sender, msg.sender); + Identity newIdentity = new Identity(msg.sender, managementWallet); emit IdentityCreated(msg.sender, address(newIdentity)); ProfileStorage profileStorage = ProfileStorage(hub.profileStorageAddress()); diff --git a/modules/Blockchain/Ethereum/index.js b/modules/Blockchain/Ethereum/index.js index 1a1c208a6..d540f1fa3 100644 --- a/modules/Blockchain/Ethereum/index.js +++ b/modules/Blockchain/Ethereum/index.js @@ -1024,18 +1024,19 @@ class Ethereum { /** * Transfers identity to new address. * @param {string} - erc725identity + * @param {string} - managementWallet */ - transferProfile(erc725identity) { + transferProfile(erc725identity, managementWallet) { const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), gasPrice: this.web3.utils.toHex(this.config.gas_price), to: this.profileContractAddress, }; - this.log.trace(`transferProfile (${erc725identity})`); + this.log.trace(`transferProfile (${erc725identity}, ${managementWallet})`); return this.transactions.queueTransaction( this.profileContractAbi, 'transferProfile', - [erc725identity], options, + [erc725identity, managementWallet], options, ); } diff --git a/modules/service/profile-service.js b/modules/service/profile-service.js index 1541c9209..3249a8f84 100644 --- a/modules/service/profile-service.js +++ b/modules/service/profile-service.js @@ -182,7 +182,10 @@ class ProfileService { if (await this.blockchain.isErc725IdentityOld(this.config.erc725Identity)) { this.logger.important('Old profile detected. Upgrading to new one.'); try { - const result = await this.blockchain.transferProfile(this.config.erc725Identity); + const result = await this.blockchain.transferProfile( + this.config.erc725Identity, + this.config.management_wallet, + ); const newErc725Identity = Utilities.normalizeHex(result.logs[1].data.substr( result.logs[1].data.length - 40, From 9edba37f871b4d29a705817ed1dbd6aa60f1cbd0 Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Mon, 17 Dec 2018 01:45:25 +0100 Subject: [PATCH 40/48] Version 2.0.40 --- 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 6b76bb467..de626368c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.39", + "version": "2.0.40", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c30a0f337..d4138c074 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.39", + "version": "2.0.40", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From 05e178c4db446b775af8224692ddc705658b435f Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Mon, 17 Dec 2018 02:06:22 +0100 Subject: [PATCH 41/48] Fetch identity from event --- config/config.json | 2 +- modules/service/profile-service.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/config.json b/config/config.json index 9b13d82e5..6f7277585 100644 --- a/config/config.json +++ b/config/config.json @@ -55,7 +55,7 @@ "network_id": "rinkeby", "gas_limit": "2000000", "gas_price": "20000000000", - "hub_contract_address": "0xa13635b8D91BCdEC59067eE2Da7A17292578bB08", + "hub_contract_address": "0x056f4DA796C00766061158A01cE068D912be9c89", "rpc_node_host": "https://rinkeby.infura.io/1WRiEqAQ9l4SW6fGdiDt", "rpc_node_port": "", "plugins": [ diff --git a/modules/service/profile-service.js b/modules/service/profile-service.js index 3249a8f84..893d36f95 100644 --- a/modules/service/profile-service.js +++ b/modules/service/profile-service.js @@ -187,8 +187,8 @@ class ProfileService { this.config.management_wallet, ); const newErc725Identity = - Utilities.normalizeHex(result.logs[1].data.substr( - result.logs[1].data.length - 40, + Utilities.normalizeHex(result.logs[result.logs.length - 1].data.substr( + result.logs[result.logs.length - 1].data.length - 40, 40, )); From 299a898d5ca1384f7e6424e0c8a115b63c247f54 Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Mon, 17 Dec 2018 02:07:32 +0100 Subject: [PATCH 42/48] Version 2.0.41 --- 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 de626368c..ee8449dae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.40", + "version": "2.0.41", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d4138c074..5af89f2eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.40", + "version": "2.0.41", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From 96c6fd0f32b0d403edf1593b1b7ded39cd35084c Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Mon, 17 Dec 2018 10:39:43 +0100 Subject: [PATCH 43/48] Add PaidOut event to holding contract (#828) --- modules/Blockchain/Ethereum/abi/holding.json | 22 +++++++++++++++++++ .../Blockchain/Ethereum/contracts/Holding.sol | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/modules/Blockchain/Ethereum/abi/holding.json b/modules/Blockchain/Ethereum/abi/holding.json index cb1ba0337..12f612237 100644 --- a/modules/Blockchain/Ethereum/abi/holding.json +++ b/modules/Blockchain/Ethereum/abi/holding.json @@ -308,6 +308,28 @@ "name": "OfferFinalized", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "offerId", + "type": "bytes32" + }, + { + "indexed": false, + "name": "holder", + "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "PaidOut", + "type": "event" + }, { "anonymous": false, "inputs": [ diff --git a/modules/Blockchain/Ethereum/contracts/Holding.sol b/modules/Blockchain/Ethereum/contracts/Holding.sol index f663fcf1d..7d4445827 100644 --- a/modules/Blockchain/Ethereum/contracts/Holding.sol +++ b/modules/Blockchain/Ethereum/contracts/Holding.sol @@ -26,6 +26,8 @@ contract Holding is Ownable { event OfferCreated(bytes32 offerId, bytes32 dataSetId, bytes32 dcNodeId, uint256 holdingTimeInMinutes, uint256 dataSetSizeInBytes, uint256 tokenAmountPerHolder, uint256 litigationIntervalInMinutes); event OfferFinalized(bytes32 offerId, address holder1, address holder2, address holder3); + event PaidOut(bytes32 offerId, address holder, uint256 amount); + function createOffer(address identity, uint256 dataSetId, uint256 dataRootHash, uint256 redLitigationHash, uint256 greenLitigationHash, uint256 blueLitigationHash, uint256 dcNodeId, uint256 holdingTimeInMinutes, uint256 tokenAmountPerHolder, uint256 dataSetSizeInBytes, uint256 litigationIntervalInMinutes) public { @@ -190,6 +192,7 @@ contract Holding is Ownable { Profile(hub.profileAddress()).transferTokens(holdingStorage.getOfferCreator(bytes32(offerId)), identity, amountToTransfer); holdingStorage.setHolderPaidAmount(bytes32(offerId), identity, amountToTransfer); + emit PaidOut(bytes32(offerId), identity, amountToTransfer); } function payOutMultiple(address identity, bytes32[] offerIds) @@ -215,6 +218,7 @@ contract Holding is Ownable { Profile(hub.profileAddress()).transferTokens(holdingStorage.getOfferCreator(offerIds[i]), identity, amountToTransfer); holdingStorage.setHolderPaidAmount(bytes32(offerIds[i]), identity, amountToTransfer); + emit PaidOut(bytes32(offerIds[i]), identity, amountToTransfer); } } From 3c72b4cb82e635d2f94686986bbe7e6010a339dd Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Mon, 17 Dec 2018 10:41:16 +0100 Subject: [PATCH 44/48] Version 2.0.42 --- 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 ee8449dae..3df16c479 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.41", + "version": "2.0.42", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5af89f2eb..df44e71e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.41", + "version": "2.0.42", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From f91ccc94c941f2ec1a8fd57080da055099745168 Mon Sep 17 00:00:00 2001 From: Uros Kukic <33048701+Kuki145@users.noreply.github.com> Date: Mon, 17 Dec 2018 15:52:22 +0100 Subject: [PATCH 45/48] Add nodeId to IdentityTransferred event (#829) * Add nodeId to IdentityTransferred event * Move nodeId as the first argument * Move nodeId as first argument in event --- modules/Blockchain/Ethereum/abi/profile.json | 5 +++++ modules/Blockchain/Ethereum/contracts/Profile.sol | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/Blockchain/Ethereum/abi/profile.json b/modules/Blockchain/Ethereum/abi/profile.json index 41a1669d7..2ecf86e1e 100644 --- a/modules/Blockchain/Ethereum/abi/profile.json +++ b/modules/Blockchain/Ethereum/abi/profile.json @@ -292,6 +292,11 @@ { "anonymous": false, "inputs": [ + { + "indexed": false, + "name": "nodeId", + "type": "bytes20" + }, { "indexed": false, "name": "oldIdentity", diff --git a/modules/Blockchain/Ethereum/contracts/Profile.sol b/modules/Blockchain/Ethereum/contracts/Profile.sol index c50ac6970..e87102dd7 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -29,7 +29,7 @@ contract Profile { event ProfileCreated(address profile, uint256 initialBalance); event IdentityCreated(address profile, address newIdentity); - event IdentityTransferred(address oldIdentity, address newIdentity); + event IdentityTransferred(bytes20 nodeId, address oldIdentity, address newIdentity); event TokenDeposit(address profile, uint256 amount); event TokensDeposited(address profile, uint256 amountDeposited, uint256 newBalance); @@ -101,7 +101,7 @@ contract Profile { profileStorage.setNodeId(oldIdentity, bytes32(0)); profileStorage.setReputation(oldIdentity, 0); - emit IdentityTransferred(oldIdentity, newIdentity); + emit IdentityTransferred(bytes20(profileStorage.getNodeId(address(newIdentity))), oldIdentity, address(newIdentity)); return address(newIdentity); } From bd604aa1746e5e3d75dd0c24f35dc46db651c2cd Mon Sep 17 00:00:00 2001 From: Nebojsa Obradovic Date: Mon, 17 Dec 2018 15:54:53 +0100 Subject: [PATCH 46/48] Version 2.0.43 --- 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 3df16c479..d4774f501 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.42", + "version": "2.0.43", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index df44e71e5..6c0da6bbf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.42", + "version": "2.0.43", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { From 324713a2a4d5fe24144c6486b38d832402de0959 Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Mon, 17 Dec 2018 17:00:07 +0100 Subject: [PATCH 47/48] change exit code (#830) --- modules/command/command-executor.js | 2 +- testnet/supervisord.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/command/command-executor.js b/modules/command/command-executor.js index e9087502d..000856956 100644 --- a/modules/command/command-executor.js +++ b/modules/command/command-executor.js @@ -50,7 +50,7 @@ class CommandExecutor { } catch (e) { this.logger.error(`Something went really wrong! OT-node shutting down... ${e}`); this.notifyError(e); - process.exit(-1); + process.exit(1); } callback(); diff --git a/testnet/supervisord.conf b/testnet/supervisord.conf index f42c664d2..a98c84ec3 100644 --- a/testnet/supervisord.conf +++ b/testnet/supervisord.conf @@ -7,7 +7,7 @@ logfile_maxbytes=0 command=bash -c "node /ot-node/testnet/register-node.js --configDir=/ot-node/data/ | tee -a complete-node.log" redirect_stderr=true autorestart=unexpected -exitcodes=0,-1,1,134 +exitcodes=0,1,134 stdout_logfile=/dev/fd/1 stdout_logfile_maxbytes=0 From 87d8f890e63379c62c73bf5d58b750ec345da989 Mon Sep 17 00:00:00 2001 From: Janko Simonovic Date: Mon, 17 Dec 2018 17:05:34 +0100 Subject: [PATCH 48/48] bump version --- 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 d4774f501..fa45ddc71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.43", + "version": "2.0.44", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6c0da6bbf..6033ec77d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.43", + "version": "2.0.44", "description": "OriginTrail node", "main": ".eslintrc.js", "config": {