Skip to content

Commit 07307bc

Browse files
Mariner Release 2.0.55 (#929)
- Prevent remote control to crash finalize command. - Allow disabling auto-payouts (#935)
1 parent 65843d8 commit 07307bc

File tree

9 files changed

+352
-31
lines changed

9 files changed

+352
-31
lines changed

modules/RemoteControl.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,11 @@ class RemoteControl {
367367
async (bid) => {
368368
const holding = await this._findHoldingByBid(bid);
369369

370+
if (holding == null || holding.data_set_id == null) {
371+
this.log.debug('Failed to get holding data for for bid %s.', bid);
372+
return;
373+
}
374+
370375
const dataInfo = await Models.data_info.findOne({
371376
where: {
372377
data_set_id: holding.data_set_id,

modules/command/dh/dh-offer-finalized-command.js

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,26 @@ class DhOfferFinalizedCommand extends Command {
5454
this.logger.important(`I've been chosen for offer ${offerId}.`);
5555

5656
await this.remoteControl.onCompletedBids();
57-
const scheduledTime = (bid.holding_time_in_minutes * 60 * 1000) + (60 * 1000);
58-
return {
59-
commands: [
60-
{
61-
name: 'dhPayOutCommand',
62-
delay: scheduledTime,
63-
retries: 3,
64-
transactional: false,
65-
data: {
66-
offerId,
57+
58+
if (this.config.disableAutoPayouts !== true) {
59+
const scheduledTime =
60+
(bid.holding_time_in_minutes * 60 * 1000) + (60 * 1000);
61+
return {
62+
commands: [
63+
{
64+
name: 'dhPayOutCommand',
65+
delay: scheduledTime,
66+
retries: 3,
67+
transactional: false,
68+
data: {
69+
offerId,
70+
viaAPI: false,
71+
},
6772
},
68-
},
69-
],
70-
};
73+
],
74+
};
75+
}
76+
return Command.empty();
7177
}
7278

7379
bid.status = 'NOT_CHOSEN';

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "origintrail_node",
3-
"version": "2.0.54",
3+
"version": "2.0.55",
44
"description": "OriginTrail node",
55
"main": ".eslintrc.js",
66
"config": {

test/bdd/features/network.feature

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,55 @@ Feature: Test basic network features
120120

121121
@first
122122
Scenario: Bootstraps should have /api/info route enabled
123-
Then 1st bootstrap should reply on info route
123+
Then 1st bootstrap should reply on info route
124+
125+
@first
126+
Scenario: DH payout scenario
127+
Given the replication difficulty is 0
128+
And I setup 5 nodes
129+
And I override configuration for all nodes
130+
| dc_holding_time_in_minutes | 1 |
131+
And I start the nodes
132+
And I use 1st node as DC
133+
And DC imports "importers/xml_examples/Retail/01_Green_to_pink_shipment.xml" as GS1
134+
Given DC initiates the replication for last imported dataset
135+
And I wait for replications to finish
136+
And DC waits for holding time
137+
Then selected DHes should be payed out
138+
139+
@first
140+
Scenario: DH with disabled auto-payouts
141+
Given the replication difficulty is 0
142+
And I setup 5 nodes
143+
And I override configuration for all nodes
144+
| dc_holding_time_in_minutes | 1 |
145+
| disableAutoPayouts | true |
146+
And I start the nodes
147+
And I use 1st node as DC
148+
And DC imports "importers/xml_examples/Retail/01_Green_to_pink_shipment.xml" as GS1
149+
Given DC initiates the replication for last imported dataset
150+
And I wait for replications to finish
151+
And DC waits for holding time
152+
Then selected DHes should not be payed out
153+
154+
@first
155+
Scenario: Node with diff management and operational wallet should successfully start
156+
Given I setup 1 node
157+
And I set 1st node's management wallet to be different then operational wallet
158+
And I start the node
159+
Then default initial token amount should be deposited on 1st node's profile
160+
161+
@third
162+
Scenario: Test repeated offer creation with same dataset
163+
Given the replication difficulty is 0
164+
And I setup 3 nodes
165+
And I start the nodes
166+
And I use 1st node as DC
167+
And DC imports "importers/xml_examples/Retail/01_Green_to_pink_shipment.xml" as GS1
168+
Then DC's last import's hash should be the same as one manually calculated
169+
Given DC initiates the replication for last imported dataset
170+
And I wait for DC to fail to finalize last offer
171+
Given I additionally setup 1 node
172+
And I start additional nodes
173+
Given DC initiates the replication for last imported dataset
174+
And I wait for replications to finish

test/bdd/steps/endpoints.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const {
44
Then, Given,
55
} = require('cucumber');
66
const { expect } = require('chai');
7+
const BN = require('bn.js');
78

89
const httpApiHelper = require('./lib/http-api-helper');
910

@@ -188,3 +189,13 @@ Given(/^([DC|DH|DV]+) calls consensus endpoint for sender: "(\S+)"$/, async func
188189
expect(consensusResponse, 'Should have key called events').to.have.all.keys('events');
189190
this.state.lastConsensusResponse = consensusResponse;
190191
});
192+
193+
Given(/^default initial token amount should be deposited on (\d+)[st|nd|rd|th]+ node's profile$/, async function (nodeIndex) {
194+
expect(nodeIndex, 'Invalid index.').to.be.within(0, this.state.nodes.length);
195+
196+
const balance = await httpApiHelper.apiBalance(this.state.nodes[nodeIndex - 1].state.node_rpc_url, false);
197+
const staked = new BN(balance.profile.staked);
198+
const initialDepositAmount = new BN(this.state.nodes[nodeIndex - 1].options.nodeConfiguration.initial_deposit_amount);
199+
200+
expect(staked.toString()).to.be.equal(initialDepositAmount.toString());
201+
});

test/bdd/steps/lib/http-api-helper.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,45 @@ async function apiNodeInfo(nodeRpcUrl) {
462462
});
463463
}
464464

465+
/**
466+
* @typedef {Object} ApiBalanceInfo
467+
* @property {Object} profile info about profile balance
468+
* @property {string} profile.minimalStake minimal stake
469+
* @property {string} profile.reserved reserved
470+
* @property {string} profile.staked staked
471+
* @property {Object} wallet info about wallet balance
472+
* @property {string} wallet.address node's wallet address
473+
* @property {string} wallet.ethBalance wallet balance in ethers
474+
* @property {string} wallet.tokenBalance wallet balance in tokens
475+
*/
476+
477+
/**
478+
* Fetch api/balance?humanReadable={{humanReadable}}
479+
*
480+
* @param {string} nodeRpcUrl URL in following format http://host:port
481+
* @param {boolean} humanReadable friendly format of response
482+
* @return {Promise.<ApiBalanceInfo>}
483+
*/
484+
async function apiBalance(nodeRpcUrl, humanReadable) {
485+
return new Promise((accept, reject) => {
486+
request(
487+
{
488+
method: 'GET',
489+
headers: { 'Content-Type': 'application/json' },
490+
uri: `${nodeRpcUrl}/api/balance?humanReadable=${humanReadable}`,
491+
json: true,
492+
},
493+
(err, res, body) => {
494+
if (err) {
495+
reject(err);
496+
return;
497+
}
498+
accept(body);
499+
},
500+
);
501+
});
502+
}
503+
465504
module.exports = {
466505
apiImport,
467506
apiImportContent,
@@ -477,4 +516,5 @@ module.exports = {
477516
apiConsensus,
478517
apiTrail,
479518
apiNodeInfo,
519+
apiBalance,
480520
};

test/bdd/steps/lib/otnode.js

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const defaultConfiguration = require('../../../../config/config.json').developme
1616
const uuidRegex = /\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi;
1717
const walletRegex = /\b0x[0-9A-F]{40}\b/gi;
1818
const identityRegex = /\b[0-9A-F]{40}\b/gi;
19+
const identityWithPrefixRegex = /\b0x[0-9A-F]{40}\b/gi;
1920
const offerIdRegex = /\b0x[0-9A-F]{64}\b/gi;
2021
const dataSetRegex = /\b0x[0-9A-F]{64}\b/gi;
2122
const walletAmountRegex = /\b\d+\b/g;
@@ -34,7 +35,8 @@ class OtNode extends EventEmitter {
3435
this.options.configDir = path.join(appDataBaseDir || tmpdir, this.id);
3536
this.options.nodeConfiguration = nodeConfiguration || {};
3637
this.options.nodeConfiguration = deepExtend(
37-
Object.assign({}, defaultConfiguration), // deepExtend changes original object.
38+
{},
39+
defaultConfiguration,
3840
this.options.nodeConfiguration,
3941
);
4042
this.logger = logger || console;
@@ -62,9 +64,13 @@ class OtNode extends EventEmitter {
6264
this.state = {};
6365
this.state.addedBids = []; // List of offer IDs (DH side).
6466
this.state.takenBids = []; // List of offer IDs (DH side).
67+
this.state.pendingLitigationDhIdentities = []; // List of pending litigations (DHs)
68+
this.state.takenReplacements = []; // List of replacement offer IDs (DH side).
6569
// Valid replications (DH side). List of internal offer IDs and their replications DH IDs
6670
// in pairs. { internalOfferId, dhId }.
6771
this.state.replications = [];
72+
// Array of replacement offer IDs
73+
this.state.replacements = [];
6874
// Valid replications (DC side). List of objects { importId, dhWallet }.
6975
this.state.holdingData = [];
7076
// Offers finalized. List of offer IDs.
@@ -149,6 +155,7 @@ class OtNode extends EventEmitter {
149155
stop() {
150156
this.logger.log(`Stopping node ${this.id}.`);
151157
assert(this.isRunning);
158+
this.started = false;
152159
this.process.kill('SIGINT');
153160
}
154161

@@ -211,6 +218,8 @@ class OtNode extends EventEmitter {
211218
const offerId = line.match(offerIdRegex)[0];
212219
} else if (line.match(/Miner found a solution of offer .+\./gi)) {
213220
const offerId = line.match(offerIdRegex)[0];
221+
} else if (line.match(/Not enough DHs submitted/gi)) {
222+
this.emit('not-enough-dhs');
214223
} else if (line.match(/Offer .+ finalized/gi)) {
215224
const offerId = line.match(offerIdRegex)[0];
216225
assert(offerId);
@@ -290,6 +299,51 @@ class OtNode extends EventEmitter {
290299
this.emit('replication-window-closed');
291300
} else if (line.match(/Offer with internal ID .+ for data set .+ written to blockchain. Waiting for DHs\.\.\./gi)) {
292301
this.emit('offer-written-blockchain');
302+
} else if (line.match(/Command dhPayOutCommand and ID .+ processed\./gi)) {
303+
this.emit('dh-pay-out-finalized');
304+
} else if (line.match(/Command dhOfferFinalizedCommand and ID .+ processed\./gi)) {
305+
this.emit('dh-offer-finalized');
306+
} else if (line.match(/Litigation initiated for DH .+ and offer .+\./gi)) {
307+
this.state.litigationStatus = 'LITIGATION_STARTED';
308+
this.emit('dc-litigation-initiated');
309+
} else if (line.match(/Litigation answered for DH .+ and offer .+\./gi)) {
310+
this.emit('dc-litigation-answered');
311+
} else if (line.match(/Litigation answered for offer .+\. DH identity .+/gi)) {
312+
this.emit('dh-litigation-answered');
313+
} else if (line.match(/Litigation completed for DH .+ and offer .+\./gi)) {
314+
this.state.litigationStatus = 'LITIGATION_COMPLETED';
315+
this.emit('dc-litigation-completed');
316+
} else if (line.match(/Litigation already in progress\.\.\. It needs to be completed in order to litigate .+ for offer .+/gi)) {
317+
const dhIdentity = line.match(identityWithPrefixRegex)[0];
318+
this.state.pendingLitigationDhIdentities.push(dhIdentity);
319+
this.emit('dc-litigation-pending');
320+
} else if (line.match(/DH .+ was penalized for the offer .+\./gi)) {
321+
this.emit('dc-litigation-completed-dh-penalized');
322+
} else if (line.match(/DH .+ was not penalized for the offer .+\./gi)) {
323+
this.emit('dc-litigation-completed-dh-not-penalized');
324+
} else if (line.match(/Replacement for DH .+ and offer .+ has been successfully started. Waiting for DHs\.\.\./gi)) {
325+
this.emit('dc-litigation-replacement-started');
326+
} else if (line.match(/Replacement triggered for offer .+\. Litigator .+\./gi)) {
327+
const offerId = line.match(offerIdRegex)[0];
328+
this.state.replacements.push(offerId);
329+
} else if (line.match(/\[DH] Replacement replication finished for offer ID .+/gi)) {
330+
const offerId = line.match(offerIdRegex)[0];
331+
assert(offerId);
332+
this.state.takenReplacements.push(offerId);
333+
this.emit('dh-litigation-replacement-received');
334+
} else if (line.match(/Successfully replaced DH .+ with DH .+ for offer .+/gi)) {
335+
this.emit('dc-litigation-replacement-completed');
336+
} else if (line.match(/Challenge answer .+ sent to .+\./gi)) {
337+
this.emit('dh-challenge-sent');
338+
} else if (line.match(/Not chosen as a replacement for offer .+\./gi)) {
339+
this.emit('dh-not-chosen-as-replacement');
340+
} else if (line.match(/Chosen as a replacement for offer .+\./gi)) {
341+
const offerId = line.match(offerIdRegex)[0];
342+
this.state.takenBids.push(offerId);
343+
this.emit('dh-chosen-as-replacement');
344+
} else if (line.match(/Replication finished for DH node .+/gi)) {
345+
const nodeId = line.match(identityRegex)[0];
346+
this.emit('dh-replication-verified', nodeId);
293347
}
294348
}
295349

@@ -301,6 +355,21 @@ class OtNode extends EventEmitter {
301355
return this.initialized && this.started && !!this.process;
302356
}
303357

358+
/**
359+
* Retruns path to the system.db.
360+
* @return {string} Path.
361+
*/
362+
get systemDbPath() {
363+
return path.join(this.options.configDir, 'system.db');
364+
}
365+
366+
get erc725Identity() {
367+
return JSON.parse(fs.readFileSync(path.join(
368+
this.options.configDir,
369+
'erc725_identity.json',
370+
))).identity;
371+
}
372+
304373
/**
305374
* Returns array of node IDs of nodes that confirmed possession of the data for
306375
* the given query,

0 commit comments

Comments
 (0)