SenShaMart/blockchain/blockchain.js

1185 lines
31 KiB
JavaScript

const Block = require('./block');
const DataFactory = require('n3').DataFactory;
const Payment = require('./payment');
const SensorRegistration = require('./sensor-registration');
const BrokerRegistration = require('./broker-registration');
const Integration = require('./integration');
const Compensation = require('./compensation');
const ChainUtil = require('../util/chain-util');
const RdsStore = require('./rds-store');
const {
MINING_REWARD,
SENSHAMART_URI_REPLACE } = require('../util/constants');
const URIS = require('./uris');
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");
}
for (const undo of this.undos) {
this.backing.undos.push(undo);
}
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);
}
}
}
function namedNode(x) {
return DataFactory.namedNode(x);
}
function literal(x) {
return DataFactory.literal(x);
}
function makeBlockName(block) {
return URIS.OBJECT.BLOCK + '/' + block.hash;
}
function makeSensorTransactionName(sensorRegistration) {
return URIS.OBJECT.SENSOR_REGISTRATION + '/' + SensorRegistration.hashToSign(sensorRegistration);
}
function makeBrokerTransactionName(brokerRegistration) {
return URIS.OBJECT.BROKER_REGISTRATION + '/' + BrokerRegistration.hashToSign(brokerRegistration);
}
function makeWalletName(input) {
return URIS.OBJECT.WALLET + '/' + input;
}
class Updater {
constructor(parent, block) {
this.parent = parent;
this.block = block;
this.balances = {};
this.sensors = {};
this.brokers = {};
this.integrations = {};
this.store = new RdsStore;
this.store.startPush();
if (block !== null) {
this.pushQuad(
namedNode(makeBlockName(this.block)),
namedNode(URIS.PREDICATE.TYPE),
namedNode(URIS.OBJECT.BLOCK));
this.pushQuad(
namedNode(makeBlockName(this.block.hash)),
namedNode(URIS.PREDICATE.LAST_BLOCK),
namedNode(makeBlockName(this.parent.getBlockFromTop(0))));
}
}
pushQuad(subject, predicate, object) {
this.store.push(
DataFactory.quad(subject, predicate, object));
}
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.getBrokerKeysSet();
for (const [key, value] of Object.entries(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.store.pushInto(this.parent.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.store = new RdsStore();
}
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.store.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.store = this.store.clone();
return cloned;
}
finish() {
if (this.parent === null) {
throw new Error("Finishing Blockchain Metadata with null parent");
}
for (const block of this.blocks) {
this.parent.blocks.push(block);
}
this.balances.finish();
this.sensors.finish();
this.brokers.finish();
this.integrations.finish();
this.store.pushInto(this.parent.store);
this.parent = null;
}
}
function uriReplacePrefix(testing, sensorName) {
if (testing.startsWith(SENSHAMART_URI_REPLACE)) {
return sensorName.concat(testing.slice(SENSHAMART_URI_REPLACE.length));
} else {
return testing;
}
}
function addNodeRDF(updater, metadata, sensorName) {
for (const triple of metadata) {
updater.pushQuad(
namedNode(uriReplacePrefix(triple.s, sensorName)),
namedNode(uriReplacePrefix(triple.p, sensorName)),
namedNode(uriReplacePrefix(triple.o, sensorName)));
}
}
function addLiteralRDF(updater, metadata, sensorName) {
for (const triple of metadata) {
updater.pushQuad(
namedNode(uriReplacePrefix(triple.s, sensorName)),
namedNode(uriReplacePrefix(triple.p, sensorName)),
literal(triple.o));
}
}
function stepPayment(updater, reward, payment) {
const verifyRes = Payment.verify(payment);
if (!verifyRes.result) {
return {
result: false,
reason: "couldn't verify a payment: " + verifyRes.reason
};
}
const inputBalance = updater.getBalanceCopy(payment.input);
if (payment.counter <= inputBalance.counter) {
return {
result: false,
reason: "payment has invalid counter"
};
}
inputBalance.counter = payment.counter;
//first loop is to check it can be payed, and spends, second loop does the paying
if (inputBalance.balance < payment.rewardAmount) {
return {
result: false,
reason: "payment rewarding more than they have"
};
}
inputBalance.balance -= payment.rewardAmount;
for (const output of payment.outputs) {
if (inputBalance.balance < output.amount) {
return {
result: false,
reason: "payment spending more than they have"
};
}
inputBalance.balance -= output.amount;
}
updater.setBalance(payment.input, inputBalance);
for (const output of payment.outputs) {
const outputBalance = updater.getBalanceCopy(output.publicKey);
outputBalance.balance += output.amount;
updater.setBalance(output.publicKey, outputBalance);
}
const rewardBalance = updater.getBalanceCopy(reward);
rewardBalance.balance += payment.rewardAmount;
updater.setBalance(rewardBalance);
return {
result: true
};
}
function stepIntegration(updater, reward, integration) {
const verifyRes = Integration.verify(integration);
if (!verifyRes.result) {
return {
result: false,
reason: "couldn't verify a integration: " + verifyRes.reason
};
}
const inputBalance = updater.getBalanceCopy(integration.input);
if (integration.counter <= inputBalance.counter) {
return {
result: false,
reason: "integration has invalid counter"
};
}
inputBalance.counter = integration.counter;
//first loop is to check it can be payed, and spends, second loop does the paying
if (inputBalance.balance < integration.rewardAmount) {
return {
result: false,
reason: "integration rewarding more than they have"
};
}
inputBalance.balance -= integration.rewardAmount;
for (const output of integration.outputs) {
const foundSensor = updater.getSensorCopy(output.sensorName);
if (foundSensor === null) {
return {
result: false,
reason: `Integration references non-existant sensor: ${output.sensor}`
};
}
if (SensorRegistration.hashToSign(foundSensor) !== output.sensorHash) {
return {
result: false,
reason: "Integration references non-current version of sensor"
};
}
const foundBroker = updater.getBrokerCopy(SensorRegistration.getIntegrationBroker(foundSensor));
if (foundBroker === null) {
return {
result: false,
reason: "Internal consitency error, can't find broker referenced by commited sensor registration"
};
}
if (BrokerRegistration.hashToSign(foundBroker) !== output.brokerHash) {
return {
result: false,
reason: "Integration references non-current version of sensor's broker"
};
}
if (inputBalance.balance < output.amount) {
return {
result: false,
reason: "integration spending more than they have"
};
}
inputBalance.balance -= output.amount;
}
updater.setBalance(integration.input, inputBalance);
const rewardBalance = updater.getBalanceCopy(reward);
rewardBalance.balance += integration.rewardAmount;
updater.setBalance(reward, rewardBalance);
const integrationCopy = Object.assign({}, integration);
const brokers = updater.getBrokerPublicKeys();
const witnesses = Integration.chooseWitnesses(integration, brokers);
if (!witnesses.result) {
return {
result: false,
reason: "Couldn't choose witnesses: " + witnesses.reason
};
}
integrationCopy.witnesses = {};
integrationCopy.compensationCount = 0;
for (const witness of witnesses.witnesses) {
integrationCopy.witnesses[witness] = false;
}
updater.setIntegration(makeIntegrationKey(integration.input, integration.counter), integrationCopy);
return {
result: true
};
}
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 {
result: false,
reason: "Couldn't verify a sensor registration: " + verifyRes.reason
};
}
const foundBroker = updater.getBrokerCopy(SensorRegistration.getIntegrationBroker(sensorRegistration));
if (foundBroker === null) {
return {
result: false,
reason: "Couldn't find sensor registration's nominated broker in the broker list"
};
}
const inputBalance = updater.getBalanceCopy(sensorRegistration.input);
if (sensorRegistration.counter <= inputBalance.counter) {
return {
result: false,
reason: "Sensor registration has invalid counter"
};
}
inputBalance.counter = sensorRegistration.counter;
if (inputBalance.balance < sensorRegistration.rewardAmount) {
return {
result: false,
reason: "Sensor registration rewarding more than they have"
};
}
inputBalance.balance -= sensorRegistration.rewardAmount;
updater.setBalance(sensorRegistration.input, inputBalance);
const rewardBalance = updater.getBalanceCopy(reward);
rewardBalance.balance += sensorRegistration.rewardAmount;
updater.setBalance(reward, rewardBalance);
const sensorName = SensorRegistration.getSensorName(sensorRegistration);
const foundExistingSensor = updater.getSensorCopy(sensorName);
if (foundExistingSensor !== null) {
if(foundExistingSensor.input !== sensorRegistration.input) {
return {
result: false,
reason: "A sensor has already been defined with this name"
};
}
}
addNodeRDF(updater, SensorRegistration.getExtraNodeMetadata(sensorRegistration), sensorName);
addLiteralRDF(updater, SensorRegistration.getExtraLiteralMetadata(sensorRegistration), sensorName);
const transactionName = makeSensorTransactionName(sensorRegistration);
if (updater.block !== null) {
updater.pushQuad(
namedNode(makeBlockName(updater.block)),
namedNode(URIS.PREDICATE.CONTAINS_TRANSACTION),
namedNode(transactionName));
updater.pushQuad(
namedNode(makeBlockName(updater.block)),
namedNode(URIS.PREDICATE.CONTAINS_SENSOR_REGISTRATION),
namedNode(transactionName));
updater.pushQuad(
namedNode(transactionName),
namedNode(URIS.PREDICATE.TYPE),
namedNode(URIS.OBJECT.SENSOR_REGISTRATION));
updater.pushQuad(
namedNode(transactionName),
namedNode(URIS.PREDICATE.HAS_COUNTER),
literal(sensorRegistration.counter));
updater.pushQuad(
namedNode(transactionName),
namedNode(URIS.PREDICATE.IS_OWNED_BY),
namedNode(makeWalletName(sensorRegistration.input)));
updater.pushQuad(
namedNode(transactionName),
namedNode(URIS.PREDICATE.DEFINES),
namedNode(sensorName));
updater.pushQuad(
namedNode(transactionName),
namedNode(URIS.PREDICATE.COSTS_PER_MINUTE),
literal(SensorRegistration.getCostPerMinute(sensorRegistration)));
updater.pushQuad(
namedNode(transactionName),
namedNode(URIS.PREDICATE.COSTS_PER_KB),
literal(SensorRegistration.getCostPerKB(sensorRegistration)));
updater.pushQuad(
namedNode(transactionName),
namedNode(URIS.PREDICATE.USES_BROKER),
namedNode(makeBrokerTransactionName(foundBroker)));
}
updater.setSensor(sensorName, sensorRegistration);
return {
result: true
};
}
function stepBrokerRegistration(updater, reward, brokerRegistration) {
const verifyRes = BrokerRegistration.verify(brokerRegistration);
if (!verifyRes.result) {
return {
result: false,
reason: "Couldn't verify a broker registration: " + verifyRes.reason
};
}
const inputBalance = updater.getBalanceCopy(brokerRegistration.input);
if (brokerRegistration.counter <= inputBalance.counter) {
return {
result: false,
reason: "Broker registration has invalid counter"
};
}
inputBalance.counter = brokerRegistration.counter;
if (inputBalance.balance < brokerRegistration.rewardAmount) {
return {
result: false,
reason: "Broker registration rewarding more than they have"
};
}
inputBalance.balance -= brokerRegistration.rewardAmount;
updater.setBalance(brokerRegistration.input, inputBalance);
const rewardBalance = updater.getBalanceCopy(reward);
rewardBalance.balance += brokerRegistration.rewardAmount;
updater.setBalance(reward, rewardBalance);
const brokerName = BrokerRegistration.getBrokerName(brokerRegistration);
const foundExistingBroker = updater.getBrokerCopy(brokerName);
if (foundExistingBroker !== null) {
if(foundExistingBroker.input !== brokerRegistration.input) {
return {
result: false,
reason: "A broker has already been defined with this name"
};
}
}
addNodeRDF(updater, BrokerRegistration.getExtraNodeMetadata(brokerRegistration), brokerName);
addLiteralRDF(updater, BrokerRegistration.getExtraLiteralMetadata(brokerRegistration), brokerName);
const transactionName = makeBrokerTransactionName(brokerRegistration);
if (updater.block !== null) {
updater.pushQuad(
namedNode(makeBlockName(updater.block)),
namedNode(URIS.PREDICATE.CONTAINS_TRANSACTION),
namedNode(transactionName));
updater.pushQuad(
namedNode(makeBlockName(updater.block)),
namedNode(URIS.PREDICATE.CONTAINS_BROKER_REGISTRATION),
namedNode(transactionName));
updater.pushQuad(
namedNode(transactionName),
namedNode(URIS.PREDICATE.TYPE),
namedNode(URIS.OBJECT.BROKER_REGISTRATION));
updater.pushQuad(
namedNode(transactionName),
namedNode(URIS.PREDICATE.HAS_COUNTER),
literal(brokerRegistration.counter));
updater.pushQuad(
namedNode(transactionName),
namedNode(URIS.PREDICATE.IS_OWNED_BY),
namedNode(makeWalletName(brokerRegistration.input)));
updater.pushQuad(
namedNode(transactionName),
namedNode(URIS.PREDICATE.DEFINES),
namedNode(brokerName));
updater.pushQuad(
namedNode(transactionName),
namedNode(URIS.PREDICATE.HAS_ENDPOINT),
literal(BrokerRegistration.getEndpoint(brokerRegistration)));
}
updater.setBroker(BrokerRegistration.getBrokerName(brokerRegistration), brokerRegistration);
return {
result: true
};
}
function verifyTxs(updater, reward, payments, sensorRegistrations, brokerRegistrations, integrations, compensations) {
const rewardBalanceCopy = updater.getBalanceCopy(reward);
rewardBalanceCopy.balance += MINING_REWARD;
updater.setBalance(reward, rewardBalanceCopy);
for (const payment of payments) {
const res = stepPayment(updater, reward, payment);
if (!res.result) {
return res;
}
}
for (const integration of integrations) {
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 = stepBrokerRegistration(updater, reward, brokerRegistration);
if (!res.result) {
return res;
}
}
for (const sensorRegistration of sensorRegistrations) {
const res = stepSensorRegistration(updater, reward, sensorRegistration);
if (!res.result) {
return res;
}
}
return {
result: true,
};
}
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 (block.difficulty !== Block.adjustDifficulty(prevBlock, block.timestamp)) {
return {
result: false,
reason: "difficulty is incorrect"
};
}
if (!Block.checkHash(block)) {
return {
result: false,
reason: "hash is invalid failed"
};
}
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.getCompensations(verifyingBlock));
}
function verifyBlocks(blocks, start_index, parentChain) {
if (blocks.length === 0) {
return {
result: false,
reason: "zero length"
};
}
if (ChainUtil.stableStringify(blocks[0]) !== ChainUtil.stableStringify(Block.genesis())) {
return {
result: false,
reason: "initial block isn't genesis"
};
}
const newChain = new Chain(parentChain);
for (let i = start_index; i < blocks.length; i++) {
const block = blocks[i];
const prevBlock = blocks[i - 1];
const updater = newChain.createUpdater(block);
const verifyResult = verifyBlock(updater, prevBlock, block);
if (verifyResult.result === false) {
return {
result: false,
reason: `Chain is invalid on block ${i}: ${verifyResult.reason}`
};
}
updater.finish();
}
return {
result: true,
newChain: newChain
};
}
//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) {
console.log(`${newBlocks[i - 1].hash}`);
console.log(`${newBlocks[i].lastHash}`);
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 {
result: true,
difference: oldBlocks.length
};
}
function onChange(blockchain, newBlocks, oldBlocks, difference) {
if (blockchain.persisting !== null) {
Blockchain.saveToDisk(blockchain, blockchain.persisting);
}
for (const listener of blockchain.listeners) {
listener(newBlocks, oldBlocks, difference);
}
}
class Blockchain {
constructor() {
this.chain = new Chain();
this.listeners = [];
this.persisting = null;
}
getBalanceCopy(publicKey) {
return this.chain.getBalanceCopy(publicKey);
}
getSensorInfo(sensorName) {
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) {
return this.chain.getBrokerCopy(brokerName);
}
blocks() {
return this.chain.blocks;
}
lastBlock() {
return this.chain.getBlockFromTop(0);
}
serialize() {
return JSON.stringify(this.chain.blocks);
}
static deserialize(serialized) {
return JSON.parse(serialized);
}
static loadFromDisk(location) {
const fs = require('fs');
//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 replaceResult = returning.replaceChain(deserialized);
if (!replaceResult.result) {
console.log(`Couldn't deserialize chain at '${location}', starting from genesis: ${replaceResult.reason}`);
}
} else {
console.log("Didn't find a persisted chain, starting from genesis");
}
return returning;
}
static saveToDisk(blockchain, location) {
const fs = require('fs');
try {
fs.writeFileSync(
location,
blockchain.serialize());
} catch (err) {
console.log(`Couldn't save blockchain to disk: ${err}`);
return false;
}
return true;
}
static loadFromDiskOverrideFS(location, saveChain, loadChain) {
Blockchain.saveToDisk = (blockchain, location) => {
saveChain(location, blockchain.serialize()); // async call, no result can be returned; must assume success.
return true;
}
Blockchain.loadFromDisk = (location) => {
const returning = new Blockchain();
returning.persisting = location;
const rawPersistedChain = loadChain(location); // sync call, because application has already loaded file,
// this simply retrieves it from memory
// TODO: Could add check for empty string to prevent error, however, result is the same so leaving for now.
const deserialized = Blockchain.deserialize(rawPersistedChain);
const replaceResult = returning.replaceChain(deserialized);
if (!replaceResult.result) {
console.log(`Couldn't deserialize chain at '${location}', starting from genesis: ${replaceResult.reason}`);
}
return returning;
}
return Blockchain.loadFromDisk(location);
}
//adds an existing block to the blockchain, returns false if the block can't be added, true if it was added
addBlock(newBlock) {
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;
}
updater.finish();
onChange(this, this.blocks(), this.blocks().slice(0,-1), this.blocks().length - 1);
return true;
}
wouldBeValidBlock(rewardee, payments, sensorRegistrations, brokerRegistrations, integrations, compensations) {
const updater = this.chain.createUpdater(null);
return verifyTxs(updater, rewardee, payments, sensorRegistrations, brokerRegistrations, integrations, compensations).result;
}
static isValidChain(blocks) {
const res = verifyBlocks(blocks, 1, new Chain());
return res.result;
}
//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()) {
return {
result: false,
reason: "Received chain is not longer than the current chain."
};
}
//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,
reason: `The received chain is not valid: ${verifyResult.reason}`
};
}
//Replacing blockchain with the new chain
const oldChain = this.chain;
this.chain = baseChain;
verifyResult.newChain.finish();
console.log(`new chain of length: ${this.blocks().length}`);
onChange(this, this.blocks(), oldChain.blocks, chainDifferenceRes.difference);
return {
result: true,
chainDifference: chainDifferenceRes.difference,
};
}
addListener(listener) {
this.listeners.push(listener);
}
rdfSource() {
return this.chain.store;
}
}
module.exports = Blockchain;