Commit some new networking code, adding integration, broker still not 100%, hasn't been committed

This commit is contained in:
Josip Milovac 2023-04-26 13:02:27 +10:00
parent 1af6d56e2d
commit 050e69e23f
18 changed files with 1829 additions and 359 deletions

View file

@ -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

View file

@ -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;
}
}

View 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;

View file

@ -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;

View 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));
});
});

View file

@ -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 = {

View file

@ -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;
}
}