From 050e69e23f2bdc00b7d300fc8af34dc7883b46b1 Mon Sep 17 00:00:00 2001 From: Josip Milovac Date: Wed, 26 Apr 2023 13:02:27 +1000 Subject: [PATCH] Commit some new networking code, adding integration, broker still not 100%, hasn't been committed --- blockchain/block.js | 136 ++++- blockchain/blockchain.js | 906 ++++++++++++++++++++++-------- blockchain/broker-registration.js | 8 +- blockchain/compensation.js | 65 +++ blockchain/integration.js | 92 ++- blockchain/integration.test.js | 43 ++ blockchain/payment.js | 18 +- blockchain/sensor-registration.js | 6 +- constants.js | 6 + miner/miner-app.js | 54 +- miner/miner.js | 35 +- network/blockchain-prop.js | 283 ++++++++++ network/test.js | 15 + package.json | 1 + ui/wallet-logic.js | 346 ++++++++++++ ui/wallet-ui.html | 92 +++ wallet/wallet-app.js | 75 ++- wallet/wallet.js | 7 +- 18 files changed, 1829 insertions(+), 359 deletions(-) create mode 100644 blockchain/compensation.js create mode 100644 blockchain/integration.test.js create mode 100644 network/blockchain-prop.js create mode 100644 network/test.js create mode 100644 ui/wallet-logic.js create mode 100644 ui/wallet-ui.html diff --git a/blockchain/block.js b/blockchain/block.js index fe73f3c..a0cce76 100644 --- a/blockchain/block.js +++ b/blockchain/block.js @@ -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; \ No newline at end of file diff --git a/blockchain/blockchain.js b/blockchain/blockchain.js index 3cc7903..f36997c 100644 --- a/blockchain/blockchain.js +++ b/blockchain/blockchain.js @@ -5,11 +5,347 @@ const Payment = require('./payment'); const SensorRegistration = require('./sensor-registration'); const BrokerRegistration = require('./broker-registration'); const Integration = require('./integration'); +const Compensation = require('./compensation'); const fs = require('fs'); const ChainUtil = require('../chain-util'); const { MINING_REWARD} = require('../constants'); +function makeIntegrationKey(publicKey, counter) { + return `${publicKey}/${counter}`; +} + +class PropertyHistory { + constructor(backing) { + this.undos = []; + this.current = {}; + if (typeof backing === "undefined") { + this.backing = null; + } else { + this.backing = backing; + } + } + + getClone(key, fallback) { + if (key in this.current) { + return this.current[key]; + } else if (this.backing !== null) { + return this.backing.getClone(key); + } else { + if (typeof fallback === "undefined" || fallback === null) { + return null; + } else { + return fallback; + } + } + } + + undo() { + if (this.undos.length === 0) { + return; + } + + const undoing = this.undos[this.undos.length - 1]; + + for (const key in undoing) { + const value = undoing[key]; + + if (value === null) { + delete this.current[key]; + } else { + this.current[key] = value; + } + } + + this.undos.pop(); + } + + add(adding) { + const undoer = {}; + + for (const key in adding) { + const value = adding[key]; + + //if doesn't exist, is null + const existing = this.getClone(key); + undoer[key] = existing; + this.current[key] = value; + } + + this.undos.push(undoer); + } + + finish() { + if (this.backing === null) { + throw new Error("Finishing Property History with null backing"); + } + + this.backing.undos.push(...this.undos); + Object.assign(this.backing.current, this.current); + + this.backing = null; + } + + clone() { + const returning = new PropertyHistory(); + returning.undos = [...this.undos]; + returning.current = Object.assign({}, this.current); + + return returning; + } +} + +const FALLBACK_BALANCE = { + balance: 0, + counter: 0 +}; + +function getPropertyClone(propertyHistory, key, fallback) { + const found = propertyHistory.getClone(key); + if (found !== null) { + return Object.assign({}, found); + } else { + if (typeof fallback === "undefined" || fallback === null) { + return null; + } else { + return Object.assign({}, fallback); + } + } +} + +class Updater { + constructor(parent, block) { + this.parent = parent; + this.block = block; + this.balances = {}; + this.sensors = {}; + this.brokers = {}; + this.integrations = {}; + this.store = new N3.Store(); + + if (block !== null) { + this.store.addQuad( + DataFactory.namedNode(this.block.hash), + DataFactory.namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), + DataFactory.namedNode("http://SSM/Block")); + + this.store.addQuad( + DataFactory.namedNode(this.block.hash), + DataFactory.namedNode("http://SSM/lastBlock"), + DataFactory.namedNode(this.parent.getBlockFromTop(0).hash)); + } + } + + getBalanceCopy(publicKey) { + if (publicKey in this.balances) { + return Object.assign({}, this.balances[publicKey]); + } else { + return this.parent.getBalanceCopy(publicKey); + } + } + + setBalance(publicKey, balance) { + this.balances[publicKey] = balance; + } + + getSensorCopy(key) { + if (key in this.sensors) { + return Object.assign({}, this.sensors[key]); + } else { + return this.parent.getSensorCopy(key); + } + } + + setSensor(key, sensor) { + this.sensors[key] = sensor; + } + + getBrokerCopy(key) { + if (key in this.brokers) { + return Object.assign({}, this.brokers[key]); + } else { + return this.parent.getBrokerCopy(key); + } + } + + setBroker(key, broker) { + this.brokers[key] = broker; + } + + getBrokerPublicKeys() { + const keys = this.parent.getBrokerPublicKeysSet(); + + for (const [key, value] of this.brokers) { + keys.add(value.input); + } + + return Array.from(keys); + } + + getIntegrationCopy(key) { + if (key in this.integrations) { + return Object.assign({}, this.integrations[key]); + } else { + return this.parent.getIntegrationCopy(key); + } + } + + setIntegration(key, integration) { + this.integrations[key] = integration; + } + + finish() { + if (this.parent === null) { + throw new Error("Finishing Blockchain Metadata with null parent"); + } + if (this.block === null) { + throw new Error("Finish Blockchain Metadata with a null block"); + } + + this.parent.blocks.push(this.block); + this.parent.balances.add(this.balances); + this.parent.sensors.add(this.sensors); + this.parent.brokers.add(this.brokers); + this.parent.integrations.add(this.integrations); + this.parent.stores.push(this.store); + + this.parent = null; + } +} + +class Chain { + constructor(parent) { + if (typeof parent === "undefined" || parent === null) { + this.parent = null; + this.blocks = [Block.genesis()]; + this.balances = new PropertyHistory(); + this.sensors = new PropertyHistory(); + this.brokers = new PropertyHistory(); + this.integrations = new PropertyHistory(); + } else { + this.parent = parent; + this.blocks = []; + this.balances = new PropertyHistory(parent.balances); + this.sensors = new PropertyHistory(parent.sensors); + this.brokers = new PropertyHistory(parent.brokers); + this.integrations = new PropertyHistory(parent.integrations); + } + this.stores = []; + } + + getBlockFromTop(i) { + //block is in our list + if (i < this.blocks.length) { + return this.blocks[this.blocks.length - i - 1]; + } + + //block is in parent, if we have a parent + if (this.parent !== null) { + //shift the index so it's relative to parent + return this.parent.getBlockFromTop(i - this.blocks.length); + } else { + return null; + } + } + + length() { + if (this.parent !== null) { + return this.parent.length() + this.blocks.length; + } else { + return this.blocks.length; + } + } + + getBalanceCopy(publicKey) { + return getPropertyClone(this.balances, publicKey, FALLBACK_BALANCE); + } + + getSensorCopy(key) { + return getPropertyClone(this.sensors, key); + } + + getSensorsMap() { + let returning; + if (this.parent !== null) { + returning = this.parent.getSensorsMap(); + } else { + returning = new Map(); + } + + for (const [key, value] of Object.entries(this.sensors.current)) { + returning.set(key, value); + } + + return returning; + } + + getBrokerCopy(key) { + return getPropertyClone(this.brokers, key); + } + + getBrokerKeysSet() { + let returning; + if (this.parent !== null) { + returning = this.parent.getBrokerKeysSet(); + } else { + returning = new Set(); + } + + for (const key of Object.keys(this.brokers.current)) { + returning.add(key); + } + + return returning; + } + + getIntegrationCopy(key) { + return getPropertyClone(this.integrations, key); + } + + createUpdater(block) { + return new Updater(this, block); + } + + undo() { + if (this.blocks.length === 0) { + throw new Error("Cannot undo chain, no blocks"); + } + + this.blocks.pop(); + this.balances.undo(); + this.sensors.undo(); + this.brokers.undo(); + this.integrations.undo(); + this.stores.pop(); + } + + clone() { + const cloned = new Chain(this.parent); + cloned.blocks = [...this.blocks]; + cloned.balances = this.balances.clone(); + cloned.sensors = this.sensors.clone(); + cloned.brokers = this.brokers.clone(); + cloned.integrations = this.integrations.clone(); + cloned.stores = [...this.stores]; + return cloned; + } + + finish() { + if (this.parent === null) { + throw new Error("Finishing Blockchain Metadata with null parent"); + } + + this.parent.blocks.push(...this.blocks); + this.balances.finish(); + this.sensors.finish(); + this.brokers.finish(); + this.integrations.finish(); + this.parent.stores.push(...this.stores); + + this.parent = null; + } +} + function addRDF(store, metadata) { for (const triple of metadata) { store.addQuad(DataFactory.quad( @@ -19,24 +355,7 @@ function addRDF(store, metadata) { } } -function getBalanceCopyGeneric(publicKey, maps) { - for (const map of maps) { - if (map.hasOwnProperty(publicKey)) { - const found = map[publicKey]; - return { - balance: found.balance, - counter: found.counter - }; - } - } - - return { - balance: 0, - counter: 0 - }; -} - -function verifyPayment(changedBalances, prevBalances, reward, payment) { +function stepPayment(updater, reward, payment) { const verifyRes = Payment.verify(payment); if (!verifyRes.result) { return { @@ -45,7 +364,7 @@ function verifyPayment(changedBalances, prevBalances, reward, payment) { }; } - const inputBalance = getBalanceCopyGeneric(payment.input, [changedBalances, prevBalances]); + const inputBalance = updater.getBalanceCopy(payment.input); if (payment.counter <= inputBalance.counter) { return { @@ -55,7 +374,7 @@ function verifyPayment(changedBalances, prevBalances, reward, payment) { } inputBalance.counter = payment.counter; - //first loop is to check it can be payed, second loop does the paying + //first loop is to check it can be payed, and spends, second loop does the paying if (inputBalance.balance < payment.rewardAmount) { return { result: false, @@ -73,23 +392,24 @@ function verifyPayment(changedBalances, prevBalances, reward, payment) { } inputBalance.balance -= output.amount; } - changedBalances[payment.input] = inputBalance; + + updater.setBalance(payment.input, inputBalance); for (const output of payment.outputs) { - const outputBalance = getBalanceCopyGeneric(output.publicKey, [changedBalances, prevBalances]); + const outputBalance = updater.getBalanceCopy(output.publicKey); outputBalance.balance += output.amount; - changedBalances[output.publicKey] = outputBalance; + updater.setBalance(output.publicKey, outputBalance); } - const rewardBalance = getBalanceCopyGeneric(reward, [changedBalances, prevBalances]); + const rewardBalance = updater.getBalanceCopy(reward); rewardBalance.balance += payment.rewardAmount; - changedBalances[reward] = rewardBalance; + updater.setBalance(rewardBalance); return { result: true }; } -function verifyIntegration(changedBalances, prevBalances, reward, integration) { +function stepIntegration(updater, reward, integration) { const verifyRes = Integration.verify(integration); if (!verifyRes.result) { return { @@ -98,7 +418,7 @@ function verifyIntegration(changedBalances, prevBalances, reward, integration) { }; } - const inputBalance = getBalanceCopyGeneric(integration.input, [changedBalances, prevBalances]); + const inputBalance = updater.getBalanceCopy(integration.input); if (integration.counter <= inputBalance.counter) { return { @@ -108,7 +428,7 @@ function verifyIntegration(changedBalances, prevBalances, reward, integration) { } inputBalance.counter = integration.counter; - //first loop is to check it can be payed, second loop does the paying + //first loop is to check it can be payed, and spends, second loop does the paying if (inputBalance.balance < integration.rewardAmount) { return { result: false, @@ -118,6 +438,20 @@ function verifyIntegration(changedBalances, prevBalances, reward, integration) { inputBalance.balance -= integration.rewardAmount; for (const output of integration.outputs) { + const foundSensor = updater.getSensorCopy(output.sensor); + + if (foundSensor === null) { + return { + result: false, + reason: `Integration references non-existant sensor: ${output.sensor}` + }; + } + if (foundSensor.counter !== output.counter) { + return { + result: false, + reason: "Integration references non-current version of sensor" + }; + } if (inputBalance.balance < output.amount) { return { result: false, @@ -126,23 +460,101 @@ function verifyIntegration(changedBalances, prevBalances, reward, integration) { } inputBalance.balance -= output.amount; } - changedBalances[integration.input] = inputBalance; + updater.setBalance(integration.input, inputBalance); - for (const output of integration.outputs) { - const outputBalance = getBalanceCopyGeneric(output.publicKey, [changedBalances, prevBalances]); - outputBalance.balance += output.amount; - changedBalances[output.publicKey] = outputBalance; - } - const rewardBalance = getBalanceCopyGeneric(reward, [changedBalances, prevBalances]); + const rewardBalance = updater.getBalanceCopy(reward); rewardBalance.balance += integration.rewardAmount; - changedBalances[reward] = rewardBalance; + updater.setBalance(reward, rewardBalance); + + const integrationCopy = Object.assign({}, integration); + const brokers = updater.getBrokerKeys(); + + const witnesses = Integration.chooseWitnesses(integration, brokers); + + integrationCopy.witnesses = {}; + integrationCopy.compensationCount = 0; + + for (const witness of witnesses) { + integrationCopy.witnesses[witness] = false; + } + + updater.setIntegration(makeIntegrationKey(integration.input, integration.counter), integrationCopy); return { result: true }; } -function verifySensorRegistration(changedBalances, prevBalances, reward, sensorRegistration, brokers) { +function stepCompensation(updater, reward, compensation) { + const verifyRes = Compensation.verify(compensation); + + if (!verifyRes.result) { + return { + result: false, + reason: "Couldn't verify a compensation: " + verifyRes.reason + }; + } + + const integrationKey = makeIntegrationKey(compensation.integration.input, compensation.integration.counter); + + const foundIntegration = updater.getIntegrationCopy(integrationKey); + + if (foundIntegration === null) { + return { + result: false, + reason: `Couldn't find integration '${integrationKey}' referenced by compensation` + }; + } + + const foundBroker = updater.getBrokerCopy(compensation.brokerName); + + if (foundBroker === null) { + return { + result: false, + reason: `Couldn't find broker '${compensation.brokerName}' referenced by compensation` + }; + } + + if (foundBroker.input !== compensation.input) { + return { + result: false, + reason: "Broker's owner doesn't match compensation's input" + }; + } + + if (!(compensation.brokerName in foundIntegration.witnesses)) { + return { + result: false, + reason: "Broker that is compensating isn't a witness for the integration" + }; + } + + if (foundIntegration.witnesses[compensation.brokerName]) { + return { + result: false, + reason: "Broker that is compensating has already compensated" + }; + } + + foundIntegration.witnesses[compensation.brokerName] = true; + ++foundIntegration.compensationCount; + + if (foundIntegration.compensationCount === Math.ceil(foundIntegration.witnessCount / 2)) { + const integrateeBalance = updater.getBalanceCopy(foundIntegration.input); + for (const output of foundIntegration.outputs) { + integrateeBalance.balance += output.amount; + } + updater.setBalance(foundIntegration.input, integrateeBalance); + } + + updater.setIntegration(integrationKey, foundIntegration); + + return { + result: true + }; +} + +function stepSensorRegistration(updater, reward, sensorRegistration) { const verifyRes = SensorRegistration.verify(sensorRegistration); if (!verifyRes.result) { return { @@ -156,20 +568,20 @@ function verifySensorRegistration(changedBalances, prevBalances, reward, sensorR if (!extInfo.result) { return { result: false, - reason: "Couldn't get sensor registration ext information: " + extMetadata.reason + reason: "Couldn't get sensor registration ext information: " + extInfo.reason }; } - if (!(extInfo.metadata.integrationBroker in brokers)) { - console.log(brokers); - console.log(extInfo.metadata.integrationBroker); + const foundBroker = updater.getBrokerCopy(extInfo.metadata.integrationBroker); + + if (foundBroker === null) { return { result: false, reason: "Couldn't find sensor registration's nominated broker in the broker list" }; } - const inputBalance = getBalanceCopyGeneric(sensorRegistration.input, [changedBalances, prevBalances]); + const inputBalance = updater.getBalanceCopy(sensorRegistration.input); if (sensorRegistration.counter <= inputBalance.counter) { return { @@ -187,18 +599,41 @@ function verifySensorRegistration(changedBalances, prevBalances, reward, sensorR } inputBalance.balance -= sensorRegistration.rewardAmount; - changedBalances[sensorRegistration.input] = inputBalance; + updater.setBalance(sensorRegistration.input, inputBalance); - const rewardBalance = getBalanceCopyGeneric(reward, [changedBalances, prevBalances]); + const rewardBalance = updater.getBalanceCopy(reward); rewardBalance.balance += sensorRegistration.rewardAmount; - changedBalances[reward] = rewardBalance; + updater.setBalance(reward, rewardBalance); + + addRDF(updater.store, sensorRegistration.metadata); + + const newSensor = extInfo.metadata; + updater.store.addQuad( + DataFactory.namedNode(newSensor.sensorName), + DataFactory.namedNode("http://SSM/transactionCounter"), + DataFactory.literal(sensorRegistration.counter)); + updater.store.addQuad( + DataFactory.namedNode(newSensor.sensorName), + DataFactory.namedNode("http://SSM/OwnedBy"), + DataFactory.namedNode("http://SSM/Wallet/" + sensorRegistration.input)); + updater.store.addQuad( + DataFactory.namedNode(updater.block.hash), + DataFactory.namedNode("http://SSM/Transaction"), + DataFactory.namedNode(newSensor.sensorName)); + updater.store.addQuad( + DataFactory.namedNode(updater.block.hash), + DataFactory.namedNode("http://SSM/SensorRegistration"), + DataFactory.namedNode(newSensor.sensorName)); + + newSensor.counter = sensorRegistration.counter; + updater.setSensor(newSensor.sensorName, newSensor); return { result: true }; } -function verifyBrokerRegistration(changedBalances, prevBalances, reward, brokerRegistration) { +function stepBrokerRegistration(updater, reward, brokerRegistration) { const verifyRes = BrokerRegistration.verify(brokerRegistration); if (!verifyRes.result) { return { @@ -207,7 +642,16 @@ function verifyBrokerRegistration(changedBalances, prevBalances, reward, brokerR }; } - const inputBalance = getBalanceCopyGeneric(brokerRegistration.input, [changedBalances, prevBalances]); + const extInfo = BrokerRegistration.getExtInformation(brokerRegistration); + + if (!extInfo.result) { + return { + result: false, + reason: "Couldn't get broker registration ext information: " + extInfo.reason + }; + } + + const inputBalance = updater.getBalanceCopy(brokerRegistration.input); if (brokerRegistration.counter <= inputBalance.counter) { return { @@ -225,50 +669,78 @@ function verifyBrokerRegistration(changedBalances, prevBalances, reward, brokerR } inputBalance.balance -= brokerRegistration.rewardAmount; - changedBalances[brokerRegistration.input] = inputBalance; + updater.setBalance(brokerRegistration.input, inputBalance); - const rewardBalance = getBalanceCopyGeneric(reward, [changedBalances, prevBalances]); + const rewardBalance = updater.getBalanceCopy(reward); rewardBalance.balance += brokerRegistration.rewardAmount; - changedBalances[reward] = rewardBalance; + updater.setBalance(reward, rewardBalance); + + addRDF(updater.store, brokerRegistration.metadata); + + const newBroker = extInfo.metadata; + newBroker.input = brokerRegistration.input; + updater.store.addQuad( + DataFactory.namedNode(newBroker.brokerName), + DataFactory.namedNode("http://SSM/transactionCounter"), + DataFactory.literal(brokerRegistration.counter)); + updater.store.addQuad( + DataFactory.namedNode(newBroker.brokerName), + DataFactory.namedNode("http://SSM/OwnedBy"), + DataFactory.namedNode("http://SSM/Wallet/" + brokerRegistration.input)); + updater.store.addQuad( + DataFactory.namedNode(updater.block.hash), + DataFactory.namedNode("http://SSM/Transaction"), + DataFactory.namedNode(newBroker.brokerName)); + updater.store.addQuad( + DataFactory.namedNode(updater.block.hash), + DataFactory.namedNode("http://SSM/BrokerRegistration"), + DataFactory.namedNode(newBroker.brokerName)); + + newBroker.counter = brokerRegistration.counter; + updater.setBroker(newBroker.brokerName, newBroker); return { result: true }; } -function verifyTxs(prevBalances, reward, brokers, payments, sensorRegistrations, brokerRegistrations, integrations) { - const changedBalances = {}; +function verifyTxs(updater, reward, payments, sensorRegistrations, brokerRegistrations, integrations, compensations) { + const rewardBalanceCopy = updater.getBalanceCopy(reward); - const rewardBalanceCopy = getBalanceCopyGeneric(reward, [prevBalances]); + rewardBalanceCopy.balance += MINING_REWARD; - changedBalances[reward] = { - balance: rewardBalanceCopy.balance + MINING_REWARD, - counter: rewardBalanceCopy.counter - }; + updater.setBalance(reward, rewardBalanceCopy); for (const payment of payments) { - const res = verifyPayment(changedBalances, prevBalances, reward, payment); + const res = stepPayment(updater, reward, payment); if (!res.result) { return res; } } for (const integration of integrations) { - const res = verifyIntegration(changedBalances, prevBalances, reward, integration); + const res = stepIntegration(updater, reward, integration); + if (!res.result) { + return res; + } + } + + for (const compensation of compensations) { + const res = stepCompensation(updater, reward, compensation); if (!res.result) { return res; } } for (const brokerRegistration of brokerRegistrations) { - const res = verifyBrokerRegistration(changedBalances, prevBalances, reward, brokerRegistration); + const res = stepBrokerRegistration(updater, reward, brokerRegistration); if (!res.result) { return res; } } for (const sensorRegistration of sensorRegistrations) { - const res = verifySensorRegistration(changedBalances, prevBalances, reward, sensorRegistration, brokers); + const res = stepSensorRegistration(updater, reward, sensorRegistration); if (!res.result) { return res; } @@ -276,60 +748,73 @@ function verifyTxs(prevBalances, reward, brokers, payments, sensorRegistrations, return { result: true, - changedBalances: changedBalances }; } -function verifyBlock(prevBalances, prevBlock, verifyingBlock, brokers) { - if (verifyingBlock.lastHash !== prevBlock.hash) { +function verifyBlockHash(prevBlock, block) { + if (block.lastHash !== prevBlock.hash) { return { result: false, reason: "last hash didn't match our last hash" }; } //TODO how to check if new block's timestamp is believable - if (verifyingBlock.difficulty !== Block.adjustDifficulty(prevBlock, verifyingBlock.timestamp)) { + if (block.difficulty !== Block.adjustDifficulty(prevBlock, block.timestamp)) { return { result: false, reason: "difficulty is incorrect" }; } - if (!Block.checkHash(verifyingBlock)) { + if (!Block.checkHash(block)) { return { result: false, reason: "hash is invalid failed" }; } - return verifyTxs(prevBalances, verifyingBlock.reward, brokers, + return { + result: true + }; +} + +function verifyBlock(updater, prevBlock, verifyingBlock) { + const verifyHashRes = verifyBlockHash(prevBlock, verifyingBlock); + + if (!verifyHashRes.result) { + return verifyHashRes; + } + + return verifyTxs(updater, verifyingBlock.reward, Block.getPayments(verifyingBlock), Block.getSensorRegistrations(verifyingBlock), Block.getBrokerRegistrations(verifyingBlock), - Block.getIntegrations(verifyingBlock)); + Block.getIntegrations(verifyingBlock), + Block.getCompensations(verifyingBlock)); } -function verifyChain(chain) { - if (chain.length === 0) { +function verifyBlocks(blocks, start_index, parentChain) { + if (blocks.length === 0) { return { result: false, reason: "zero length" }; } - if (ChainUtil.stableStringify(chain[0]) !== ChainUtil.stableStringify(Block.genesis())) { + if (ChainUtil.stableStringify(blocks[0]) !== ChainUtil.stableStringify(Block.genesis())) { return { result: false, reason: "initial block isn't genesis" }; } - const balances = {}; - const brokers = {}; + const newChain = new Chain(parentChain); - for (let i = 1; i < chain.length; i++) { - const block = chain[i]; - const lastBlock = chain[i - 1]; + for (let i = start_index; i < blocks.length; i++) { + const block = blocks[i]; + const prevBlock = blocks[i - 1]; - const verifyResult = verifyBlock(balances, lastBlock, block, brokers); + const updater = newChain.createUpdater(block); + + const verifyResult = verifyBlock(updater, prevBlock, block); if (verifyResult.result === false) { return { @@ -338,217 +823,155 @@ function verifyChain(chain) { }; } - for (const publicKey in verifyResult.changedBalances) { - balances[publicKey] = verifyResult.changedBalances[publicKey]; - } - - const blockMetadata = getBlockMetadata(chain[i]); - addBlockMetadata(brokers, blockMetadata.brokers); + updater.finish(); } return { result: true, - balances: balances + newChain: newChain }; } -//returns the first index where the two chains differ -function findChainDifference(oldChain, newChain) { - for (let i = 1; i < oldChain.length; ++i) { - if (oldChain[i].hash !== newChain[i].hash) { - return i; +//returns the first index where the two chains differ, checking the new chain for correct hashes +function findBlocksDifference(oldBlocks, newBlocks) { + for (let i = 1; i < oldBlocks.length; ++i) { + const verifyRes = verifyBlockHash(newBlocks[i - 1], newBlocks[i]); + + if (!verifyRes.result) { + return { + result: false, + reason: `Couldn't verify hashes for block ${i}: ${verifyRes.reason}` + }; + } + + if (oldBlocks[i].hash !== newBlocks[i].hash) { + return { + result: true, + difference: i + } } } - return oldChain.length; -} - -function getBlockMetadata(block) { - - const returning = { - sensors: {}, - brokers: {}, - store: new N3.Store() + return { + result: true, + difference: oldBlocks.length }; - - returning.store.addQuad( - DataFactory.namedNode(block.hash), - DataFactory.namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), - DataFactory.namedNode("http://SSM/Block")); - returning.store.addQuad( - DataFactory.namedNode(block.hash), - DataFactory.namedNode("http://SSM/lastBlock"), - DataFactory.namedNode(block.lastHash)); - - for (const tx of Block.getSensorRegistrations(block)) { - addRDF(returning.store, tx.metadata); - - const extData = SensorRegistration.getExtInformation(tx).metadata; - returning.store.addQuad( - DataFactory.namedNode(block.hash), - DataFactory.namedNode("http://SSM/Transaction"), - DataFactory.namedNode(extData.sensorName)); - returning.store.addQuad( - DataFactory.namedNode(block.hash), - DataFactory.namedNode("http://SSM/SensorRegistration"), - DataFactory.namedNode(extData.sensorName)); - - returning.sensors[extData.sensorName] = extData; - } - for (const tx of Block.getBrokerRegistrations(block)) { - addRDF(returning.store, tx.metadata); - - const extData = BrokerRegistration.getExtInformation(tx).metadata; - returning.store.addQuad( - DataFactory.namedNode(block.hash), - DataFactory.namedNode("http://SSM/Transaction"), - DataFactory.namedNode(extData.brokerName)); - returning.store.addQuad( - DataFactory.namedNode(block.hash), - DataFactory.namedNode("http://SSM/SBrokerRegistration"), - DataFactory.namedNode(extData.brokerName)); - - returning.brokers[extData.brokerName] = extData; - } - - return returning; } -//returns the undoing object -function addBlockMetadata(map, metadatas) { - const returning = {}; - - for (const key in metadatas) { - const value = metadatas[key]; - - if (key in map) { - returning[key] = map[key]; - } else { - returning[key] = null; - } - - map[key] = value; +function saveToDisk(blockchain, location) { + try { + fs.writeFileSync( + location, + blockchain.serialize()); + } catch (err) { + console.log(`Couldn't save blockchain to disk: ${err}`); + return false; } + return true; } -function undoBlockMetadata(map, undoer) { - for (const key in undoer) { - const value = undoer[key]; - - if (value === null) { - delete map[key]; - } else { - map[key] = value; - } +function onChange(blockchain, newBlocks, oldBlocks, difference) { + if (blockchain.persisting !== null) { + saveToDisk(blockchain, blockchain.persisting); + } + for (const listener of blockchain.listeners) { + listener(newBlocks, oldBlocks, difference); } } class Blockchain { constructor() { - this.chain = [Block.genesis()]; - this.balances = {}; - this.stores = []; - this.sensors = {}; - this.sensorUndos = []; - this.brokers = {}; - this.brokerUndos = []; + this.chain = new Chain(); + this.listeners = []; + this.persisting = null; } getBalanceCopy(publicKey) { - return getBalanceCopyGeneric(publicKey, [this.balances]); + return this.chain.getBalanceCopy(publicKey); } getSensorInfo(sensorName) { - if (sensorName in this.sensors) { - return this.sensors[sensorName]; - } else { - return null; + return this.chain.getSensorCopy(sensorName); + } + + getSensors() { + const sensorsMap = this.chain.getSensorsMap(); + + const returning = {}; + + for (const [key, value] of sensorsMap) { + returning[key] = value; } + + return returning; } getBrokerInfo(brokerName) { - if (brokerName in this.brokers) { - return this.brokers[brokerName]; - } else { - return null; - } + return this.chain.getBrokerCopy(brokerName); + } + + blocks() { + return this.chain.blocks; } lastBlock() { - return this.chain[this.chain.length - 1]; + return this.chain.getBlockFromTop(0); } serialize() { - return JSON.stringify(this.chain); + return JSON.stringify(this.chain.blocks); } static deserialize(serialized) { return JSON.parse(serialized); } - saveToDisk(location) { - try { - fs.writeFileSync( - location, - this.serialize()); - } catch (err) { - console.log(`Couldn't save blockchain to disk: ${err}`); - return false; - } - return true; - } - static loadFromDisk(location) { //possible race if deleted after check, but we live with it I guess + + const returning = new Blockchain(); + returning.persisting = location; + if (fs.existsSync(location)) { const rawPersistedChain = fs.readFileSync(location, 'utf8'); const deserialized = Blockchain.deserialize(rawPersistedChain); - const returning = new Blockchain(); const replaceResult = returning.replaceChain(deserialized); if (!replaceResult.result) { - console.log(`Couldn't deserialize chain at '${location}', starting from genesis`); + console.log(`Couldn't deserialize chain at '${location}', starting from genesis: ${replaceResult.reason}`); } - return returning; } else { console.log("Didn't find a persisted chain, starting from genesis"); - return new Blockchain(); } + + return returning; } //adds an existing block to the blockchain, returns false if the block can't be added, true if it was added addBlock(newBlock) { - const verifyResult = verifyBlock(this.balances, this.lastBlock(), newBlock, this.brokers); + + const updater = this.chain.createUpdater(newBlock); + + const verifyResult = verifyBlock(updater, this.lastBlock(), newBlock); if (!verifyResult.result) { console.log(`Couldn't add block: ${verifyResult.reason}`); return false; } - //all seems to be good, persist - this.chain.push(newBlock); + updater.finish(); - for (const publicKey in verifyResult.changedBalances) { - this.balances[publicKey] = verifyResult.changedBalances[publicKey]; - } - - const metadata = getBlockMetadata(newBlock); - - this.stores.push(metadata.store); - this.sensorUndos.push(addBlockMetadata(this.sensors, metadata.sensors)); - this.brokerUndos.push(addBlockMetadata(this.brokers, metadata.brokers)); - - //console.log("Added new block"); - //console.log(newBlock); + onChange(this, this.blocks(), this.blocks().slice(0,-1), this.blocks().length - 1); return true; } wouldBeValidBlock(rewardee, payments, sensorRegistrations, brokerRegistrations, integrations) { - return verifyTxs(this.balances, rewardee, this.brokers, payments, sensorRegistrations, brokerRegistrations, integrations).result; + const updater = this.chain.createUpdater(null); + return verifyTxs(updater, rewardee, payments, sensorRegistrations, brokerRegistrations, integrations).result; } - static isValidChain(chain) { - const res = verifyChain(chain); + static isValidChain(blocks) { + const res = verifyBlocks(blocks, 1, new Chain()); return res.result; } @@ -556,13 +979,28 @@ class Blockchain { //return result: false on fail, result: true on success //TODO: faster verification of the new chain by only verifying from divergence, would require saving some historical balance state replaceChain(newChain) { - if (newChain.length <= this.chain.length) { + if (newChain.length <= this.chain.length()) { return { result: false, reason: "Received chain is not longer than the current chain." }; } - const verifyResult = verifyChain(newChain); + + //find where they differ + const chainDifferenceRes = findBlocksDifference(this.chain.blocks, newChain); + + if (!chainDifferenceRes.result) { + return chainDifferenceRes; + } + + const baseChain = this.chain.clone(); + const baseChainLength = baseChain.length(); + + for (let i = baseChainLength - 1; i >= chainDifferenceRes.difference; i--) { + baseChain.undo(); + } + + const verifyResult = verifyBlocks(newChain, chainDifferenceRes.difference, baseChain); if (!verifyResult.result) { return { result: false, @@ -573,36 +1011,20 @@ class Blockchain { //Replacing blockchain with the new chain const oldChain = this.chain; - this.chain = newChain; + this.chain = baseChain; + verifyResult.newChain.finish(); - //find where they differ - const chainDifference = findChainDifference(oldChain, newChain); - - //fix metadata - for (let i = oldChain.length - 1; i >= chainDifference; i--) { - this.stores.pop(); - undoBlockMetadata(this.sensors, this.sensorUndos[i]); - this.sensorUndos.pop(); - undoBlockMetadata(this.brokers, this.brokerUndos[i]); - this.brokerUndos.pop(); - } - for (let i = chainDifference; i < newChain.length; ++i) { - const metadata = getBlockMetadata(newChain[i]); - - this.stores.push(metadata.store); - this.sensorUndos.push(addBlockMetadata(this.sensors, metadata.sensors)); - this.brokerUndos.push(addBlockMetadata(this.brokers, metadata.brokers)); - } - - //fix balance - this.balances = verifyResult.balances; + onChange(this, this.blocks(), oldChain, chainDifferenceRes.difference); return { result: true, - chainDifference: chainDifference, - oldChain: oldChain + chainDifference: chainDifferenceRes.difference, }; } + + addListener(listener) { + this.listeners.push(listener); + } } module.exports = Blockchain; \ No newline at end of file diff --git a/blockchain/broker-registration.js b/blockchain/broker-registration.js index 8e5b4a4..6b6dd3b 100644 --- a/blockchain/broker-registration.js +++ b/blockchain/broker-registration.js @@ -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; } } diff --git a/blockchain/compensation.js b/blockchain/compensation.js new file mode 100644 index 0000000..f994e6e --- /dev/null +++ b/blockchain/compensation.js @@ -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; \ No newline at end of file diff --git a/blockchain/integration.js b/blockchain/integration.js index dcc8191..483aed2 100644 --- a/blockchain/integration.js +++ b/blockchain/integration.js @@ -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; \ No newline at end of file diff --git a/blockchain/integration.test.js b/blockchain/integration.test.js new file mode 100644 index 0000000..cbece6e --- /dev/null +++ b/blockchain/integration.test.js @@ -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)); + }); +}); \ No newline at end of file diff --git a/blockchain/payment.js b/blockchain/payment.js index 471697b..9bc4b33 100644 --- a/blockchain/payment.js +++ b/blockchain/payment.js @@ -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 = { diff --git a/blockchain/sensor-registration.js b/blockchain/sensor-registration.js index d190e6b..3b2aeab 100644 --- a/blockchain/sensor-registration.js +++ b/blockchain/sensor-registration.js @@ -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; } } diff --git a/constants.js b/constants.js index 938ca03..d0e7798 100644 --- a/constants.js +++ b/constants.js @@ -2,6 +2,9 @@ const DIFFICULTY = 3; const MINE_RATE = 3000; const MINING_REWARD = 50; +const DEFAULT_UI_HTML = "./ui/wallet-ui.html"; +const DEFAULT_UI_JS = "./ui/wallet-logic.js"; + const DEFAULT_PORT_MINER_BASE = 3000; const DEFAULT_PORT_MINER_API = DEFAULT_PORT_MINER_BASE + 1; const DEFAULT_PORT_MINER_CHAIN = DEFAULT_PORT_MINER_BASE + 2; @@ -28,6 +31,9 @@ module.exports = { MINE_RATE, MINING_REWARD, + DEFAULT_UI_HTML, + DEFAULT_UI_JS, + DEFAULT_PORT_MINER_API, DEFAULT_PORT_MINER_CHAIN, DEFAULT_PORT_MINER_TX_SHARE, diff --git a/miner/miner-app.js b/miner/miner-app.js index 6fcb89a..501aa11 100644 --- a/miner/miner-app.js +++ b/miner/miner-app.js @@ -31,8 +31,8 @@ const express = require('express'); const bodyParser = require('body-parser'); const P2pServer = require('../p2p-server'); +const BlockchainProp = require('../network/blockchain-prop'); const QueryEngine = require('@comunica/query-sparql-rdfjs').QueryEngine; - const Blockchain = require('../blockchain/blockchain'); const Miner = require('./miner'); 'use strict';/* "use strict" is to indicate that the code should be executed in "strict mode". @@ -73,17 +73,9 @@ const chainServerPeers = config.get({ key: "miner-chain-server-peers", default: [] }); -const txShareServerPort = config.get({ - key: "miner-tx-share-server-port", - default: DEFAULT_PORT_MINER_TX_SHARE -}); -const txShareServerPeers = config.get({ - key: "miner-tx-share-server-peers", - default: [] -}); -const txRecvServerPort = config.get({ - key: "miner-tx-recv-port", - default: DEFAULT_PORT_MINER_TX_RECV +const minerPublicAddress = config.get({ + key: "miner-public-address", + default: "-" }); const apiPort = config.get({ key: "miner-api-port", @@ -92,42 +84,10 @@ const apiPort = config.get({ const blockchain = Blockchain.loadFromDisk(blockchainLocation); -function onMined(block) { - if (!blockchain.addBlock(block)) { - //invalid block, return - return; - } +const chainServer = new BlockchainProp("Chain-server", true, blockchain); +const miner = new Miner(blockchain, minerPublicKey); - miner.onNewBlock(block); - blockchain.saveToDisk(blockchainLocation); - chainServer.broadcast(blockchain.serialize()); -} - -function onChainServerConnect(socket) { - console.log("onChainServerConnect"); - P2pServer.send(socket, blockchain.serialize()); -} - -function onChainServerRecv(data) { - const replaceResult = blockchain.replaceChain(data); - if (!replaceResult.result) { - //failed to replace - return; - } - - for (let i = replaceResult.chainDifference; i < blockchain.chain.length; i++) { - miner.onNewBlock(blockchain.chain[i]); - } - - blockchain.saveToDisk(blockchainLocation); -} - -const chainServer = new P2pServer("Chain-server"); -const txShareServer = new P2pServer("Tx-share-server"); -const txRecvServer = new P2pServer("Tx-share-server"); -const miner = new Miner(blockchain, minerPublicKey, onMined); - -chainServer.start(chainServerPort, chainServerPeers, onChainServerConnect, onChainServerRecv); +chainServer.start(chainServerPort, minerPublicAddress, chainServerPeers); const app = express(); const myEngine = new QueryEngine(); diff --git a/miner/miner.js b/miner/miner.js index df4bd66..b834c3b 100644 --- a/miner/miner.js +++ b/miner/miner.js @@ -4,6 +4,7 @@ const Payment = require('../blockchain/payment'); const Integration = require('../blockchain/integration'); const SensorRegistration = require('../blockchain/sensor-registration'); const BrokerRegistration = require('../blockchain/broker-registration'); +const Compensation = require('../blockchain/compensation'); const ITERATIONS = 1; @@ -27,6 +28,7 @@ function mine(miner) { miner.txs.sensorRegistrations.mining, miner.txs.brokerRegistrations.mining, miner.txs.integrations.mining, + miner.txs.compensations.mining, miner.nonce, difficulty); @@ -34,7 +36,7 @@ function mine(miner) { //success const endTime = process.hrtime.bigint(); console.log(`Mined a block of difficulty ${difficulty} in ${Number(endTime - miner.minedStartTime) / 1000000}ms`); - miner.onMined(new Block( + miner.blockchain.addBlock(new Block( timestamp, miner.lastBlock.hash, hash, @@ -43,6 +45,7 @@ function mine(miner) { miner.txs.sensorRegistrations.mining, miner.txs.brokerRegistrations.mining, miner.txs.integrations.mining, + miner.txs.compensations.mining, miner.nonce, difficulty)); miner.state = STATE_INTERRUPTED; @@ -69,8 +72,7 @@ function startMine(miner) { miner.txs.integrations.mining = [...miner.txs.integrations.pool]; miner.txs.sensorRegistrations.mining = [...miner.txs.sensorRegistrations.pool]; miner.txs.brokerRegistrations.mining = [...miner.txs.brokerRegistrations.pool]; - - miner.lastBlock = miner.blockchain.chain[miner.blockchain.chain.length - 1]; + miner.txs.compensations.mining = [...miner.txs.compensations.pool]; miner.nonce = 0; miner.state = STATE_RUNNING; @@ -89,23 +91,24 @@ function clearFromBlock(miner, txs, blockTxs) { if (foundIndex !== -1) { txs.pool.splice(foundIndex, 1); } - - if (txs.mining.some(findTx(tx))) { - miner.state = STATE_INTERRUPTED; - } } } class Miner { - constructor(blockchain, reward, onMined) { - this.blockchain = blockchain; - this.onMined = onMined; + constructor(blockchain, reward) { + this.lastBlock = blockchain.lastBlock(); this.state = STATE_INTERRUPTED; - this.lastBlock = null; this.reward = reward; this.minedStartTime = null; + this.blockchain = blockchain; + blockchain.addListener((newBlocks, oldBlocks, difference) => { + for (var i = difference; i < newBlocks.length; i++) { + this.onNewBlock(newBlocks[i]); + } + }); + this.txs = { payments: { pool: [], @@ -122,6 +125,10 @@ class Miner { brokerRegistrations: { pool: [], mining: [] + }, + compensations: { + pool: [], + mining: [] } }; @@ -142,6 +149,7 @@ class Miner { case Integration: txs = this.txs.integrations; break; case SensorRegistration: txs = this.txs.sensorRegistrations; break; case BrokerRegistration: txs = this.txs.brokerRegistrations; break; + case Compensation: txs = this.txs.compensations; break; default: throw new Error(`unknown tx type: ${tx.type.name()}`); } @@ -162,6 +170,11 @@ class Miner { clearFromBlock(this, this.txs.integrations, Block.getIntegrations(block)); clearFromBlock(this, this.txs.sensorRegistrations, Block.getSensorRegistrations(block)); clearFromBlock(this, this.txs.brokerRegistrations, Block.getBrokerRegistrations(block)); + clearFromBlock(this, this.txs.compensations, Block.getCompensations(block)); + + this.state = STATE_INTERRUPTED; + + this.lastBlock = block; } } diff --git a/network/blockchain-prop.js b/network/blockchain-prop.js new file mode 100644 index 0000000..3a5765b --- /dev/null +++ b/network/blockchain-prop.js @@ -0,0 +1,283 @@ +const Websocket = require('ws'); +const Assert = require('assert'); +const ChainUtil = require('../chain-util'); +const Block = require('../blockchain/block'); +const Blockchain = require('../blockchain/blockchain'); + +const STATE_INIT = 0; +const STATE_CONNECTING = 1; +const STATE_RUNNING = 2; + +const PEER_OK = 0; +const PEER_DEAD = 1; + +const chainValidation = { + start: ChainUtil.createValidateIsIntegerWithMin(0), + blocks: ChainUtil.createValidateArray(Block.validateIsBlock) +}; + +class Connection { + constructor(parent) { + this.parent = parent; + this.address = null; + this.socket = null; + this.state = STATE_INIT; + + this.prev = null; + this.next = null; + + this.blockIndex = null; + + this.queue = null; + this.queueTimer = null; + + this.sub = { + txs: false + }; + + this.logName = `${parent.logName}:${parent.connectionCounter}`; + parent.connectionCounter++; + } + + accepted(socket) { + console.log(`${this.logName} accepted`); + this.socket = socket; + this.state = STATE_RUNNING; + + this.socket.on("error", () => { + this.onError(); + }); + + this.socket.on("open", () => { + this.onConnection(); + }); + + this.socket.on("message", (data) => { + this.onMessage(data); + }); + + this.onConnection(); + } + + connect(address) { + console.log(`${this.logName} connecting`); + this.address = address; + this.state = STATE_CONNECTING; + + this.reconnectWait = 1; + this.socket = new Websocket(this.address); + + this.socket.on("error", () => { + this.onError(); + }); + + this.socket.on("open", () => { + this.onConnection(); + }); + + this.socket.on("message", (data) => { + this.onMessage(data); + }); + } + + retryDead(address) { + + } + + onError() { + switch (this.state) { + case STATE_CONNECTING: + //this.reconnectWait seconds + random [0,1000] ms + setTimeout(() => this.socket = new Websocket(this.address), + 1000 * this.reconnectWait + Math.floor(Math.random() * 1000)); + this.reconnectWait *= 2; + if (this.reconnectWait > 64) { + this.reconnectWait = 64; + } + break; + case STATE_RUNNING: + this.socket.close(); + this.next.prev = this.prev; + this.prev.next = this.next; + this.next = null; + this.prev = null; + if (this.address !== null) { + this.state = STATE_CONNECTING; + this.reconnectWait = 1; + this.socket = new Websocket(this.address); + } else { + //do nothing? + } + break; + } + } + + onConnection() { + this.state = STATE_RUNNING; + + this.prev = this.parent.connected; + this.next = this.parent.connected.next; + this.next.prev = this; + this.prev.next = this; + + const sending = { + sub: { + txs: this.parent.subTxs + }, + address: this.parent.myAddress + }; + + const blocks = this.parent.blockchain.blocks(); + + if (blocks.length > 1) { + sending.chain = { + blocks: blocks.slice(1), + start: 1 + } + } + + this.socket.send(JSON.stringify(sending)); + + this.blockIndex = blocks.length; + } + + onQueueTimer() { + this.queueTimer = null; + if (this.state !== STATE_RUNNING) { + return; + } + + this.checkSend(); + + // we don't retimer as we wait for external to send + } + + onMessage(event) { + var recved = null; + try { + recved = JSON.parse(event); + } catch (ex) { + console.log(`Bad message on ${this.logName}, not a json object`); + this.onError(); + return; + } + + if ("chain" in recved) { + const validationRes = ChainUtil.validateObject(recved.chain, chainValidation); + if (!validationRes.result) { + console.log(`${this.logName} couldn't validate chain message: ${validationRes.reason}`); + this.onError(); + return; + } + + console.log(`${this.logName} recved chain with start: ${recved.chain.start}`); + + var newBlocks = this.parent.blockchain.blocks().slice(0, recved.chain.start + 1); + newBlocks = newBlocks.concat(recved.chain.blocks); + + this.parent.updatingConnection = this; + this.parent.blockchain.replaceChain(newBlocks); + this.parent.updatingConnection = null; + } + } + + sendChain(oldBlocks, blocks) { + if (this.queue === null) { + this.queue = {}; + } + + var startIndex = this.blockIndex - 1; + + while (oldBlocks[startIndex].hash !== blocks[startIndex].hash) { + startIndex--; + } + + this.queue.chain = { + blocks: blocks.slice(startIndex + 1), + start: startIndex + 1 + }; + + this.checkSend(); + } + + checkSend() { + if (this.queue === null) { + return; + } + + if (this.socket.bufferedAmount === 0) { + this.socket.send(JSON.stringify(this.queue)); + + if ("chain" in this.queue) { + this.blockIndex = this.queue.chain.start + this.queue.chain.blocks.length; + } + + this.queue = null; + } else if (this.queueTimer === null) { + this.queueTimer = setTimeout(this.onQueueTimer, 1000); + } + } +} + +function updateBlocksImpl(server, newBlocks, oldBlocks) { + if (server.updatingConnection !== null) { + server.updatingConnection.blockIndex = blocks.length; + } + + for (var connection = server.connected.next; connection !== server.connected; connection = connection.next) { + if (connection === server.updatingConnection) { + continue; + } + connection.sendChain(oldBlocks, newBlocks); + } +} + +//this acts as a publisher, and subscriber +class PropServer { + constructor(logName, subTxs, blockchain) { + this.logName = logName; + this.peerState = new Map(); + this.connected = { + next: null, + prev: null + }; + this.connected.next = this.connected; + this.connected.prev = this.connected; + this.blockchain = blockchain; + this.blockchain.addListener((newBlocks, oldBlocks, difference) => { + updateBlocksImpl(this, newBlocks, oldBlocks); + }); + this.port = null; + this.myAddress = null; + this.server = null; + this.connectionCounter = 0; + this.subTxs = subTxs; + this.updatingConnection = null; + } + + start(port, myAddress, peers) { + if (this.port !== null) { + console.log(`Couldn't start BlockchainPub '${this.logName}', already started`); + return; + } + + this.port = port; + this.myAddress = myAddress; + for (const peer of peers) { + if (!this.peerState.has(peer)) { + this.peerState.set(peer, PEER_OK); + + const connection = new Connection(this); + connection.connect(peer); + } + } + + this.server = new Websocket.Server({ port: port }); + this.server.on('connection', socket => { + const connection = new Connection(this); + connection.accepted(socket); + }); + } +} + +module.exports = PropServer; \ No newline at end of file diff --git a/network/test.js b/network/test.js new file mode 100644 index 0000000..05d0ce9 --- /dev/null +++ b/network/test.js @@ -0,0 +1,15 @@ +const PropServer = require('./blockchain-prop'); +const Block = require('../blockchain/block'); + +const s1 = new PropServer('s1', false); +const s2 = new PropServer('s2', false); +const s3 = new PropServer('s3', false); + +s1.start(9100, 'ws://127.0.0.1:9100', []); +s2.start(9101, 'ws://127.0.0.1:9101', ['ws://127.0.0.1:9100']); +s3.start(9102, 'ws://127.0.0.1:9102', ['ws://127.0.0.1:9101']); + +const blocks = [Block.genesis()]; +blocks.push(Block.debugMine(blocks[blocks.length - 1], 'eh', [], [], [], [], [])); + +s3.updateBlocks(blocks); \ No newline at end of file diff --git a/package.json b/package.json index af58ee3..f218a12 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "mqtt": "^4.1.0", "multer": "^1.3.1", "n3": "^1.16.3", + "seedrandom": "^3.0.5", "uuid": "^9.0.0", "ws": "^8.9.0" } diff --git a/ui/wallet-logic.js b/ui/wallet-logic.js new file mode 100644 index 0000000..21d9132 --- /dev/null +++ b/ui/wallet-logic.js @@ -0,0 +1,346 @@ +function startSenshamartWalletUI() { + const publicKeySpan = document.getElementById("publicKey"); + const coinCountSpan = document.getElementById("coinCount"); + const status = document.getElementById("status"); + + var currentTab = document.getElementById("payTab"); + + const initTab = function (baseName) { + const buttonName = baseName + "Button"; + const button = document.getElementById(buttonName); + if (button === null) { + console.log("Couldn't find: " + buttonName); + return; + } + const tabName = baseName + "Tab"; + const tab = document.getElementById(tabName); + if (tab === null) { + console.log("Couldn't find: " + tabName); + return; + } + tab.style.display = "none"; + + button.onclick = function (_) { + currentTab.style.display = "none"; + tab.style.display = "initial"; + currentTab = tab; + }; + }; + + initTab("pay"); + initTab("query"); + initTab("sensorInfo"); + initTab("brokerInfo"); + initTab("registerSensor"); + initTab("registerBroker"); + initTab("integrate"); + currentTab.style.display = "initial"; + + const refreshInfo = { + balance: { + onNew: [], + onDel: [], + onChange: [], + vals: {} + }, + sensor: { + onNew: [], + onDel: [], + onChange: [], + vals: {} + }, + broker: { + onNew: [], + onDel: [], + onChange: [], + vals: {} + }, + integration: { + onNew: [], + onDel: [], + onChange: [], + vals: {} + } + }; + + let ourPubKey = null; + + //sensorInfo + const sensorFilter = document.getElementById("sensorFilter"); + const inSensorsSelect = {}; + const sensorsSelect = document.getElementById("sensors"); + const refreshButton = document.getElementById("refresh"); + + + let refreshCounter = 0; + let refreshFailed = false; + let loaded = false; + + const statusOK = function (str) { + status.innerHTML = str; + status.style.backgroundColor = 'lightgreen'; + }; + + const statusWorking = function (str) { + status.innerHTML = str; + status.style.backgroundColor = 'yellow'; + }; + + const statusError = function (str) { + status.innerHTML = str; + status.style.backgroundColor = 'red'; + }; + + const refresh = function () { + if (loaded !== true) { + return; + } + if (refreshCounter !== 0) { + status.innerHTML = "Couldn't refresh, already currently refreshing"; + return; + } + + const updateInfo = function (type, newData) { + const oldData = type.vals; + type.vals = newData; + + for (const [key, value] of Object.entries(newData)) { + if (!(key in oldData)) { + for (const handler of type.onNew) { + handler(key, value); + } + } else { + for (const handler of type.onChange) { + handler(key, value); + } + } + } + for (const [key, value] of Object.entries(oldData)) { + if (!(key in newData)) { + for (const handler of type.onDel) { + handler(key); + } + } + } + }; + + const refreshFetch = function (type, path) { + fetch(path).then(function (res) { + return res.json(); + }).then(function (data) { + updateInfo(type, data); + }).catch(function (err) { + console.log(err); + statusError(`Error: ${err}`); + refreshFailed = true; + }).finally(function () { + refreshCounter--; + if (refreshCounter === 0 && refreshFailed === false) { + statusOK("Refresh finished"); + } + }); + }; + + refreshCounter = 2; + refreshFailed = false; + statusWorking("Refreshing"); + + refreshFetch(refreshInfo.sensor, "/Sensors"); + refreshFetch(refreshInfo.balance, "/Balances"); + }; + + refreshButton.onclick = function (_) { + refresh(); + }; + + fetch("/public-key").then(function (res) { + return res.json(); + }).then(function (pubKey) { + ourPubKey = pubKey; + publicKeySpan.innerHTML = pubKey; + loaded = true; + refresh(); + }).catch(function (err) { + console.log(err); + }); + + //our balace header + refreshInfo.balance.onNew.push(function (key, data) { + if (key === ourPubKey) { + coinCountSpan.innerHTML = data.balance; + } + }); + refreshInfo.balance.onChange.push(function (key, data) { + if (key === ourPubKey) { + coinCountSpan.innerHTML = data.balance; + } + }); + + //pay + const payWallets = document.getElementById("payWallets"); + const payInfo = document.getElementById("payInfo"); + + const payAmount = document.getElementById("payAmount"); + const payTo = document.getElementById("payTo"); + const payReward = document.getElementById("payReward"); + const payDo = document.getElementById("payDo"); + + payDo.onclick = function (_) { + if (payTo.value === "") { + statusError("Empty wallet to pay to"); + return; + } + + const payAmountValue = Number.parseInt(payAmount.value); + + if (Number.isNaN(payAmountValue)) { + statusError("Couldn't convert pay amount to a number"); + return; + } + if (payAmountValue <= 0) { + statusError("Trying to pay a non-positive amount"); + return; + } + + const payRewardValue = Number.parseInt(payReward.value); + + if (Number.isNaN(payRewardValue)) { + statusError("Couldn't convert pay reward to a number"); + return; + } + if (payReward.value < 0) { + statusError("Trying to reward a negative amount"); + return; + } + + fetch('/Payment', { + method: 'POST', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + rewardAmount: payRewardValue, + outputs: [{ + publicKey: payTo.value, + amount: payAmountValue + }] + }) + }); + }; + + refreshInfo.balance.onNew.push(function (key, data) { + const newOption = new Option(key,key); + payWallets.appendChild(newOption); + }); + + refreshInfo.balance.onDel.push(function (key) { + const child = payWallets.namedItem(key); + if (child !== null) { + payWallets.removeChild(child); + } + }); + + refreshInfo.balance.onChange.push(function (key, data) { + const child = payWallets.namedItem(key); + if (child === null) { + return; + } + if (child.selected) { + payInfo.innerHTML = data.balance; + } + }); + + payWallets.oninput = function (_) { + if (payWallets.selectedIndex === -1) { + payInfo.innerHTML = ""; + return; + } + + const selectedIndex = payWallets.selectedIndex; + const selectedOption = payWallets.item(selectedIndex); + const selectedBalance = refreshInfo.balance.vals[selectedOption.value]; + + payInfo.innerHTML = selectedBalance.balance; + payTo.value = selectedOption.value; + }; + + //query + + const queryInput = document.getElementById("queryInput"); + const queryGo = document.getElementById("queryGo"); + const queryHead = document.getElementById("queryHead"); + const queryBody = document.getElementById("queryBody"); + + const queryClearTable = function (obj) { + while (obj.rows.length !== 0) { + obj.deleteRow(-1); + } + }; + + queryGo.onclick = function (_) { + const input = queryInput.value; + + queryGo.disabled = true; + + queryClearTable(queryHead); + queryClearTable(queryBody); + + fetch("/sparql", { + method: 'POST', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + query: input + }) + }).then(function (res) { + return res.json(); + }).then(function (entries) { + + const headers = new Map(); + + for (const obj of entries) { + for (const [key, value] of Object.entries(obj)) { + if (!headers.has(key)) { + headers.set(key, headers.size); + } + } + } + + const headerRow = queryHead.insertRow(-1); + const headerCells = []; + for (var i = 0; i < headers.size; ++i) { + const created = document.createElement('th'); + headerRow.appendChild(created); + headerCells.push(created); + } + + for (const [key, value] of headers) { + headerCells[value].innerHTML = key; + } + + for (const obj of entries) { + const dataRow = queryBody.insertRow(); + + const cells = []; + + for (var i = 0; i < headers.size; ++i) { + cells.push(dataRow.insertCell()); + } + + for (const [key, value] of Object.entries(obj)) { + cells[headers.get(key)].innerHTML = value.value; + } + } + queryGo.disabled = false; + }); + }; + + //sensor + const sensorSensors = document.getElementById("sensorSensors"); + const sensorInfo = document.getElementById("sensorInfo"); + + refreshInfo.sensor.onNew.push(function(key, data) { + const newOption = new Option(key, key); + }); +} \ No newline at end of file diff --git a/ui/wallet-ui.html b/ui/wallet-ui.html new file mode 100644 index 0000000..4c7872f --- /dev/null +++ b/ui/wallet-ui.html @@ -0,0 +1,92 @@ + + + + + +
+ Senshamart simple ui +
+
LOADING
+
+ Wallet public key: LOADING +
+
+ Current amount: LOADING +
+
+ + + + + + + +
+
+
+ Pay: +
+
+ To: +
+
+ Reward: +
+ +
Existing Wallets:
+
+ + +
+
+
+
Query:
+
+ +
+
+ +
+
+ + + +
+
+
+
+
+ Sensors: +
+
+ + + + + + +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+ + + + \ No newline at end of file diff --git a/wallet/wallet-app.js b/wallet/wallet-app.js index c7f52bc..fbb267a 100644 --- a/wallet/wallet-app.js +++ b/wallet/wallet-app.js @@ -1,7 +1,9 @@ //WALLET const express = require('express'); const bodyParser = require('body-parser'); -const P2pServer = require('../p2p-server'); +const BlockchainProp = require('../network/blockchain-prop'); + +const fs = require('fs'); const N3 = require('n3'); @@ -13,6 +15,8 @@ const QueryEngine = require('@comunica/query-sparql-rdfjs').QueryEngine; const Blockchain = require('../blockchain/blockchain'); const { + DEFAULT_UI_HTML, + DEFAULT_UI_JS, DEFAULT_PORT_WALLET_API, DEFAULT_PORT_WALLET_CHAIN, DEFAULT_PORT_MINER_CHAIN @@ -41,32 +45,47 @@ const chainServerPort = config.get({ key: "wallet-chain-server-port", default: DEFAULT_PORT_WALLET_CHAIN }); +const chainServerPublicAddress = config.get({ + key: "wallet-chain-server-public-address", + default: "-" +}); const chainServerPeers = config.get({ key: "wallet-chain-server-peers", default: ["ws://127.0.0.1:" + DEFAULT_PORT_MINER_CHAIN] }); +const uiHtmlLocation = config.get({ + key: "wallet-ui-html", + default: DEFAULT_UI_HTML +}); +const uiJsLocation = config.get({ + key: "wallet-ui-js", + default: DEFAULT_UI_JS +}); const blockchain = Blockchain.loadFromDisk(blockchainLocation); -function onChainServerRecv(data) { - const replaceResult = blockchain.replaceChain(Blockchain.deserialize(data)); - if (!replaceResult.result) { - console.log(`Failed to replace chain: ${replaceResult.reason}`); - //failed to replace - return; - } +const chainServer = new BlockchainProp("Wallet-chain-server", false, blockchain); - blockchain.saveToDisk(blockchainLocation); -} - -const chainServer = new P2pServer("Chain-server"); - -chainServer.start(chainServerPort, chainServerPeers, (_) => { }, onChainServerRecv); +chainServer.start(chainServerPort, chainServerPublicAddress, chainServerPeers); const app = express(); app.use(bodyParser.json()); app.listen(apiPort, () => console.log(`Listening on port ${apiPort}`)); +//UI + +app.get('/logic.js', (req, res) => { + res.type('.js').sendFile(uiJsLocation, { + root:"./" + }); +}); + +app.get('/ui.html', (req, res) => { + res.type('.html').sendFile(uiHtmlLocation, { + root:"./" + }); +}); + app.get('/ChainServer/sockets', (req, res) => { res.json(chainServer.sockets); }); @@ -91,20 +110,34 @@ app.get('/Balance', (req, res) => { res.json(balance); }); app.get('/Balances', (req, res) => { - const balances = blockchain.balances; + const balances = blockchain.chain.balances.current; res.json(balances); }); +app.get('/Sensors', (req, res) => { + res.json(blockchain.chain.sensors.current); +}); +app.get('/Brokers', (req, res) => { + res.json(blockchain.chain.sensors.current); +}); +app.get('/Integrations', (req, res) => { + res.json(blockchain.chain.integrations.current); +}); app.post('/Payment', (req, res) => { + console.log(JSON.stringify(req.body)); + const rewardAmount = req.body.rewardAmount; + const outputs = req.body.outputs; + res.json(wallet.createPayment( - req.body.rewardAmount, - req.body.outputs, + rewardAmount, + outputs, blockchain)); }); app.post('/Integration', (req, res) => { res.json(wallet.createIntegration( req.body.rewardAmount, + req.body.witnessCount, req.body.outputs, blockchain)); }); @@ -166,7 +199,7 @@ app.post('/BrokerRegistration', (req, res) => { }); if (quad.predicate.id === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" - && quad.object.id === "SSM/Broker") { + && quad.object.id === "http://SSM/Broker") { brokers.push(quad.subject.id); } return; @@ -265,13 +298,13 @@ app.post('/sparql', (req, res) => { req.body.query, { readOnly: true, - sources: blockchain.stores + sources: blockchain.chain.stores }); bindingsStream.on('data', (binding) => { - result.push(binding); + result.push(binding.entries); }); bindingsStream.on('end', () => { - res.json(JSON.stringify(result)); + res.json(result); }); bindingsStream.on('error', (err) => { console.error(err); diff --git a/wallet/wallet.js b/wallet/wallet.js index 1f26bbf..9401e69 100644 --- a/wallet/wallet.js +++ b/wallet/wallet.js @@ -19,6 +19,9 @@ class Wallet { //TODO: API for multiple outputs //returns Transaction createPayment(rewardAmount, outputs, blockchain) { + console.log(`${outputs}`); + console.log(`${rewardAmount}`); + const balance = blockchain.getBalanceCopy(this.publicKey); if (balance.counter > this.counter) { @@ -49,7 +52,7 @@ class Wallet { //TODO: API for multiple sensors //returns Transaction - createIntegration(rewardAmount, outputs, blockchain) { + createIntegration(rewardAmount, witnessCount, outputs, blockchain) { const balance = blockchain.getBalanceCopy(this.publicKey); if (balance.counter > this.counter) { @@ -69,7 +72,7 @@ class Wallet { const counterToUse = this.counter + 1; this.counter++; - return new Integration(this.keyPair, counterToUse, outputs, rewardAmount); + return new Integration(this.keyPair, counterToUse, outputs, witnessCount, rewardAmount); } createIntegrationAsTransaction(rewardAmount, outputs, blockchain) {