Commit some new networking code, adding integration, broker still not 100%, hasn't been committed
This commit is contained in:
parent
1af6d56e2d
commit
050e69e23f
18 changed files with 1829 additions and 359 deletions
|
@ -20,8 +20,21 @@ function getData(block, key) {
|
|||
}
|
||||
}
|
||||
|
||||
const acceptableMembers = new Set();
|
||||
acceptableMembers.add("timestamp");
|
||||
acceptableMembers.add("lastHash");
|
||||
acceptableMembers.add("hash");
|
||||
acceptableMembers.add("reward");
|
||||
acceptableMembers.add("payments");
|
||||
acceptableMembers.add("sensorRegistrations");
|
||||
acceptableMembers.add("brokerRegistrations");
|
||||
acceptableMembers.add("integrations");
|
||||
acceptableMembers.add("compensations");
|
||||
acceptableMembers.add("nonce");
|
||||
acceptableMembers.add("difficulty");
|
||||
|
||||
class Block {
|
||||
constructor(timestamp, lastHash, hash, reward, payments, sensorRegistrations, brokerRegistrations, integrations, nonce, difficulty) {
|
||||
constructor(timestamp, lastHash, hash, reward, payments, sensorRegistrations, brokerRegistrations, integrations, compensations, nonce, difficulty) {
|
||||
this.timestamp = timestamp;
|
||||
this.lastHash = lastHash;
|
||||
this.hash = hash;
|
||||
|
@ -38,6 +51,9 @@ class Block {
|
|||
if (integrations !== null && integrations.length !== 0) {
|
||||
this.integrations = integrations;
|
||||
}
|
||||
if (compensations !== null && compensations.length !== 0) {
|
||||
this.compensations = compensations;
|
||||
}
|
||||
this.nonce = nonce;
|
||||
if (difficulty === undefined) {
|
||||
this.difficulty = DIFFICULTY;
|
||||
|
@ -62,6 +78,10 @@ class Block {
|
|||
return getData(block, "integrations");
|
||||
}
|
||||
|
||||
static getCompensations(block) {
|
||||
return getData(block, "compensations");
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `Block -
|
||||
Timestamp : ${this.timestamp}
|
||||
|
@ -78,7 +98,7 @@ class Block {
|
|||
return new this('Genesis time', '-----', 'f1r57-h45h', null, null, null, null, null, 0, DIFFICULTY);
|
||||
}
|
||||
|
||||
static hash(timestamp, lastHash, reward, payments, sensorRegistrations, brokerRegistrations, integrations, nonce, difficulty) {
|
||||
static hash(timestamp, lastHash, reward, payments, sensorRegistrations, brokerRegistrations, integrations, compensations, nonce, difficulty) {
|
||||
//backwards compatible hashing:
|
||||
//if we add a new type of thing to the chain, the hash of previous blocks won't change as it will be undefined
|
||||
let hashing = `${timestamp}${lastHash}${nonce}${difficulty}${reward}`;
|
||||
|
@ -86,6 +106,7 @@ class Block {
|
|||
hashing = concatIfNotUndefined(hashing, 'sensorRegistrations', sensorRegistrations);
|
||||
hashing = concatIfNotUndefined(hashing, 'brokerRegistrations', brokerRegistrations);
|
||||
hashing = concatIfNotUndefined(hashing, 'integrations', integrations);
|
||||
hashing = concatIfNotUndefined(hashing, 'compensations', compensations);
|
||||
|
||||
return ChainUtil.hash(hashing).toString();
|
||||
}
|
||||
|
@ -99,6 +120,7 @@ class Block {
|
|||
block.sensorRegistrations,
|
||||
block.brokerRegistrations,
|
||||
block.integrations,
|
||||
block.compensations,
|
||||
block.nonce,
|
||||
block.difficulty);
|
||||
}
|
||||
|
@ -127,7 +149,7 @@ class Block {
|
|||
}
|
||||
}
|
||||
|
||||
static debugMine(lastBlock, reward, payments, sensorRegistrations,brokerRegistrations,integrations) {
|
||||
static debugMine(lastBlock, reward, payments, sensorRegistrations,brokerRegistrations,integrations,compensations) {
|
||||
const timestamp = Date.now();
|
||||
const difficulty = Block.adjustDifficulty(lastBlock, timestamp);
|
||||
|
||||
|
@ -144,6 +166,7 @@ class Block {
|
|||
sensorRegistrations,
|
||||
brokerRegistrations,
|
||||
integrations,
|
||||
compensations,
|
||||
nonce,
|
||||
difficulty);
|
||||
} while (hash.substring(0, difficulty) !== '0'.repeat(difficulty));
|
||||
|
@ -157,9 +180,116 @@ class Block {
|
|||
sensorRegistrations,
|
||||
brokerRegistrations,
|
||||
integrations,
|
||||
compensations,
|
||||
nonce,
|
||||
difficulty);
|
||||
}
|
||||
|
||||
static validateIsBlock(block) {
|
||||
if (!(block instanceof Object)) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "Is not an object"
|
||||
};
|
||||
}
|
||||
|
||||
for (const key in block) {
|
||||
if (!acceptableMembers.has(key)) {
|
||||
return {
|
||||
result: false,
|
||||
reason: `Block has key not in acceptable members`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!("timestamp" in block)) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "Block doesn't have a timestamp"
|
||||
};
|
||||
}
|
||||
const timestampRes = ChainUtil.validateIsIntegerWithMin(block.timestamp, 0);
|
||||
if (!timestampRes.result) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "Timestamp validation failed: " + timestampRes.reason
|
||||
};
|
||||
}
|
||||
|
||||
if (!("lastHash" in block)) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "Block doesn't have lastHash"
|
||||
};
|
||||
}
|
||||
const lastHashRes = ChainUtil.validateIsString(block.lastHash);
|
||||
if (!lastHashRes.result) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "lastHash validation failed: " + lastHashRes.reason
|
||||
};
|
||||
}
|
||||
|
||||
if (!("hash" in block)) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "Block doesn't have hash"
|
||||
};
|
||||
}
|
||||
const hashRes = ChainUtil.validateIsString(block.hash);
|
||||
if (!hashRes.result) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "hash validation failed: " + hashRes.reason
|
||||
};
|
||||
}
|
||||
|
||||
if (!("reward" in block)) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "Block doesn't have reward"
|
||||
};
|
||||
}
|
||||
const rewardRes = ChainUtil.validateIsPublicKey(block.reward);
|
||||
if (!rewardRes.result) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "reward validation failed: " + rewardRes.reason
|
||||
};
|
||||
}
|
||||
|
||||
if (!("nonce" in block)) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "Block doesn't have nonce"
|
||||
};
|
||||
}
|
||||
const nonceRes = ChainUtil.validateIsIntegerWithMin(block.nonce);
|
||||
if (!nonceRes.result) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "nonce validation failed: " + nonceRes.reason
|
||||
};
|
||||
}
|
||||
|
||||
if (!("difficulty" in block)) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "Block doesn't have difficulty"
|
||||
};
|
||||
}
|
||||
const difficultyRes = ChainUtil.validateIsIntegerWithMin(block.difficulty);
|
||||
if (!difficultyRes.result) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "difficulty validation failed: " + difficultyRes.reason
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
result: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Block;
|
File diff suppressed because it is too large
Load diff
|
@ -21,14 +21,14 @@ function validateMetadata(t) {
|
|||
|
||||
for (const triple of t) {
|
||||
switch (triple.p) {
|
||||
case "IoT device metadata/Cost_of_Using_IoT_Devices/Cost_Per_Minute": costPerMinute.push(triple); break;
|
||||
case "IoT device metadata/Cost_of_Using_IoT_Devices/Cost_Per_Kbyte": costPerKB.push(triple); break;
|
||||
case "http://SSM/Cost_of_Using_IoT_Devices/Cost_Per_Minute": costPerMinute.push(triple); break;
|
||||
case "http://SSM/Cost_of_Using_IoT_Devices/Cost_Per_Kbyte": costPerKB.push(triple); break;
|
||||
case "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":
|
||||
if (triple.o === "SSM/Broker") {
|
||||
if (triple.o === "http://SSM/Broker") {
|
||||
isBroker.push(triple.s);
|
||||
}
|
||||
break;
|
||||
case "IoT device metadata/Integration/Endpoint": integrationEndpoint.push(triple); break;
|
||||
case "http://SSM/Integration/Endpoint": integrationEndpoint.push(triple); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
65
blockchain/compensation.js
Normal file
65
blockchain/compensation.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
const ChainUtil = require('../chain-util');
|
||||
const Integration = require('./integration');
|
||||
|
||||
const integrationValidation = {
|
||||
input: ChainUtil.valdiateIsPublicKey,
|
||||
counter: ChainUtil.createValidateIsIntegerWithMin(1)
|
||||
};
|
||||
|
||||
const baseValidation = {
|
||||
input: ChainUtil.validateIsPublicKey,
|
||||
brokerName: ChainUtil.validateIsString,
|
||||
integration: ChainUtil.createValidateObject(integrationValidation),
|
||||
signature: ChainUtil.validateIsSignature
|
||||
};
|
||||
|
||||
class Compensation {
|
||||
constructor(senderKeyPair, brokerName, integration) {
|
||||
const verifyIntegration = Integration.verify(integration);
|
||||
|
||||
if (!verifyIntegration.result) {
|
||||
throw new Error(verifyIntegration.reason);
|
||||
}
|
||||
|
||||
this.input = senderKeyPair.getPublic().encode('hex');
|
||||
this.brokerName = brokerName;
|
||||
this.integration = {
|
||||
input: integration.input,
|
||||
counter: integration.counter
|
||||
};
|
||||
this.signature = senderKeyPair.sign(Compensation.hashToSign(this));
|
||||
|
||||
const verification = Compensation.verify(this);
|
||||
if (!verification.result) {
|
||||
throw new Error(verification.reason);
|
||||
}
|
||||
}
|
||||
|
||||
static hashToSign(transaction) {
|
||||
return ChainUtil.hash([
|
||||
transaction.input,
|
||||
transaction.brokerName,
|
||||
transaction.integration]);
|
||||
}
|
||||
|
||||
static verify(transaction) {
|
||||
const validationRes = ChainUtil.validateObject(transaction, baseValidation);
|
||||
if (!validationRes.result) {
|
||||
return validationRes;
|
||||
}
|
||||
|
||||
const verifyRes = ChainUtil.verifySignature(
|
||||
transaction.input,
|
||||
transaction.signature,
|
||||
Compensation.hashToSign(transaction));
|
||||
if (!verifyRes.result) {
|
||||
return verifyRes;
|
||||
}
|
||||
|
||||
return {
|
||||
result: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Compensation;
|
|
@ -1,23 +1,32 @@
|
|||
const ChainUtil = require('../chain-util');
|
||||
const SeedRandom = require('seedrandom');
|
||||
|
||||
const outputValidation = {
|
||||
publicKey: ChainUtil.validateIsPublicKey,
|
||||
sensor: ChainUtil.validateIsString,
|
||||
amount: ChainUtil.createValidateIsIntegerWithMin(1),
|
||||
counter: ChainUtil.createValidateIsIntegerWithMin(1)
|
||||
};
|
||||
|
||||
function validateOutputs(t) {
|
||||
if (!ChainUtil.validateArray(t, (output) => {
|
||||
return ChainUtil.validateObject(output, outputValidation).result;
|
||||
})) {
|
||||
return false;
|
||||
const validateArrayRes = ChainUtil.validateArray(t, (output) => {
|
||||
return ChainUtil.validateObject(output, outputValidation);
|
||||
});
|
||||
|
||||
if (!validateArrayRes.result) {
|
||||
return validateArrayRes;
|
||||
}
|
||||
|
||||
if (t.outputs.length <= 0) {
|
||||
return false;
|
||||
if (t.length <= 0) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "Integration must have at least 1 output"
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
return {
|
||||
result: true
|
||||
};
|
||||
}
|
||||
|
||||
const baseValidation = {
|
||||
|
@ -25,17 +34,19 @@ const baseValidation = {
|
|||
counter: ChainUtil.createValidateIsIntegerWithMin(1),
|
||||
rewardAmount: ChainUtil.createValidateIsIntegerWithMin(0),
|
||||
outputs: validateOutputs,
|
||||
witnessCount: ChainUtil.createValidateIsIntegerWithMin(0),
|
||||
signature: ChainUtil.validateIsSignature
|
||||
};
|
||||
|
||||
class Integration {
|
||||
constructor(senderKeyPair, counter, outputs, rewardAmount) {
|
||||
constructor(senderKeyPair, counter, outputs, witnessCount, rewardAmount) {
|
||||
this.input = senderKeyPair.getPublic().encode('hex');
|
||||
this.counter = counter;
|
||||
this.rewardAmount = rewardAmount;
|
||||
this.outputs = outputs;
|
||||
this.signature = senderKeyPair.sign(Integration.hashToSign(this));
|
||||
this.witnessCount = witnessCount;
|
||||
|
||||
this.signature = senderKeyPair.sign(Integration.hashToSign(this));
|
||||
|
||||
const verification = Integration.verify(this);
|
||||
if (!verification.result) {
|
||||
|
@ -43,31 +54,33 @@ class Integration {
|
|||
}
|
||||
}
|
||||
|
||||
static createOutput(recipientPublicKey, sensorId, amount) {
|
||||
static createOutput(recipientPublicKey, sensorId, amount, counter) {
|
||||
return {
|
||||
publicKey: recipientPublicKey,
|
||||
sensor: sensorId,
|
||||
amount: amount
|
||||
amount: amount,
|
||||
counter: counter
|
||||
};
|
||||
}
|
||||
|
||||
static hashToSign(registration) {
|
||||
static hashToSign(integration) {
|
||||
return ChainUtil.hash([
|
||||
registration.counter,
|
||||
registration.rewardAmount,
|
||||
registration.outputs]);
|
||||
integration.counter,
|
||||
integration.rewardAmount,
|
||||
integration.witnesses,
|
||||
integration.outputs]);
|
||||
}
|
||||
|
||||
static verify(registration) {
|
||||
const validationRes = ChainUtil.validateObject(registration, baseValidation);
|
||||
static verify(integration) {
|
||||
const validationRes = ChainUtil.validateObject(integration, baseValidation);
|
||||
if (!validationRes.result) {
|
||||
return validationRes;
|
||||
}
|
||||
|
||||
const verifyRes = ChainUtil.verifySignature(
|
||||
registration.input,
|
||||
registration.signature,
|
||||
Integration.hashToSign(registration));
|
||||
integration.input,
|
||||
integration.signature,
|
||||
Integration.hashToSign(integration));
|
||||
if (!verifyRes.result) {
|
||||
return verifyRes;
|
||||
}
|
||||
|
@ -76,6 +89,45 @@ class Integration {
|
|||
result: true
|
||||
};
|
||||
}
|
||||
|
||||
static chooseWitnesses(integration, brokerList) {
|
||||
const brokerListCopy = [...brokerList];
|
||||
brokerListCopy.sort();
|
||||
|
||||
const witnessCount = integration.witnessCount;
|
||||
|
||||
if (witnessCount > brokerList.length) {
|
||||
return {
|
||||
result: false,
|
||||
reason: "Not enough brokers for the number of witnesses requested"
|
||||
};
|
||||
}
|
||||
|
||||
if (witnessCount === brokerList.length) {
|
||||
return {
|
||||
result: true,
|
||||
witnesses: brokerListCopy
|
||||
};
|
||||
}
|
||||
|
||||
const rng = new SeedRandom.alea(integration.signature, Integration.hashToSign(integration));
|
||||
|
||||
const witnesses = [];
|
||||
|
||||
for (var i = 0; i < witnessCount; ++i) {
|
||||
const chosen = Math.floor(rng() * brokerListCopy.length);
|
||||
|
||||
witnesses.push(brokerListCopy[chosen]);
|
||||
brokerListCopy[chosen] = brokerListCopy[brokerListCopy.length - 1];
|
||||
brokerListCopy.pop();
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
result: true,
|
||||
witnesses: witnesses
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Integration;
|
43
blockchain/integration.test.js
Normal file
43
blockchain/integration.test.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
const Integration = require('./integration');
|
||||
const ChainUtil = require('../chain-util');
|
||||
|
||||
function createDummyIntegration(keyPair, witnesses) {
|
||||
return new Integration(
|
||||
keyPair,
|
||||
1,
|
||||
[Integration.createOutput(keyPair.getPublic().encode('hex'), 'a', 5, 1)],
|
||||
witnesses,
|
||||
0);
|
||||
}
|
||||
|
||||
describe('Integration', () => {
|
||||
let keyPair;
|
||||
|
||||
beforeEach(() => {
|
||||
keyPair = ChainUtil.genKeyPair();
|
||||
});
|
||||
|
||||
it("Choose witnesses doesn't care about brokers ordering, 1 witness", () => {
|
||||
const brokers_f = ['a', 'b', 'c'];
|
||||
const brokers_b = ['c', 'b', 'a'];
|
||||
|
||||
const integration = createDummyIntegration(keyPair, 1);
|
||||
expect(Integration.chooseWitnesses(integration, brokers_f)).toEqual(Integration.chooseWitnesses(integration, brokers_b));
|
||||
});
|
||||
|
||||
it("Choose witnesses doesn't care about brokers ordering, 2 witness", () => {
|
||||
const brokers_f = ['a', 'b', 'c'];
|
||||
const brokers_b = ['c', 'b', 'a'];
|
||||
|
||||
const integration = createDummyIntegration(keyPair, 2);
|
||||
expect(Integration.chooseWitnesses(integration, brokers_f)).toEqual(Integration.chooseWitnesses(integration, brokers_b));
|
||||
});
|
||||
|
||||
it("Choose witnesses doesn't care about brokers ordering, 3 witness", () => {
|
||||
const brokers_f = ['a', 'b', 'c'];
|
||||
const brokers_b = ['c', 'b', 'a'];
|
||||
|
||||
const integration = createDummyIntegration(keyPair, 3);
|
||||
expect(Integration.chooseWitnesses(integration, brokers_f)).toEqual(Integration.chooseWitnesses(integration, brokers_b));
|
||||
});
|
||||
});
|
|
@ -6,17 +6,23 @@ const outputValidation = {
|
|||
};
|
||||
|
||||
function validateOutputs(t) {
|
||||
if (!ChainUtil.validateArray(t, function (output) {
|
||||
return ChainUtil.validateObject(output, outputValidation).result;
|
||||
})) {
|
||||
return false;
|
||||
let validateRes = ChainUtil.validateArray(t, function (output) {
|
||||
return ChainUtil.validateObject(output, outputValidation);
|
||||
});
|
||||
if (!validateRes.result) {
|
||||
return validateRes
|
||||
}
|
||||
|
||||
if (t.length <= 0) {
|
||||
return false;
|
||||
return {
|
||||
result: false,
|
||||
reason: "Outputs length isn't positive"
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
return {
|
||||
result: true
|
||||
};
|
||||
}
|
||||
|
||||
const baseValidation = {
|
||||
|
|
|
@ -20,14 +20,14 @@ function validateMetadata(t) {
|
|||
|
||||
for (const triple of t) {
|
||||
switch (triple.p) {
|
||||
case "IoT device metadata/Cost_of_Using_IoT_Devices/Cost_Per_Minute": costPerMinute.push(triple); break;
|
||||
case "IoT device metadata/Cost_of_Using_IoT_Devices/Cost_Per_Kbyte": costPerKB.push(triple); break;
|
||||
case "http://SSM/Cost_of_Using_IoT_Devices/Cost_Per_Minute": costPerMinute.push(triple); break;
|
||||
case "http://SSM/Cost_of_Using_IoT_Devices/Cost_Per_Kbyte": costPerKB.push(triple); break;
|
||||
case "http://www.w3.org/1999/02/22-rdf-syntax-ns#type":
|
||||
if (triple.o === "http://www.w3.org/ns/sosa/Sensor") {
|
||||
isSensor.push(triple.s);
|
||||
}
|
||||
break;
|
||||
case "IoT device metadata/Integration/Broker": integrationBroker.push(triple); break;
|
||||
case "http://SSM/Integration/Broker": integrationBroker.push(triple); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue