diff --git a/config/config.json b/config/config.json index f278fe963..6f7277585 100644 --- a/config/config.json +++ b/config/config.json @@ -53,9 +53,9 @@ "blockchain": { "blockchain_title": "Ethereum", "network_id": "rinkeby", - "gas_limit": "1500000", + "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": [ @@ -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..20abffc4b 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,48 @@ 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 + * @param {string} - managementWallet + */ + transferProfile(erc725identity, managementWallet) { + return this.blockchain.transferProfile(erc725identity, managementWallet); + } + + /** + * 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); + } + + /** + * PayOut for multiple offers. + * @returns {Promise} + */ + payOutMultiple( + blockchainIdentity, + offerIds, + ) { + return this.blockchain.payOutMultiple( + blockchainIdentity, + offerIds, + ); + } } module.exports = Blockchain; 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/abi/holding.json b/modules/Blockchain/Ethereum/abi/holding.json index 121054a8e..12f612237 100644 --- a/modules/Blockchain/Ethereum/abi/holding.json +++ b/modules/Blockchain/Ethereum/abi/holding.json @@ -170,17 +170,21 @@ "type": "function" }, { - "constant": true, - "inputs": [], - "name": "profile", - "outputs": [ + "constant": false, + "inputs": [ { - "name": "", + "name": "identity", "type": "address" + }, + { + "name": "offerIds", + "type": "bytes32[]" } ], + "name": "payOutMultiple", + "outputs": [], "payable": false, - "stateMutability": "view", + "stateMutability": "nonpayable", "type": "function" }, { @@ -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": [ { @@ -332,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/abi/profile.json b/modules/Blockchain/Ethereum/abi/profile.json index 66478bcbe..2ecf86e1e 100644 --- a/modules/Blockchain/Ethereum/abi/profile.json +++ b/modules/Blockchain/Ethereum/abi/profile.json @@ -17,32 +17,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "constant": false, - "inputs": [ - { - "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": true, "inputs": [], @@ -99,6 +73,43 @@ "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": [], + "name": "version", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, { "constant": false, "inputs": [ @@ -117,6 +128,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": [ @@ -185,36 +226,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "constant": false, - "inputs": [ - { - "name": "payer", - "type": "address" - }, - { - "name": "identity1", - "type": "address" - }, - { - "name": "identity2", - "type": "address" - }, - { - "name": "identity3", - "type": "address" - }, - { - "name": "amount", - "type": "uint256" - } - ], - "name": "reserveTokens", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, { "constant": false, "inputs": [ @@ -233,20 +244,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "constant": true, - "inputs": [], - "name": "profileStorage", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -292,6 +289,28 @@ "name": "IdentityCreated", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "nodeId", + "type": "bytes20" + }, + { + "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/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/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/Holding.sol b/modules/Blockchain/Ethereum/contracts/Holding.sol index ee3c2acce..7d4445827 100644 --- a/modules/Blockchain/Ethereum/contracts/Holding.sol +++ b/modules/Blockchain/Ethereum/contracts/Holding.sol @@ -12,17 +12,12 @@ 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{ + require(hubAddress!=address(0)); hub = Hub(hubAddress); - holdingStorage = HoldingStorage(hub.holdingStorageAddress()); - profileStorage = ProfileStorage(hub.profileStorageAddress()); - profile = Profile(hub.profileAddress()); difficultyOverride = 0; } @@ -31,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 { @@ -48,10 +45,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 +56,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 +78,7 @@ contract Holding is Ownable { difficulty ); - holdingStorage.setOfferLitigationHashes( + HoldingStorage(hub.holdingStorageAddress()).setOfferLitigationHashes( offerId, bytes32(redLitigationHash), bytes32(greenLitigationHash), @@ -97,6 +93,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 +109,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 +123,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.getWithdrawalAmount(payer).add(amount.mul(3)) > profileStorage.getStake(payer) - profileStorage.getStakeReserved(payer)) { + profileStorage.setWithdrawalPending(payer,false); + } + if(profileStorage.getWithdrawalPending(identity1) && profileStorage.getWithdrawalAmount(identity1).add(amount) > profileStorage.getStake(identity1) - profileStorage.getStakeReserved(identity1)) { + profileStorage.setWithdrawalPending(identity1,false); + } + if(profileStorage.getWithdrawalPending(identity2) && profileStorage.getWithdrawalAmount(identity2).add(amount) > profileStorage.getStake(identity2) - profileStorage.getStakeReserved(identity2)) { + profileStorage.setWithdrawalPending(identity2,false); + } + if(profileStorage.getWithdrawalPending(identity3) && profileStorage.getWithdrawalAmount(identity3).add(amount) > profileStorage.getStake(identity3) - profileStorage.getStakeReserved(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(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()); + // Verify holder uint256 amountToTransfer = holdingStorage.getHolderStakedAmount(bytes32(offerId), identity); require(amountToTransfer > 0, "Sender is not holding this data set!"); @@ -143,6 +190,36 @@ 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); + emit PaidOut(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); + + holdingStorage.setHolderPaidAmount(bytes32(offerIds[i]), identity, amountToTransfer); + emit PaidOut(bytes32(offerIds[i]), identity, amountToTransfer); + } } function ecrecovery(bytes32 hash, bytes sig) internal pure returns (address) { 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/Identity.sol b/modules/Blockchain/Ethereum/contracts/Identity.sol index 7aa47ce46..401935cca 100644 --- a/modules/Blockchain/Ethereum/contracts/Identity.sol +++ b/modules/Blockchain/Ethereum/contracts/Identity.sol @@ -22,16 +22,44 @@ 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)); + + otVersion = 1; + + 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 +127,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..e87102dd7 100644 --- a/modules/Blockchain/Ethereum/contracts/Profile.sol +++ b/modules/Blockchain/Ethereum/contracts/Profile.sol @@ -10,7 +10,8 @@ import {ERC20} from './TracToken.sol'; contract Profile { using SafeMath for uint256; Hub public hub; - ProfileStorage public profileStorage; + + uint256 public version = 100; uint256 public minimalStake = 10**21; uint256 public withdrawalTime = 5 minutes; @@ -18,7 +19,6 @@ contract Profile { constructor(address hubAddress) public { require(hubAddress != address(0)); hub = Hub(hubAddress); - profileStorage = ProfileStorage(hub.profileStorageAddress()); } modifier onlyHolding(){ @@ -26,58 +26,90 @@ contract Profile { "Function can only be called by Holding contract!"); _; } - + event ProfileCreated(address profile, uint256 initialBalance); event IdentityCreated(address profile, address newIdentity); + event IdentityTransferred(bytes20 nodeId, address oldIdentity, address newIdentity); event TokenDeposit(address profile, uint256 amount); 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(bytes32 profileNodeId, uint256 initialBalance, bool senderHas725, address identity) public { + + function createProfile(address managementWallet, bytes32 profileNodeId, uint256 initialBalance, bool senderHas725, address identity) public { + 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!"); 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); + 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); } 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); + 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); + } + } + + 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, managementWallet); + 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)); + 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(bytes20(profileStorage.getNodeId(address(newIdentity))), oldIdentity, address(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!"); + + 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"); @@ -92,7 +124,9 @@ 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!"); + + ProfileStorage profileStorage = ProfileStorage(hub.profileStorageAddress()); require(profileStorage.getWithdrawalPending(identity) == false, "Withrdrawal process already pending!"); @@ -114,7 +148,9 @@ 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!"); + + 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!"); @@ -143,62 +179,14 @@ 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 { + require(profile!=address(0)); + 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)); @@ -208,6 +196,9 @@ 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!"); require(profileStorage.getStakeReserved(sender) >= amount, "Sender does not have enough tokens reserved to transfer!"); @@ -229,4 +220,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; diff --git a/modules/Blockchain/Ethereum/index.js b/modules/Blockchain/Ethereum/index.js index 79300326a..d540f1fa3 100644 --- a/modules/Blockchain/Ethereum/index.js +++ b/modules/Blockchain/Ethereum/index.js @@ -4,6 +4,7 @@ const Utilities = require('../../Utilities'); const Models = require('../../../models'); const Op = require('sequelize/lib/operators'); const uuidv4 = require('uuid/v4'); +const ethereumAbi = require('ethereumjs-abi'); class Ethereum { /** @@ -111,6 +112,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 +250,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}, ${blockchainIdentity})`); return this.transactions.queueTransaction( this.profileContractAbi, 'createProfile', [ + managementWallet, Utilities.normalizeHex(profileNodeId), initialBalance, isSender725, blockchainIdentity, ], options, @@ -985,44 +998,93 @@ 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; - } + /** + * 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 + * @param {string} - managementWallet + */ + 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}, ${managementWallet})`); + return this.transactions.queueTransaction( + this.profileContractAbi, 'transferProfile', + [erc725identity, managementWallet], options, + ); + } + + /** + * Returns true if ERC725 contract is older version. + * @param {string} - address of ERC 725 identity. + * @return {Promise} + */ + async isErc725IdentityOld(address) { + const erc725IdentityContract = new this.web3.eth.Contract( + this.erc725IdentityContractAbi, + address, + ); + + try { + await erc725IdentityContract.methods.otVersion().call(); + return false; } catch (error) { - throw new Error(error); + if (error.toString().includes('Couldn\'t decode uint256 from ABI: 0x')) { + return true; + } + throw error; } - return enoughETH && enoughTRAC; } /** - * Token contract address getter - * @return {any|*} + * PayOut for multiple offers. + * @returns {Promise} */ - getTokenContractAddress() { - return this.tokenContractAddress; + payOutMultiple( + blockchainIdentity, + offerIds, + ) { + const gasLimit = offerIds.length * 200000; + 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, + ); } } diff --git a/modules/Blockchain/Ethereum/migrations/2_total_migration.js b/modules/Blockchain/Ethereum/migrations/2_total_migration.js index 8ec514077..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; @@ -197,7 +194,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 +214,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 +254,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/offer.test.js b/modules/Blockchain/Ethereum/test/offer.test.js index c7699fa4e..55c860579 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(); @@ -101,6 +103,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, @@ -276,11 +279,18 @@ 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 @@ -293,6 +303,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(20); + 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, gas: 4000000 }, + ); + + 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(); diff --git a/modules/Blockchain/Ethereum/test/profile.test.js b/modules/Blockchain/Ethereum/test/profile.test.js index 94c3b1deb..bfcb32c55 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, @@ -309,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], 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: { 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..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,14 +502,34 @@ class EventEmitter { } }); - this._on('api-create-offer', async (data) => { - if (!appState.enoughFunds) { - data.response.status(400); + 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: 'Insufficient funds', + 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', }); - return; } + }); + + this._on('api-create-offer', async (data) => { const { dataSetId, holdingTimeInMinutes, @@ -599,30 +620,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 +686,6 @@ class EventEmitter { }); this._on('eth-OfferCreated', async (eventData) => { - if (!appState.enoughFunds) { - return; - } let { dcNodeId, } = eventData; 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/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..c2822d8ee 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/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/migration/m1-payout-all-migration.js b/modules/migration/m1-payout-all-migration.js new file mode 100644 index 000000000..0858e1c8c --- /dev/null +++ b/modules/migration/m1-payout-all-migration.js @@ -0,0 +1,85 @@ +const models = require('../../models'); +const Utilities = require('../Utilities'); + +const BATCH_SIZE = 15; + +/** + * Runs all pending payout commands + */ +class M1PayoutAllMigration { + constructor({ + logger, blockchain, config, notifyError, + }) { + this.logger = logger; + this.config = config; + this.blockchain = blockchain; + this.notifyError = notifyError; + } + + /** + * Run migration + */ + async run() { + /* get all pending payouts */ + let pendingPayOuts = await models.commands.findAll({ + where: { + status: 'PENDING', + name: 'dhPayOutCommand', + }, + }); + + if (pendingPayOuts.length === 0) { + this.logger.warn('No pending payouts.'); + return; + } + + const offerLimit = 60; + 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); + + 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 { + // 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); + } + } + } +} + +module.exports = M1PayoutAllMigration; diff --git a/modules/service/dc-service.js b/modules/service/dc-service.js index 6da349ec2..29b08cd05 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) { @@ -49,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, @@ -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)) { @@ -138,38 +134,13 @@ 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; } } - - 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 63f00e075..893d36f95 100644 --- a/modules/service/profile-service.js +++ b/modules/service/profile-service.js @@ -38,14 +38,33 @@ 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); - 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 +157,51 @@ 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, + this.config.management_wallet, + ); + const newErc725Identity = + Utilities.normalizeHex(result.logs[result.logs.length - 1].data.substr( + result.logs[result.logs.length - 1].data.length - 40, + 40, + )); + + this.logger.important('**************************************************************************'); + 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; + 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..f6bad1282 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); }); @@ -469,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, + }); + }); } /** diff --git a/ot-node.js b/ot-node.js index a8690545c..d334c19ce 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'); @@ -78,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); @@ -327,7 +333,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 +385,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 +420,8 @@ class OTNode { try { await profileService.initProfile(); + await this._runMigration(blockchain); + await profileService.upgradeProfile(); } catch (e) { log.error('Failed to create profile'); console.log(e); @@ -482,6 +461,38 @@ class OTNode { appState.started = true; } + /** + * Run one time migration + * Note: implement migration service + * @deprecated + * @private + */ + async _runMigration(blockchain) { + 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 migration = new M1PayoutAllMigration({ logger: log, blockchain, config }); + + try { + await migration.run(); + log.warn(`One-time payout migration completed. Lasted ${Date.now() - migrationsStartedMills} millisecond(s)`); + + 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); + } + } + + 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/package-lock.json b/package-lock.json index 7b2c24fce..fa45ddc71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.33", + "version": "2.0.44", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 528f80d94..6033ec77d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.33", + "version": "2.0.44", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { 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/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/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..820b85aeb 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; }) @@ -362,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 9ab5f3372..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: { @@ -83,6 +84,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, @@ -103,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, @@ -121,6 +124,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({ @@ -517,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, @@ -534,6 +539,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; }), }); diff --git a/testnet/supervisord.conf b/testnet/supervisord.conf index 1d99051c8..a98c84ec3 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,1,134 stdout_logfile=/dev/fd/1 stdout_logfile_maxbytes=0 [program:arango] command=arangod +autorestart=true stdout_logfile=/dev/null stdout_logfile_maxbytes=0