reorg code, integration publish/subscribe working, still some TODOs
This commit is contained in:
parent
043a95d9ef
commit
1af6d56e2d
35 changed files with 3052 additions and 1401 deletions
106
app/miner.js
106
app/miner.js
|
@ -1,106 +0,0 @@
|
||||||
const Block = require('../blockchain/block');
|
|
||||||
|
|
||||||
const ITERATIONS = 1;
|
|
||||||
|
|
||||||
class Miner {
|
|
||||||
static STATE_RUNNING = 0;
|
|
||||||
static STATE_INTERRUPTED = 1;
|
|
||||||
|
|
||||||
constructor(blockchain, transactionPool, reward, p2pServer) {
|
|
||||||
this.blockchain = blockchain;
|
|
||||||
this.transactionPool = transactionPool;
|
|
||||||
this.p2pServer = p2pServer;
|
|
||||||
this.state = Miner.STATE_INTERRUPTED;
|
|
||||||
this.lastBlock = null;
|
|
||||||
|
|
||||||
this.minedStartTime = null;
|
|
||||||
|
|
||||||
this.mining = {};
|
|
||||||
this.mining.transactions = [];
|
|
||||||
this.mining.reward = reward;
|
|
||||||
this.mining.metadatas = [];
|
|
||||||
|
|
||||||
this.startMine();
|
|
||||||
}
|
|
||||||
|
|
||||||
interrupt() {
|
|
||||||
if (this.state === Miner.STATE_RUNNING) {
|
|
||||||
this.state = Miner.STATE_INTERRUPTED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interruptIfContainsTransaction(transaction) {
|
|
||||||
if (this.state === Miner.STATE_RUNNING && this.mining.metadatas.find(t => t.id === transaction.id)) {
|
|
||||||
this.state = Miner.STATE_INTERRUPTED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
interruptIfContainsMetadata(metadata) {
|
|
||||||
if (this.state === Miner.STATE_RUNNING && this.mining.transactions.find(t => t.id === metadata.id)) {
|
|
||||||
this.state = Miner.STATE_INTERRUPTED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startMine() {
|
|
||||||
//only continue if state is waiting or restarting
|
|
||||||
if (this.state !== Miner.STATE_INTERRUPTED && this.state !== Miner.STATE_RESTARTING) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.minedStartTime = process.hrtime.bigint();
|
|
||||||
|
|
||||||
this.mining.transactions = this.transactionPool.validTransactionsCopy();
|
|
||||||
this.mining.metadatas = this.transactionPool.validMetadatasCopy();
|
|
||||||
|
|
||||||
this.lastBlock = this.blockchain.chain[this.blockchain.chain.length - 1];
|
|
||||||
|
|
||||||
this.nonce = 0;
|
|
||||||
this.state = Miner.STATE_RUNNING;
|
|
||||||
|
|
||||||
this.mine();
|
|
||||||
}
|
|
||||||
|
|
||||||
mine() {
|
|
||||||
if (this.state !== Miner.STATE_RUNNING) {
|
|
||||||
this.state = Miner.STATE_RESTARTING;
|
|
||||||
this.startMine();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const timestamp = Date.now();
|
|
||||||
const difficulty = Block.adjustDifficulty(this.lastBlock, timestamp);
|
|
||||||
|
|
||||||
for (let i = 0; i < ITERATIONS; ++i) {
|
|
||||||
const hash = Block.hash(
|
|
||||||
timestamp,
|
|
||||||
this.lastBlock.hash,
|
|
||||||
this.mining.reward,
|
|
||||||
this.mining.transactions,
|
|
||||||
this.mining.metadatas,
|
|
||||||
this.nonce,
|
|
||||||
difficulty);
|
|
||||||
|
|
||||||
if (hash.substring(0, difficulty) === '0'.repeat(difficulty)) {
|
|
||||||
//success
|
|
||||||
const endTime = process.hrtime.bigint();
|
|
||||||
console.log(`Mined a block of difficulty ${difficulty} in ${Number(endTime - this.minedStartTime) / 1000000}ms`);
|
|
||||||
this.p2pServer.blockMined(new Block(
|
|
||||||
timestamp,
|
|
||||||
this.lastBlock.hash,
|
|
||||||
hash,
|
|
||||||
this.mining.reward,
|
|
||||||
this.mining.transactions,
|
|
||||||
this.mining.metadatas,
|
|
||||||
this.nonce,
|
|
||||||
difficulty));
|
|
||||||
this.state = Miner.STATE_RESTARTING;
|
|
||||||
setImmediate(() => { this.startMine() });
|
|
||||||
} else {
|
|
||||||
//failure
|
|
||||||
this.nonce++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setImmediate(() => { this.mine() });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Miner;
|
|
||||||
|
|
|
@ -1,214 +0,0 @@
|
||||||
const Websocket = require('ws');
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const process = require('process');
|
|
||||||
const Miner = require('./miner');
|
|
||||||
const Transaction = require('../wallet/transaction');
|
|
||||||
const TransactionPool = require('../wallet/transaction-pool');
|
|
||||||
const Metadata = require('../wallet/metadata');
|
|
||||||
const Blockchain = require('../blockchain');
|
|
||||||
|
|
||||||
const P2P_PORT = process.env.P2P_PORT || 5000;
|
|
||||||
const peers = process.env.PEERS ? process.env.PEERS.split(',') : [];
|
|
||||||
const MESSAGE_TYPES = {
|
|
||||||
chain: 'CHAIN',
|
|
||||||
transaction: 'TRANSACTION',
|
|
||||||
clear_transactions: 'CLEAR_TRANSACTIONS',
|
|
||||||
metadata: 'METADATA'
|
|
||||||
};
|
|
||||||
|
|
||||||
class P2pServer {
|
|
||||||
constructor(transactionPool, rewardPublicKey, chainStorageLocation) {
|
|
||||||
this.blockchain = new Blockchain();
|
|
||||||
this.transactionPool = transactionPool;
|
|
||||||
this.sockets = [];
|
|
||||||
this.chainStorageLocation = chainStorageLocation;
|
|
||||||
|
|
||||||
//possible race if deleted after check, but we live with it I guess
|
|
||||||
if (fs.existsSync(this.chainStorageLocation)) {
|
|
||||||
const rawPersistedChain = fs.readFileSync(this.chainStorageLocation, 'utf8');
|
|
||||||
const deserialized = Blockchain.deserialize(rawPersistedChain);
|
|
||||||
if (deserialized === null) {
|
|
||||||
console.log(`Couldn't deserialize chain at '${this.chainStorageLocation}', starting from genesis`);
|
|
||||||
} else {
|
|
||||||
this.blockchain = deserialized;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("Didn't find a persisted chain, starting from genesis");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.miner = new Miner(this.blockchain, this.transactionPool, rewardPublicKey, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
listen() {
|
|
||||||
const server = new Websocket.Server({ port: P2P_PORT });
|
|
||||||
server.on('connection', socket => this.connectSocket(socket));
|
|
||||||
|
|
||||||
this.connectToPeers();
|
|
||||||
|
|
||||||
console.log(`Listening for peer-to-peer connections on: ${P2P_PORT}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
connectToPeers() {
|
|
||||||
peers.forEach(peer => {
|
|
||||||
const socket = new Websocket(peer);
|
|
||||||
|
|
||||||
socket.on('open', () => this.connectSocket(socket));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
connectSocket(socket) {
|
|
||||||
this.sockets.push(socket);
|
|
||||||
console.log('Socket connected');
|
|
||||||
|
|
||||||
this.messageHandler(socket);
|
|
||||||
|
|
||||||
this.sendChain(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
messageHandler(socket) {
|
|
||||||
socket.on('message', message => {
|
|
||||||
const data = JSON.parse(message);
|
|
||||||
switch(data.type) {
|
|
||||||
case MESSAGE_TYPES.chain:
|
|
||||||
this.newChain(data.chain);
|
|
||||||
break;
|
|
||||||
case MESSAGE_TYPES.transaction:
|
|
||||||
this.newTransaction(data.transaction, false);
|
|
||||||
break;
|
|
||||||
case MESSAGE_TYPES.metadata:
|
|
||||||
this.newMetadata(data.metadata, false);
|
|
||||||
break;
|
|
||||||
//case MESSAGE_TYPES.clear_transactions:
|
|
||||||
// this.transactionPool.clear();
|
|
||||||
// break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
newMetadata(metadata, broadcast) {
|
|
||||||
if (!Metadata.verifyMetadata(metadata)) {
|
|
||||||
console.log("Couldn't add metadata to p2pServer, couldn't verify");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this.transactionPool.updateOrAddMetadata(metadata)) {
|
|
||||||
case TransactionPool.Return.add:
|
|
||||||
this.miner.startMine();
|
|
||||||
break;
|
|
||||||
case TransactionPool.Return.update:
|
|
||||||
this.miner.interruptIfContainsMetadata(metadata);
|
|
||||||
break;
|
|
||||||
case TransactionPool.Return.error:
|
|
||||||
console.log("Couldn't add metadata to p2pServer, couldn't updateOrAdd");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (broadcast === undefined || broadcast) {
|
|
||||||
this.broadcastMetadata(metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newTransaction(transaction, broadcast) {
|
|
||||||
if (!Transaction.verify(transaction)) {
|
|
||||||
console.log("Couldn't add transaction to p2pServer, couldn't verify");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this.transactionPool.updateOrAddTransaction(transaction)) {
|
|
||||||
case TransactionPool.Return.add:
|
|
||||||
this.miner.startMine();
|
|
||||||
break;
|
|
||||||
case TransactionPool.Return.update:
|
|
||||||
this.miner.interruptIfContainsTransaction(transaction);
|
|
||||||
break;
|
|
||||||
case TransactionPool.Return.error:
|
|
||||||
console.log("Couldn't add transaction to p2pServer, couldn't updateOrAdd");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (broadcast === undefined || broadcast) {
|
|
||||||
this.broadcastTransaction(transaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
blockMined(block) {
|
|
||||||
if (!this.blockchain.addBlock(block)) {
|
|
||||||
//invalid block, return
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.transactionPool.clearFromBlock(block);
|
|
||||||
this.miner.interrupt();
|
|
||||||
this.persistChain(this.blockchain);
|
|
||||||
this.syncChains();
|
|
||||||
}
|
|
||||||
|
|
||||||
newChain(chain, persist) {
|
|
||||||
const replaceResult = this.blockchain.replaceChain(chain);
|
|
||||||
if (!replaceResult.result) {
|
|
||||||
//failed to replace
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < replaceResult.chainDifference; i++) {
|
|
||||||
this.transactionPool.clearFromBlock(this.blockchain.chain[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.miner.interrupt();
|
|
||||||
|
|
||||||
if (typeof persist === "undefined" || persist) {
|
|
||||||
this.persistChain(this.blockchain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
persistChain(chain) {
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(
|
|
||||||
this.chainStorageLocation,
|
|
||||||
chain.serialize());
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Couldn't persist chain, aborting: ${err}`);
|
|
||||||
process.exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendChain(socket) {
|
|
||||||
socket.send(JSON.stringify({
|
|
||||||
type: MESSAGE_TYPES.chain,
|
|
||||||
chain: this.blockchain.serialize()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
sendTransaction(socket, transaction) {
|
|
||||||
socket.send(JSON.stringify({
|
|
||||||
type: MESSAGE_TYPES.transaction,
|
|
||||||
transaction
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMetadata(socket, metadata) {
|
|
||||||
socket.send(JSON.stringify({
|
|
||||||
type: MESSAGE_TYPES.metadata,
|
|
||||||
metadata
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
syncChains() {
|
|
||||||
this.sockets.forEach(socket => this.sendChain(socket));
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcastTransaction(transaction) {
|
|
||||||
this.sockets.forEach(socket => this.sendTransaction(socket, transaction));
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcastMetadata(metadata) {
|
|
||||||
this.sockets.forEach(socket => this.sendMetadata(socket, metadata));
|
|
||||||
}
|
|
||||||
|
|
||||||
//broadcastClearTransactions() {
|
|
||||||
// this.sockets.forEach(socket => socket.send(JSON.stringify({
|
|
||||||
// type: MESSAGE_TYPES.clear_transactions
|
|
||||||
// })));
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = P2pServer;
|
|
|
@ -1,20 +1,43 @@
|
||||||
const ChainUtil = require('../chain-util');
|
const ChainUtil = require('../chain-util');
|
||||||
const { DIFFICULTY, MINE_RATE } = require('../config');
|
const { DIFFICULTY, MINE_RATE } = require('../constants');
|
||||||
|
|
||||||
function concatIfNotUndefined(concatTo, concatting) {
|
function concatIfNotUndefined(concatTo, prefix, concatting) {
|
||||||
if (typeof concatting !== "undefined") {
|
if (typeof concatting !== "undefined" && concatting.length !== 0) {
|
||||||
concatTo += `${concatting}`;
|
return concatTo + `${prefix}${concatting}`;
|
||||||
|
} else {
|
||||||
|
return concatTo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(block, key) {
|
||||||
|
|
||||||
|
const got = block[key];
|
||||||
|
|
||||||
|
if (typeof got !== "undefined" && got !== null) {
|
||||||
|
return got;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Block {
|
class Block {
|
||||||
constructor(timestamp, lastHash, hash, reward, transactions, metadatas, nonce, difficulty) {
|
constructor(timestamp, lastHash, hash, reward, payments, sensorRegistrations, brokerRegistrations, integrations, nonce, difficulty) {
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.lastHash = lastHash;
|
this.lastHash = lastHash;
|
||||||
this.hash = hash;
|
this.hash = hash;
|
||||||
this.reward = reward;
|
this.reward = reward;
|
||||||
this.transactions = transactions;
|
if (payments !== null && payments.length !== 0) {
|
||||||
this.metadatas = metadatas;
|
this.payments = payments;
|
||||||
|
}
|
||||||
|
if (sensorRegistrations !== null && sensorRegistrations.length !== 0) {
|
||||||
|
this.sensorRegistrations = sensorRegistrations;
|
||||||
|
}
|
||||||
|
if (brokerRegistrations !== null && brokerRegistrations.length !== 0) {
|
||||||
|
this.brokerRegistrations = brokerRegistrations;
|
||||||
|
}
|
||||||
|
if (integrations !== null && integrations.length !== 0) {
|
||||||
|
this.integrations = integrations;
|
||||||
|
}
|
||||||
this.nonce = nonce;
|
this.nonce = nonce;
|
||||||
if (difficulty === undefined) {
|
if (difficulty === undefined) {
|
||||||
this.difficulty = DIFFICULTY;
|
this.difficulty = DIFFICULTY;
|
||||||
|
@ -23,20 +46,20 @@ class Block {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTransactions(block) {
|
static getPayments(block) {
|
||||||
if (typeof block.transactions !== "undefined" && block.transactions !== null) {
|
return getData(block, "payments");
|
||||||
return block.transactions;
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static getMetadatas(block) {
|
static getSensorRegistrations(block) {
|
||||||
if (typeof block.metadatas !== "undefined" && block.metadatas !== null) {
|
return getData(block, "sensorRegistrations");
|
||||||
return block.metadatas;
|
}
|
||||||
} else {
|
|
||||||
return [];
|
static getBrokerRegistrations(block) {
|
||||||
}
|
return getData(block, "brokerRegistrations");
|
||||||
|
}
|
||||||
|
|
||||||
|
static getIntegrations(block) {
|
||||||
|
return getData(block, "integrations");
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
|
@ -52,16 +75,17 @@ class Block {
|
||||||
}
|
}
|
||||||
|
|
||||||
static genesis() {
|
static genesis() {
|
||||||
return new this('Genesis time', '-----', 'f1r57-h45h', null, null, null, 0, DIFFICULTY);
|
return new this('Genesis time', '-----', 'f1r57-h45h', null, null, null, null, null, 0, DIFFICULTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static hash(timestamp, lastHash, reward, transactions, metadatas, nonce, difficulty) {
|
static hash(timestamp, lastHash, reward, payments, sensorRegistrations, brokerRegistrations, integrations, nonce, difficulty) {
|
||||||
//backwards compatible hashing:
|
//backwards compatible hashing:
|
||||||
//if we add a new type of thing to the chain, the hash of previous blocks won't change as if will be undefined
|
//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}`;
|
let hashing = `${timestamp}${lastHash}${nonce}${difficulty}${reward}`;
|
||||||
concatIfNotUndefined(hashing, reward);
|
hashing = concatIfNotUndefined(hashing, 'payments', payments);
|
||||||
concatIfNotUndefined(hashing, transactions);
|
hashing = concatIfNotUndefined(hashing, 'sensorRegistrations', sensorRegistrations);
|
||||||
concatIfNotUndefined(hashing, metadatas);
|
hashing = concatIfNotUndefined(hashing, 'brokerRegistrations', brokerRegistrations);
|
||||||
|
hashing = concatIfNotUndefined(hashing, 'integrations', integrations);
|
||||||
|
|
||||||
return ChainUtil.hash(hashing).toString();
|
return ChainUtil.hash(hashing).toString();
|
||||||
}
|
}
|
||||||
|
@ -71,23 +95,17 @@ class Block {
|
||||||
block.timestamp,
|
block.timestamp,
|
||||||
block.lastHash,
|
block.lastHash,
|
||||||
block.reward,
|
block.reward,
|
||||||
block.transactions,
|
block.payments,
|
||||||
block.metadatas,
|
block.sensorRegistrations,
|
||||||
|
block.brokerRegistrations,
|
||||||
|
block.integrations,
|
||||||
block.nonce,
|
block.nonce,
|
||||||
block.difficulty);
|
block.difficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
//returns false if block's hash doesn't match internals
|
//returns false if block's hash doesn't match internals
|
||||||
static checkHash(block) {
|
static checkHash(block) {
|
||||||
|
const computedHash = Block.blockHash(block);
|
||||||
const computedHash = Block.hash(
|
|
||||||
block.timestamp,
|
|
||||||
block.lastHash,
|
|
||||||
block.reward,
|
|
||||||
block.transactions,
|
|
||||||
block.metadatas,
|
|
||||||
block.nonce,
|
|
||||||
block.difficulty);
|
|
||||||
|
|
||||||
if (computedHash !== block.hash) {
|
if (computedHash !== block.hash) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -108,6 +126,40 @@ class Block {
|
||||||
return Math.max(0, prevDifficulty - 1);
|
return Math.max(0, prevDifficulty - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static debugMine(lastBlock, reward, payments, sensorRegistrations,brokerRegistrations,integrations) {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const difficulty = Block.adjustDifficulty(lastBlock, timestamp);
|
||||||
|
|
||||||
|
let nonce = 0;
|
||||||
|
let hash = '';
|
||||||
|
|
||||||
|
do {
|
||||||
|
nonce++;
|
||||||
|
hash = Block.hash(
|
||||||
|
timestamp,
|
||||||
|
lastBlock.hash,
|
||||||
|
reward,
|
||||||
|
payments,
|
||||||
|
sensorRegistrations,
|
||||||
|
brokerRegistrations,
|
||||||
|
integrations,
|
||||||
|
nonce,
|
||||||
|
difficulty);
|
||||||
|
} while (hash.substring(0, difficulty) !== '0'.repeat(difficulty));
|
||||||
|
|
||||||
|
return new Block(
|
||||||
|
timestamp,
|
||||||
|
lastBlock.hash,
|
||||||
|
hash,
|
||||||
|
reward,
|
||||||
|
payments,
|
||||||
|
sensorRegistrations,
|
||||||
|
brokerRegistrations,
|
||||||
|
integrations,
|
||||||
|
nonce,
|
||||||
|
difficulty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Block;
|
module.exports = Block;
|
|
@ -4,13 +4,13 @@ describe('Block', () => {
|
||||||
let data, lastBlock, block;
|
let data, lastBlock, block;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
data = 'bar';
|
reward = 'bar';
|
||||||
lastBlock = Block.genesis();
|
lastBlock = Block.genesis();
|
||||||
block = Block.mineBlock(lastBlock, data);
|
block = Block.debugMine(lastBlock, reward,[],[]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets the `data` to match the input', () => {
|
it('sets the `data` to match the input', () => {
|
||||||
expect(block.data).toEqual(data);
|
expect(block.reward).toEqual(reward);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets the `lastHash` to match the hash of the last block', () => {
|
it('sets the `lastHash` to match the hash of the last block', () => {
|
||||||
|
|
608
blockchain/blockchain.js
Normal file
608
blockchain/blockchain.js
Normal file
|
@ -0,0 +1,608 @@
|
||||||
|
const Block = require('./block');
|
||||||
|
const N3 = require('n3');
|
||||||
|
const DataFactory = require('n3').DataFactory;
|
||||||
|
const Payment = require('./payment');
|
||||||
|
const SensorRegistration = require('./sensor-registration');
|
||||||
|
const BrokerRegistration = require('./broker-registration');
|
||||||
|
const Integration = require('./integration');
|
||||||
|
const fs = require('fs');
|
||||||
|
const ChainUtil = require('../chain-util');
|
||||||
|
const {
|
||||||
|
MINING_REWARD} = require('../constants');
|
||||||
|
|
||||||
|
function addRDF(store, metadata) {
|
||||||
|
for (const triple of metadata) {
|
||||||
|
store.addQuad(DataFactory.quad(
|
||||||
|
DataFactory.namedNode(triple.s),
|
||||||
|
DataFactory.namedNode(triple.p),
|
||||||
|
DataFactory.namedNode(triple.o)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const verifyRes = Payment.verify(payment);
|
||||||
|
if (!verifyRes.result) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "couldn't verify a payment: " + verifyRes.reason
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputBalance = getBalanceCopyGeneric(payment.input, [changedBalances, prevBalances]);
|
||||||
|
|
||||||
|
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, 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;
|
||||||
|
}
|
||||||
|
changedBalances[payment.input] = inputBalance;
|
||||||
|
|
||||||
|
for (const output of payment.outputs) {
|
||||||
|
const outputBalance = getBalanceCopyGeneric(output.publicKey, [changedBalances, prevBalances]);
|
||||||
|
outputBalance.balance += output.amount;
|
||||||
|
changedBalances[output.publicKey] = outputBalance;
|
||||||
|
}
|
||||||
|
const rewardBalance = getBalanceCopyGeneric(reward, [changedBalances, prevBalances]);
|
||||||
|
rewardBalance.balance += payment.rewardAmount;
|
||||||
|
changedBalances[reward] = rewardBalance;
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyIntegration(changedBalances, prevBalances, reward, integration) {
|
||||||
|
const verifyRes = Integration.verify(integration);
|
||||||
|
if (!verifyRes.result) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "couldn't verify a integration: " + verifyRes.reason
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputBalance = getBalanceCopyGeneric(integration.input, [changedBalances, prevBalances]);
|
||||||
|
|
||||||
|
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, 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) {
|
||||||
|
if (inputBalance.balance < output.amount) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "integration spending more than they have"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
inputBalance.balance -= output.amount;
|
||||||
|
}
|
||||||
|
changedBalances[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]);
|
||||||
|
rewardBalance.balance += integration.rewardAmount;
|
||||||
|
changedBalances[reward] = rewardBalance;
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifySensorRegistration(changedBalances, prevBalances, reward, sensorRegistration, brokers) {
|
||||||
|
const verifyRes = SensorRegistration.verify(sensorRegistration);
|
||||||
|
if (!verifyRes.result) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Couldn't verify a sensor registration: " + verifyRes.reason
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const extInfo = SensorRegistration.getExtInformation(sensorRegistration);
|
||||||
|
|
||||||
|
if (!extInfo.result) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Couldn't get sensor registration ext information: " + extMetadata.reason
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(extInfo.metadata.integrationBroker in brokers)) {
|
||||||
|
console.log(brokers);
|
||||||
|
console.log(extInfo.metadata.integrationBroker);
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Couldn't find sensor registration's nominated broker in the broker list"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputBalance = getBalanceCopyGeneric(sensorRegistration.input, [changedBalances, prevBalances]);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
changedBalances[sensorRegistration.input] = inputBalance;
|
||||||
|
|
||||||
|
const rewardBalance = getBalanceCopyGeneric(reward, [changedBalances, prevBalances]);
|
||||||
|
rewardBalance.balance += sensorRegistration.rewardAmount;
|
||||||
|
changedBalances[reward] = rewardBalance;
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyBrokerRegistration(changedBalances, prevBalances, reward, brokerRegistration) {
|
||||||
|
const verifyRes = BrokerRegistration.verify(brokerRegistration);
|
||||||
|
if (!verifyRes.result) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Couldn't verify a broker registration: " + verifyRes.reason
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputBalance = getBalanceCopyGeneric(brokerRegistration.input, [changedBalances, prevBalances]);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
changedBalances[brokerRegistration.input] = inputBalance;
|
||||||
|
|
||||||
|
const rewardBalance = getBalanceCopyGeneric(reward, [changedBalances, prevBalances]);
|
||||||
|
rewardBalance.balance += brokerRegistration.rewardAmount;
|
||||||
|
changedBalances[reward] = rewardBalance;
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyTxs(prevBalances, reward, brokers, payments, sensorRegistrations, brokerRegistrations, integrations) {
|
||||||
|
const changedBalances = {};
|
||||||
|
|
||||||
|
const rewardBalanceCopy = getBalanceCopyGeneric(reward, [prevBalances]);
|
||||||
|
|
||||||
|
changedBalances[reward] = {
|
||||||
|
balance: rewardBalanceCopy.balance + MINING_REWARD,
|
||||||
|
counter: rewardBalanceCopy.counter
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const payment of payments) {
|
||||||
|
const res = verifyPayment(changedBalances, prevBalances, reward, payment);
|
||||||
|
if (!res.result) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const integration of integrations) {
|
||||||
|
const res = verifyIntegration(changedBalances, prevBalances, reward, integration);
|
||||||
|
if (!res.result) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const brokerRegistration of brokerRegistrations) {
|
||||||
|
const res = verifyBrokerRegistration(changedBalances, prevBalances, reward, brokerRegistration);
|
||||||
|
if (!res.result) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const sensorRegistration of sensorRegistrations) {
|
||||||
|
const res = verifySensorRegistration(changedBalances, prevBalances, reward, sensorRegistration, brokers);
|
||||||
|
if (!res.result) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true,
|
||||||
|
changedBalances: changedBalances
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyBlock(prevBalances, prevBlock, verifyingBlock, brokers) {
|
||||||
|
if (verifyingBlock.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)) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "difficulty is incorrect"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!Block.checkHash(verifyingBlock)) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "hash is invalid failed"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return verifyTxs(prevBalances, verifyingBlock.reward, brokers,
|
||||||
|
Block.getPayments(verifyingBlock),
|
||||||
|
Block.getSensorRegistrations(verifyingBlock),
|
||||||
|
Block.getBrokerRegistrations(verifyingBlock),
|
||||||
|
Block.getIntegrations(verifyingBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyChain(chain) {
|
||||||
|
if (chain.length === 0) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "zero length"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (ChainUtil.stableStringify(chain[0]) !== ChainUtil.stableStringify(Block.genesis())) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "initial block isn't genesis"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const balances = {};
|
||||||
|
const brokers = {};
|
||||||
|
|
||||||
|
for (let i = 1; i < chain.length; i++) {
|
||||||
|
const block = chain[i];
|
||||||
|
const lastBlock = chain[i - 1];
|
||||||
|
|
||||||
|
const verifyResult = verifyBlock(balances, lastBlock, block, brokers);
|
||||||
|
|
||||||
|
if (verifyResult.result === false) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: `Chain is invalid on block ${i}: ${verifyResult.reason}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const publicKey in verifyResult.changedBalances) {
|
||||||
|
balances[publicKey] = verifyResult.changedBalances[publicKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockMetadata = getBlockMetadata(chain[i]);
|
||||||
|
addBlockMetadata(brokers, blockMetadata.brokers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true,
|
||||||
|
balances: balances
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oldChain.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBlockMetadata(block) {
|
||||||
|
|
||||||
|
const returning = {
|
||||||
|
sensors: {},
|
||||||
|
brokers: {},
|
||||||
|
store: new N3.Store()
|
||||||
|
};
|
||||||
|
|
||||||
|
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 undoBlockMetadata(map, undoer) {
|
||||||
|
for (const key in undoer) {
|
||||||
|
const value = undoer[key];
|
||||||
|
|
||||||
|
if (value === null) {
|
||||||
|
delete map[key];
|
||||||
|
} else {
|
||||||
|
map[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Blockchain {
|
||||||
|
constructor() {
|
||||||
|
this.chain = [Block.genesis()];
|
||||||
|
this.balances = {};
|
||||||
|
this.stores = [];
|
||||||
|
this.sensors = {};
|
||||||
|
this.sensorUndos = [];
|
||||||
|
this.brokers = {};
|
||||||
|
this.brokerUndos = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getBalanceCopy(publicKey) {
|
||||||
|
return getBalanceCopyGeneric(publicKey, [this.balances]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSensorInfo(sensorName) {
|
||||||
|
if (sensorName in this.sensors) {
|
||||||
|
return this.sensors[sensorName];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getBrokerInfo(brokerName) {
|
||||||
|
if (brokerName in this.brokers) {
|
||||||
|
return this.brokers[brokerName];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastBlock() {
|
||||||
|
return this.chain[this.chain.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
return JSON.stringify(this.chain);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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`);
|
||||||
|
}
|
||||||
|
return returning;
|
||||||
|
} else {
|
||||||
|
console.log("Didn't find a persisted chain, starting from genesis");
|
||||||
|
return new Blockchain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//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);
|
||||||
|
|
||||||
|
if (!verifyResult.result) {
|
||||||
|
console.log(`Couldn't add block: ${verifyResult.reason}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//all seems to be good, persist
|
||||||
|
this.chain.push(newBlock);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
wouldBeValidBlock(rewardee, payments, sensorRegistrations, brokerRegistrations, integrations) {
|
||||||
|
return verifyTxs(this.balances, rewardee, this.brokers, payments, sensorRegistrations, brokerRegistrations, integrations).result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static isValidChain(chain) {
|
||||||
|
const res = verifyChain(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."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const verifyResult = verifyChain(newChain);
|
||||||
|
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 = newChain;
|
||||||
|
|
||||||
|
//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;
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true,
|
||||||
|
chainDifference: chainDifference,
|
||||||
|
oldChain: oldChain
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Blockchain;
|
55
blockchain/blockchain.test.js
Normal file
55
blockchain/blockchain.test.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
const Blockchain = require('./index');
|
||||||
|
const Block = require('./block');
|
||||||
|
|
||||||
|
describe('Blockchain', () => {
|
||||||
|
let bc, bc2;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
bc = new Blockchain();
|
||||||
|
bc2 = new Blockchain();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('starts with genesis block', () => {
|
||||||
|
expect(bc.chain[0]).toEqual(Block.genesis());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds a new block', () => {
|
||||||
|
const reward = 'test-reward-key';
|
||||||
|
expect(bc.addBlock(Block.debugMine(bc.lastBlock(),reward,[],[]))).toBe(true);
|
||||||
|
|
||||||
|
expect(bc.lastBlock().reward).toEqual(reward);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates a valid chain', () => {
|
||||||
|
expect(bc2.addBlock(Block.debugMine(bc2.lastBlock(), 'test-reward-key', [], []))).toBe(true);
|
||||||
|
|
||||||
|
expect(Blockchain.isValidChain(bc2.chain)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalidates a chain with a corrupt genesis block', () => {
|
||||||
|
bc2.chain[0].hash = 'Bad data';
|
||||||
|
|
||||||
|
expect(Blockchain.isValidChain(bc2.chain)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalidates a corrupt chain', () => {
|
||||||
|
expect(bc2.addBlock(Block.debugMine(bc2.lastBlock(), 'test-reward-key', [], []))).toBe(true);
|
||||||
|
bc2.chain[1].reward = 'Not foo';
|
||||||
|
|
||||||
|
expect(Blockchain.isValidChain(bc2.chain)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('replaces the chain with a valid chain', () => {
|
||||||
|
expect(bc2.addBlock(Block.debugMine(bc2.lastBlock(), 'test-reward-key', [], []))).toBe(true);
|
||||||
|
expect(bc.replaceChain(bc2.chain).result).toBe(true);
|
||||||
|
|
||||||
|
expect(bc.chain).toEqual(bc2.chain);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not replace the chain with one of less than or equal to length', () => {
|
||||||
|
expect(bc.addBlock(Block.debugMine(bc.lastBlock(), 'test-reward-key', [], []))).toBe(true);
|
||||||
|
expect(bc.replaceChain(bc2.chain).result).toBe(false);
|
||||||
|
|
||||||
|
expect(bc.chain).not.toEqual(bc2.chain);
|
||||||
|
})
|
||||||
|
});
|
189
blockchain/broker-registration.js
Normal file
189
blockchain/broker-registration.js
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
const ChainUtil = require('../chain-util');
|
||||||
|
|
||||||
|
const tripleValidator = {
|
||||||
|
s: ChainUtil.validateIsString,
|
||||||
|
p: ChainUtil.validateIsString,
|
||||||
|
o: ChainUtil.validateIsString
|
||||||
|
};
|
||||||
|
|
||||||
|
function validateMetadata(t) {
|
||||||
|
|
||||||
|
let isBroker = [];
|
||||||
|
let costPerMinute = [];
|
||||||
|
let costPerKB = [];
|
||||||
|
let integrationEndpoint = [];
|
||||||
|
|
||||||
|
const validationRes = ChainUtil.validateArray(t, ChainUtil.createValidateObject(tripleValidator));
|
||||||
|
|
||||||
|
if (!validationRes.result) {
|
||||||
|
return validationRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
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://www.w3.org/1999/02/22-rdf-syntax-ns#type":
|
||||||
|
if (triple.o === "SSM/Broker") {
|
||||||
|
isBroker.push(triple.s);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "IoT device metadata/Integration/Endpoint": integrationEndpoint.push(triple); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBroker.length === 0) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "No broker is defined"
|
||||||
|
};
|
||||||
|
} else if (isBroker.length > 1) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Multiple brokers are defined"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const brokerName = isBroker[0];
|
||||||
|
|
||||||
|
if (costPerMinute.length === 0) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "No cost per minute was defined"
|
||||||
|
};
|
||||||
|
} else if (costPerMinute.length > 1) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Multiple cost per minutes were defined"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const CostPerMinuteValue = Number.parseInt(costPerMinute[0].o);
|
||||||
|
if (CostPerMinuteValue === NaN) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Couldn't parse cost per minute as an integer"
|
||||||
|
};
|
||||||
|
} else if (CostPerMinuteValue < 1) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Cost per minute was negative"
|
||||||
|
}
|
||||||
|
} else if (costPerMinute[0].s != brokerName) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Cost per minute object isn't the broker"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (costPerKB.length === 0) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "No cost per KB was defined"
|
||||||
|
};
|
||||||
|
} else if (costPerKB.length > 1) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Multiple cost per KB were defined"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const CostPerKBValue = Number.parseInt(costPerKB[0].o);
|
||||||
|
if (CostPerKBValue === NaN) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Couldn't parse cost per KB as an integer"
|
||||||
|
};
|
||||||
|
} else if (CostPerKBValue < 1) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Cost per KB was negative"
|
||||||
|
}
|
||||||
|
} else if (costPerKB[0].s != brokerName) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Cost per KB object isn't the broker"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integrationEndpoint.length === 0) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "No integration endpoint was defined"
|
||||||
|
};
|
||||||
|
} else if (integrationEndpoint.length > 1) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Multiple integration endpoints were defined"
|
||||||
|
};
|
||||||
|
} else if (integrationEndpoint[0].s != brokerName) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Integration endpoint object isn't the broker"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true,
|
||||||
|
metadata: {
|
||||||
|
brokerName: brokerName,
|
||||||
|
costPerMinute: CostPerMinuteValue,
|
||||||
|
costPerKB: CostPerKBValue,
|
||||||
|
integrationEndpoint: integrationEndpoint[0].o
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseValidation = {
|
||||||
|
input: ChainUtil.validateIsPublicKey,
|
||||||
|
counter: ChainUtil.validateIsInteger,
|
||||||
|
rewardAmount: ChainUtil.createValidateIsIntegerWithMin(0),
|
||||||
|
metadata: validateMetadata,
|
||||||
|
signature: ChainUtil.validateIsSignature
|
||||||
|
};
|
||||||
|
|
||||||
|
class BrokerRegistration {
|
||||||
|
constructor(senderKeyPair, counter, metadata, rewardAmount) {
|
||||||
|
this.input = senderKeyPair.getPublic().encode('hex');
|
||||||
|
this.counter = counter;
|
||||||
|
this.rewardAmount = rewardAmount;
|
||||||
|
this.metadata = metadata;
|
||||||
|
this.signature = senderKeyPair.sign(BrokerRegistration.hashToSign(this));
|
||||||
|
|
||||||
|
const verification = BrokerRegistration.verify(this);
|
||||||
|
if (!verification.result) {
|
||||||
|
throw new Error(verification.reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static hashToSign(registration) {
|
||||||
|
return ChainUtil.hash([
|
||||||
|
registration.counter,
|
||||||
|
registration.rewardAmount,
|
||||||
|
registration.metadata]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static verify(registration) {
|
||||||
|
const validationRes = ChainUtil.validateObject(registration, baseValidation);
|
||||||
|
if (!validationRes.result) {
|
||||||
|
return validationRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const signatureRes = ChainUtil.verifySignature(
|
||||||
|
registration.input,
|
||||||
|
registration.signature,
|
||||||
|
BrokerRegistration.hashToSign(registration));
|
||||||
|
|
||||||
|
if (!signatureRes.result) {
|
||||||
|
return signatureRes.reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getExtInformation(registration) {
|
||||||
|
return validateMetadata(registration.metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BrokerRegistration;
|
|
@ -1,278 +0,0 @@
|
||||||
const Block = require('./block');
|
|
||||||
const N3 = require('n3');
|
|
||||||
const DataFactory = require('n3').DataFactory;
|
|
||||||
const Transaction = require('../wallet/transaction');
|
|
||||||
const { MINING_REWARD } = require('../config');
|
|
||||||
|
|
||||||
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 verifyBlock(prevBalances, prevBlock, verifyingBlock) {
|
|
||||||
if (verifyingBlock.lastHash !== prevBlock.hash) {
|
|
||||||
return {
|
|
||||||
result: false,
|
|
||||||
reason: "last hash didn't match our last hash"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//how to check if new block's timestamp is believable
|
|
||||||
if (verifyingBlock.difficulty !== Block.adjustDifficulty(prevBlock, verifyingBlock.timestamp)) {
|
|
||||||
return {
|
|
||||||
result: false,
|
|
||||||
reason: "difficulty is incorrect"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!Block.checkHash(verifyingBlock)) {
|
|
||||||
return {
|
|
||||||
result: false,
|
|
||||||
reason: "hash is invalid failed"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const changedBalances = {};
|
|
||||||
|
|
||||||
const rewardBalanceCopy = getBalanceCopyGeneric(verifyingBlock.reward, [prevBalances]);
|
|
||||||
|
|
||||||
changedBalances[verifyingBlock.reward] = {
|
|
||||||
balance: rewardBalanceCopy.balance + MINING_REWARD,
|
|
||||||
counter: rewardBalanceCopy.counter
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const transaction of Block.getTransactions(verifyingBlock)) {
|
|
||||||
if (!Transaction.verify(transaction)) {
|
|
||||||
return {
|
|
||||||
result: false,
|
|
||||||
reason: "couldn't verify a transaction" };
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputBalance = getBalanceCopyGeneric(transaction.input, [changedBalances, prevBalances]);
|
|
||||||
|
|
||||||
if (transaction.counter <= inputBalance.counter) {
|
|
||||||
return {
|
|
||||||
result: false,
|
|
||||||
reason: "transaction has invalid counter"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
inputBalance.counter = transaction.counter;
|
|
||||||
|
|
||||||
for (const output of transaction.outputs) {
|
|
||||||
const outputBalance = getBalanceCopyGeneric(output.publicKey, [changedBalances, prevBalances]);
|
|
||||||
|
|
||||||
if (output.amount > inputBalance.balance) {
|
|
||||||
return {
|
|
||||||
result: false,
|
|
||||||
reason: "transaction spending more than they have"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
inputBalance.balance -= output.amount;
|
|
||||||
outputBalance.balance += output.amount;
|
|
||||||
changedBalances[output.publicKey] = outputBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
changedBalances[transaction.input] = inputBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
result: true,
|
|
||||||
changedBalances: changedBalances
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function verifyChain(chain) {
|
|
||||||
if (chain.length === 0) {
|
|
||||||
return {
|
|
||||||
result: false,
|
|
||||||
reason: "zero length"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (JSON.stringify(chain[0]) !== JSON.stringify(Block.genesis())) {
|
|
||||||
return {
|
|
||||||
result: false,
|
|
||||||
reason: "initial block isn't genesis"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const balances = {};
|
|
||||||
|
|
||||||
for (let i = 1; i < chain.length; i++) {
|
|
||||||
const block = chain[i];
|
|
||||||
const lastBlock = chain[i - 1];
|
|
||||||
|
|
||||||
const verifyResult = verifyBlock(balances, lastBlock, block);
|
|
||||||
|
|
||||||
if (verifyResult.result === false) {
|
|
||||||
return {
|
|
||||||
result: false,
|
|
||||||
reason: `Chain is invalid on block ${i}: ${verifyResult.reason}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const publicKey in verifyResult.changedBalances) {
|
|
||||||
balances[publicKey] = verifyResult.changedBalances[publicKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
result: true,
|
|
||||||
balances: balances
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addBlockMetadata(blockchain, block) {
|
|
||||||
const metadatas = Block.getMetadatas(block);
|
|
||||||
for (const metadata of metadatas) {
|
|
||||||
if (!("SSNmetadata" in metadata)) {
|
|
||||||
//assert?
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ssn = metadata.SSNmetadata;
|
|
||||||
|
|
||||||
const parser = new N3.Parser();
|
|
||||||
|
|
||||||
parser.parse(
|
|
||||||
ssn,
|
|
||||||
(error, quadN, prefixes) => {
|
|
||||||
if (quadN) {
|
|
||||||
blockchain.store.addQuad(DataFactory.quad(
|
|
||||||
DataFactory.namedNode(quadN.subject.id),
|
|
||||||
DataFactory.namedNode(quadN.predicate.id),
|
|
||||||
DataFactory.namedNode(quadN.object.id),
|
|
||||||
DataFactory.namedNode(metadata.id)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Blockchain {
|
|
||||||
constructor() {
|
|
||||||
this.chain = [Block.genesis()];
|
|
||||||
this.balances = {};
|
|
||||||
this.store = new N3.Store();
|
|
||||||
}
|
|
||||||
|
|
||||||
getBalanceCopy(publicKey) {
|
|
||||||
return getBalanceCopyGeneric(publicKey, [this.balances]);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastBlock() {
|
|
||||||
return this.chain[this.chain.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize() {
|
|
||||||
return JSON.stringify(this.chain);
|
|
||||||
}
|
|
||||||
|
|
||||||
static deserialize(serialized) {
|
|
||||||
const returning = new Blockchain();
|
|
||||||
const replaceResult = returning.replaceChain(JSON.parse(serialized));
|
|
||||||
if(!replaceResult.result) {
|
|
||||||
//chain wasn't valid
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (!verifyResult.result) {
|
|
||||||
console.log(`Couldn't add block: ${verifyResult.reason}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//all seems to be good, persist
|
|
||||||
this.chain.push(newBlock);
|
|
||||||
|
|
||||||
for (const publicKey in verifyResult.changedBalances) {
|
|
||||||
this.balances[publicKey] = verifyResult.changedBalances[publicKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
addBlockMetadata(this, newBlock);
|
|
||||||
|
|
||||||
//console.log("Added new block");
|
|
||||||
//console.log(newBlock);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static isValidChain(chain) {
|
|
||||||
const res = verifyChain(chain);
|
|
||||||
|
|
||||||
return res.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
//return false on fail, 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."
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const verifyResult = verifyChain(newChain);
|
|
||||||
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 = newChain;
|
|
||||||
|
|
||||||
//find where they differ
|
|
||||||
const chainDifference = findChainDifference(oldChain, newChain);
|
|
||||||
console.log(`chain difference was ${chainDifference}`);
|
|
||||||
|
|
||||||
//fix metadata
|
|
||||||
for (let i = oldChain.length - 1; i >= chainDifference; i--) {
|
|
||||||
for (const metadata of Block.getMetadatas(oldChain[i])) {
|
|
||||||
this.store.deleteGraph(metadata.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = chainDifference; i < newChain.length; ++i) {
|
|
||||||
addBlockMetadata(this, newChain[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
//fix balance
|
|
||||||
this.balances = verifyResult.balances;
|
|
||||||
|
|
||||||
return {
|
|
||||||
result: true,
|
|
||||||
chainDifference: chainDifference,
|
|
||||||
oldChain: oldChain
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Blockchain;
|
|
|
@ -1,55 +0,0 @@
|
||||||
const Blockchain = require('./index');
|
|
||||||
const Block = require('./block');
|
|
||||||
|
|
||||||
describe('Blockchain', () => {
|
|
||||||
let bc, bc2;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
bc = new Blockchain();
|
|
||||||
bc2 = new Blockchain();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('starts with genesis block', () => {
|
|
||||||
expect(bc.chain[0]).toEqual(Block.genesis());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds a new block', () => {
|
|
||||||
const data = 'foo';
|
|
||||||
bc.addBlock(data);
|
|
||||||
|
|
||||||
expect(bc.chain[bc.chain.length-1].data).toEqual(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('validates a valid chain', () => {
|
|
||||||
bc2.addBlock('foo');
|
|
||||||
|
|
||||||
expect(bc.isValidChain(bc2.chain)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('invalidates a chain with a corrupt genesis block', () => {
|
|
||||||
bc2.chain[0].data = 'Bad data';
|
|
||||||
|
|
||||||
expect(bc.isValidChain(bc2.chain)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('invalidates a corrupt chain', () => {
|
|
||||||
bc2.addBlock('foo');
|
|
||||||
bc2.chain[1].data = 'Not foo';
|
|
||||||
|
|
||||||
expect(bc.isValidChain(bc2.chain)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('replaces the chain with a valid chain', () => {
|
|
||||||
bc2.addBlock('goo');
|
|
||||||
bc.replaceChain(bc2.chain);
|
|
||||||
|
|
||||||
expect(bc.chain).toEqual(bc2.chain);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not replace the chain with one of less than or equal to length', () => {
|
|
||||||
bc.addBlock('foo');
|
|
||||||
bc.replaceChain(bc2.chain);
|
|
||||||
|
|
||||||
expect(bc.chain).not.toEqual(bc2.chain);
|
|
||||||
})
|
|
||||||
});
|
|
81
blockchain/integration.js
Normal file
81
blockchain/integration.js
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
const ChainUtil = require('../chain-util');
|
||||||
|
|
||||||
|
const outputValidation = {
|
||||||
|
publicKey: ChainUtil.validateIsPublicKey,
|
||||||
|
sensor: ChainUtil.validateIsString,
|
||||||
|
amount: ChainUtil.createValidateIsIntegerWithMin(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
function validateOutputs(t) {
|
||||||
|
if (!ChainUtil.validateArray(t, (output) => {
|
||||||
|
return ChainUtil.validateObject(output, outputValidation).result;
|
||||||
|
})) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.outputs.length <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseValidation = {
|
||||||
|
input: ChainUtil.validateIsPublicKey,
|
||||||
|
counter: ChainUtil.createValidateIsIntegerWithMin(1),
|
||||||
|
rewardAmount: ChainUtil.createValidateIsIntegerWithMin(0),
|
||||||
|
outputs: validateOutputs,
|
||||||
|
signature: ChainUtil.validateIsSignature
|
||||||
|
};
|
||||||
|
|
||||||
|
class Integration {
|
||||||
|
constructor(senderKeyPair, counter, outputs, rewardAmount) {
|
||||||
|
this.input = senderKeyPair.getPublic().encode('hex');
|
||||||
|
this.counter = counter;
|
||||||
|
this.rewardAmount = rewardAmount;
|
||||||
|
this.outputs = outputs;
|
||||||
|
this.signature = senderKeyPair.sign(Integration.hashToSign(this));
|
||||||
|
|
||||||
|
|
||||||
|
const verification = Integration.verify(this);
|
||||||
|
if (!verification.result) {
|
||||||
|
throw new Error(verification.reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static createOutput(recipientPublicKey, sensorId, amount) {
|
||||||
|
return {
|
||||||
|
publicKey: recipientPublicKey,
|
||||||
|
sensor: sensorId,
|
||||||
|
amount: amount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static hashToSign(registration) {
|
||||||
|
return ChainUtil.hash([
|
||||||
|
registration.counter,
|
||||||
|
registration.rewardAmount,
|
||||||
|
registration.outputs]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static verify(registration) {
|
||||||
|
const validationRes = ChainUtil.validateObject(registration, baseValidation);
|
||||||
|
if (!validationRes.result) {
|
||||||
|
return validationRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const verifyRes = ChainUtil.verifySignature(
|
||||||
|
registration.input,
|
||||||
|
registration.signature,
|
||||||
|
Integration.hashToSign(registration));
|
||||||
|
if (!verifyRes.result) {
|
||||||
|
return verifyRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Integration;
|
78
blockchain/payment.js
Normal file
78
blockchain/payment.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
const ChainUtil = require('../chain-util');
|
||||||
|
|
||||||
|
const outputValidation = {
|
||||||
|
publicKey: ChainUtil.validateIsPublicKey,
|
||||||
|
amount: ChainUtil.createValidateIsIntegerWithMin(1)
|
||||||
|
};
|
||||||
|
|
||||||
|
function validateOutputs(t) {
|
||||||
|
if (!ChainUtil.validateArray(t, function (output) {
|
||||||
|
return ChainUtil.validateObject(output, outputValidation).result;
|
||||||
|
})) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.length <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseValidation = {
|
||||||
|
input: ChainUtil.validateIsPublicKey,
|
||||||
|
counter: ChainUtil.createValidateIsIntegerWithMin(1),
|
||||||
|
rewardAmount: ChainUtil.createValidateIsIntegerWithMin(0),
|
||||||
|
outputs: validateOutputs,
|
||||||
|
signature: ChainUtil.validateIsSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
class Payment {
|
||||||
|
constructor(senderKeyPair, counter, outputs, rewardAmount) {
|
||||||
|
this.input = senderKeyPair.getPublic().encode('hex');
|
||||||
|
this.counter = counter;
|
||||||
|
this.rewardAmount = rewardAmount;
|
||||||
|
this.outputs = outputs;
|
||||||
|
this.signature = senderKeyPair.sign(Payment.hashToSign(this));
|
||||||
|
|
||||||
|
const verification = Payment.verify(this);
|
||||||
|
if (!verification.result) {
|
||||||
|
throw new Error(verification.reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static hashToSign(transaction) {
|
||||||
|
return ChainUtil.hash([
|
||||||
|
transaction.counter,
|
||||||
|
transaction.rewardAmount,
|
||||||
|
transaction.outputs]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static createOutput(recipient, amount) {
|
||||||
|
return {
|
||||||
|
publicKey: recipient,
|
||||||
|
amount: amount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static verify(transaction) {
|
||||||
|
const validationRes = ChainUtil.validateObject(transaction, baseValidation);
|
||||||
|
if (!validationRes.result) {
|
||||||
|
return validationRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const verifyRes = ChainUtil.verifySignature(
|
||||||
|
transaction.input,
|
||||||
|
transaction.signature,
|
||||||
|
Payment.hashToSign(transaction));
|
||||||
|
if (!verifyRes.result) {
|
||||||
|
return verifyRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Payment;
|
|
@ -1,6 +1,6 @@
|
||||||
const Transaction = require('./transaction');
|
const Transaction = require('./transaction');
|
||||||
const Wallet = require('./index');
|
const Wallet = require('./index');
|
||||||
const { MINING_REWARD } = require('../config');
|
const { MINING_REWARD } = require('../constants');
|
||||||
|
|
||||||
describe('Transaction', () => {
|
describe('Transaction', () => {
|
||||||
let transaction, wallet, recipient, amount;
|
let transaction, wallet, recipient, amount;
|
188
blockchain/sensor-registration.js
Normal file
188
blockchain/sensor-registration.js
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
const ChainUtil = require('../chain-util');
|
||||||
|
|
||||||
|
const tripleValidator = {
|
||||||
|
s: ChainUtil.validateIsString,
|
||||||
|
p: ChainUtil.validateIsString,
|
||||||
|
o: ChainUtil.validateIsString
|
||||||
|
};
|
||||||
|
|
||||||
|
function validateMetadata(t) {
|
||||||
|
let isSensor = [];
|
||||||
|
let costPerMinute = [];
|
||||||
|
let costPerKB = [];
|
||||||
|
let integrationBroker = [];
|
||||||
|
|
||||||
|
const validationRes = ChainUtil.validateArray(t, ChainUtil.createValidateObject(tripleValidator));
|
||||||
|
|
||||||
|
if (!validationRes.result) {
|
||||||
|
return validationRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
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://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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSensor.length === 0) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "No sensor is defined"
|
||||||
|
};
|
||||||
|
} else if (isSensor.length > 1) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Multiple sensors are defined"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const sensorName = isSensor[0];
|
||||||
|
|
||||||
|
if (costPerMinute.length === 0) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "No cost per minute was defined"
|
||||||
|
};
|
||||||
|
} else if (costPerMinute.length > 1) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Multiple cost per minutes were defined"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const CostPerMinuteValue = Number.parseInt(costPerMinute[0].o);
|
||||||
|
if (CostPerMinuteValue === NaN) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Couldn't parse cost per minute as an integer"
|
||||||
|
};
|
||||||
|
} else if (CostPerMinuteValue < 1) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Cost per minute was negative"
|
||||||
|
}
|
||||||
|
} else if (costPerMinute[0].s != sensorName) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Cost per minute object isn't the broker"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (costPerKB.length === 0) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "No cost per KB was defined"
|
||||||
|
};
|
||||||
|
} else if (costPerKB.length > 1) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Multiple cost per KB were defined"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const CostPerKBValue = Number.parseInt(costPerKB[0].o);
|
||||||
|
if (CostPerKBValue === NaN) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Couldn't parse cost per KB as an integer"
|
||||||
|
};
|
||||||
|
} else if (CostPerKBValue < 1) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Cost per KB was negative"
|
||||||
|
}
|
||||||
|
} else if (costPerKB[0].s != sensorName) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Cost per KB object isn't the broker"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integrationBroker.length === 0) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "No integration broker was defined"
|
||||||
|
};
|
||||||
|
} else if (integrationBroker.length > 1) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Multiple integration brokers were defined"
|
||||||
|
};
|
||||||
|
} else if (integrationBroker[0].s != sensorName) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Integration broker subjsect isn't the sensor"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true,
|
||||||
|
metadata: {
|
||||||
|
sensorName: sensorName,
|
||||||
|
costPerMinute: CostPerMinuteValue,
|
||||||
|
costPerKB: CostPerKBValue,
|
||||||
|
integrationBroker: integrationBroker[0].o
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseValidation = {
|
||||||
|
input: ChainUtil.validateIsPublicKey,
|
||||||
|
counter: ChainUtil.createValidateIsIntegerWithMin(1),
|
||||||
|
rewardAmount: ChainUtil.createValidateIsIntegerWithMin(0),
|
||||||
|
metadata: validateMetadata,
|
||||||
|
signature: ChainUtil.validateIsSignature
|
||||||
|
};
|
||||||
|
|
||||||
|
class SensorRegistration {
|
||||||
|
constructor(senderKeyPair, counter, metadata, rewardAmount) {
|
||||||
|
this.input = senderKeyPair.getPublic().encode('hex');
|
||||||
|
this.counter = counter;
|
||||||
|
this.rewardAmount = rewardAmount;
|
||||||
|
this.metadata = metadata;
|
||||||
|
this.signature = senderKeyPair.sign(SensorRegistration.hashToSign(this));
|
||||||
|
|
||||||
|
const verification = SensorRegistration.verify(this);
|
||||||
|
if (!verification.result) {
|
||||||
|
throw new Error(verification.reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static hashToSign(registration) {
|
||||||
|
return ChainUtil.hash([
|
||||||
|
registration.counter,
|
||||||
|
registration.rewardAmount,
|
||||||
|
registration.metadata]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static verify(registration) {
|
||||||
|
const validationResult = ChainUtil.validateObject(registration, baseValidation);
|
||||||
|
if (!validationResult.result) {
|
||||||
|
console.log(`Failed validation: ${validationResult.reason}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const verifyRes = ChainUtil.verifySignature(
|
||||||
|
registration.input,
|
||||||
|
registration.signature,
|
||||||
|
SensorRegistration.hashToSign(registration));
|
||||||
|
if (!verifyRes.result) {
|
||||||
|
return verifyRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getExtInformation(registration) {
|
||||||
|
return validateMetadata(registration.metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SensorRegistration;
|
|
@ -1,7 +1,7 @@
|
||||||
const Transaction = require('./transaction');
|
const Transaction = require('./transaction');
|
||||||
const Metadata = require('./metadata');
|
const Metadata = require('./metadata');
|
||||||
const Wallet = require('./index');
|
const Wallet = require('./index');
|
||||||
const { MINING_REWARD } = require('../config');
|
const { MINING_REWARD } = require('../constants');
|
||||||
|
|
||||||
describe('Transaction & Metadata', () => {
|
describe('Transaction & Metadata', () => {
|
||||||
let transaction, metadata, wallet, recipient, amount,
|
let transaction, metadata, wallet, recipient, amount,
|
18
blockchain/transaction.js
Normal file
18
blockchain/transaction.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
const Payment = require('./payment');
|
||||||
|
const Integration = require('./integration');
|
||||||
|
const SensorRegistration = require('./sensor-registration');
|
||||||
|
const BrokerRegistration = require('./broker-registration');
|
||||||
|
|
||||||
|
class Transaction {
|
||||||
|
constructor(transaction, type) {
|
||||||
|
this.transaction = transaction;
|
||||||
|
this.verify = type.verify;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static mapId(type) {
|
||||||
|
return type.name();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Transaction;
|
219
broker/broker-app.js
Normal file
219
broker/broker-app.js
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
//BROKER
|
||||||
|
const express = require('express');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
const P2pServer = require('../p2p-server');
|
||||||
|
const Broker = require('./broker');
|
||||||
|
|
||||||
|
const Aedes = require('aedes');
|
||||||
|
|
||||||
|
const Config = require('../config');
|
||||||
|
const ChainUtil = require('../chain-util');
|
||||||
|
|
||||||
|
const QueryEngine = require('@comunica/query-sparql-rdfjs').QueryEngine;
|
||||||
|
const Blockchain = require('../blockchain/blockchain');
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {
|
||||||
|
DEFAULT_PORT_BROKER_API,
|
||||||
|
DEFAULT_PORT_BROKER_CHAIN,
|
||||||
|
DEFAULT_PORT_BROKER_SENSOR_HANDSHAKE,
|
||||||
|
DEFAULT_PORT_BROKER_SENSOR_MQTT,
|
||||||
|
DEFAULT_PORT_BROKER_CLIENT_MQTT,
|
||||||
|
DEFAULT_PORT_MINER_CHAIN
|
||||||
|
} = require('../constants');
|
||||||
|
|
||||||
|
const CONFIGS_STORAGE_LOCATION = "./settings.json";
|
||||||
|
|
||||||
|
const config = new Config(CONFIGS_STORAGE_LOCATION);
|
||||||
|
|
||||||
|
const broker = new Broker(config.get({
|
||||||
|
key: "broker-keypair",
|
||||||
|
default: ChainUtil.genKeyPair(),
|
||||||
|
transform: ChainUtil.deserializeKeyPair
|
||||||
|
}));
|
||||||
|
const broker_name = config.get({
|
||||||
|
key: "broker-name",
|
||||||
|
default: broker.keyPair.getPublic().encode('hex')
|
||||||
|
});
|
||||||
|
const apiPort = config.get({
|
||||||
|
key: "broker-api-port",
|
||||||
|
default: DEFAULT_PORT_BROKER_API
|
||||||
|
});
|
||||||
|
const blockchainLocation = config.get({
|
||||||
|
key: "broker-blockchain-location",
|
||||||
|
default: "./broker_blockchain.json"
|
||||||
|
});
|
||||||
|
const chainServerPort = config.get({
|
||||||
|
key: "broker-chain-server-port",
|
||||||
|
default: DEFAULT_PORT_BROKER_CHAIN
|
||||||
|
});
|
||||||
|
const chainServerPeers = config.get({
|
||||||
|
key: "broker-chain-server-peers",
|
||||||
|
default: ["ws://127.0.0.1:" + DEFAULT_PORT_MINER_CHAIN]
|
||||||
|
});
|
||||||
|
const sensorHandshakePort = config.get({
|
||||||
|
key: "broker-sensor-handshake-port",
|
||||||
|
default: DEFAULT_PORT_BROKER_SENSOR_HANDSHAKE
|
||||||
|
});
|
||||||
|
const sensorMQTTPort = config.get({
|
||||||
|
key: "broker-sensor-MQTT-port",
|
||||||
|
default: DEFAULT_PORT_BROKER_SENSOR_MQTT
|
||||||
|
});
|
||||||
|
const clientMQTTPort = config.get({
|
||||||
|
key: "broker-client-MQTT-port",
|
||||||
|
default: DEFAULT_PORT_BROKER_CLIENT_MQTT
|
||||||
|
});
|
||||||
|
|
||||||
|
const blockchain = Blockchain.loadFromDisk(blockchainLocation);
|
||||||
|
|
||||||
|
let sensorsServing = {};
|
||||||
|
|
||||||
|
const sensorMQTT = new Aedes({
|
||||||
|
id: broker_name
|
||||||
|
});
|
||||||
|
const sensorMQTTServer = require('net').createServer(sensorMQTT.handle);
|
||||||
|
const sensorMQTTSubscriptions = {};
|
||||||
|
const clientMQTT = new Aedes({
|
||||||
|
id: broker_name
|
||||||
|
});
|
||||||
|
const clientMQTTServer = require('net').createServer(clientMQTT.handle);
|
||||||
|
|
||||||
|
function onNewPacket(sensor, data) {
|
||||||
|
//check to see if sensor has been paid for
|
||||||
|
|
||||||
|
clientMQTT.publish({
|
||||||
|
topic: sensor,
|
||||||
|
payload: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockchain.saveToDisk(blockchainLocation);
|
||||||
|
|
||||||
|
sensorsServing = {};
|
||||||
|
|
||||||
|
for (const sensorName in blockchain.sensors) {
|
||||||
|
const sensorData = blockchain.sensors[sensorName];
|
||||||
|
|
||||||
|
if (sensorData.integrationBroker === broker_name) {
|
||||||
|
sensorsServing[sensorName] = sensorData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//UNSUBSCRIBE
|
||||||
|
for (const sensorName in sensorMQTTSubscriptions) {
|
||||||
|
if (!(sensorName in sensorsServing)) {
|
||||||
|
|
||||||
|
const deliverFunction = sensorMQTTSubscriptions[sensorName];
|
||||||
|
|
||||||
|
sensorMQTT.unsubscribe(sensorName, deliverFunction, () => { });
|
||||||
|
|
||||||
|
delete sensorMQTTSubscriptions[sensorName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//SUBSCRIBE
|
||||||
|
for (const sensorName in sensorsServing) {
|
||||||
|
if (!(sensorName in sensorMQTTSubscriptions)) {
|
||||||
|
const deliverFunction = (packet, cb) => {
|
||||||
|
onNewPacket(packet.topic, packet.payload);
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
|
||||||
|
sensorMQTTSubscriptions[sensorName] = deliverFunction;
|
||||||
|
|
||||||
|
sensorMQTT.subscribe(sensorName, deliverFunction, () => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onSensorHandshakeMsg(sensor, data) {
|
||||||
|
onNewPacket(sensor, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chainServer = new P2pServer("Chain-server");
|
||||||
|
chainServer.start(chainServerPort, chainServerPeers, (_) => { }, onChainServerRecv);
|
||||||
|
|
||||||
|
broker.start(sensorHandshakePort, onSensorHandshakeMsg);
|
||||||
|
sensorMQTTServer.listen(sensorMQTTPort, () => {
|
||||||
|
console.log("Sensor MQTT started");
|
||||||
|
});
|
||||||
|
clientMQTTServer.listen(clientMQTTPort, () => {
|
||||||
|
console.log("Client MQTT started");
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
|
|
||||||
|
app.listen(apiPort, () => console.log(`Listening on port ${apiPort}`));
|
||||||
|
|
||||||
|
app.get('/sensors', (req, res) => {
|
||||||
|
res.json(sensorsServing);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/ChainServer/sockets', (req, res) => {
|
||||||
|
res.json(chainServer.sockets);
|
||||||
|
});
|
||||||
|
app.post('/ChainServer/connect', (req, res) => {
|
||||||
|
chainServer.connect(req.body.url);
|
||||||
|
res.json("Connecting");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/public-key', (req, res) => {
|
||||||
|
res.json(wallet.publicKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/key-pair', (req, res) => {
|
||||||
|
res.json(ChainUtil.serializeKeyPair(wallet.keyPair));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/MyBalance', (req, res) => {
|
||||||
|
res.json(blockchain.getBalanceCopy(wallet.publicKey));
|
||||||
|
});
|
||||||
|
app.get('/Balance', (req, res) => {
|
||||||
|
const balance = blockchain.getBalanceCopy(req.body.publicKey);
|
||||||
|
res.json(balance);
|
||||||
|
});
|
||||||
|
app.get('/Balances', (req, res) => {
|
||||||
|
const balances = blockchain.balances;
|
||||||
|
res.json(balances);
|
||||||
|
});
|
||||||
|
|
||||||
|
const myEngine = new QueryEngine();
|
||||||
|
|
||||||
|
app.post('/sparql', (req, res) => {
|
||||||
|
const start = async function () {
|
||||||
|
try {
|
||||||
|
let result = [];
|
||||||
|
const bindingsStream = await myEngine.queryBindings(
|
||||||
|
req.body.query,
|
||||||
|
{
|
||||||
|
readOnly: true,
|
||||||
|
sources: getBlockchain().stores
|
||||||
|
});
|
||||||
|
bindingsStream.on('data', (binding) => {
|
||||||
|
result.push(binding);
|
||||||
|
});
|
||||||
|
bindingsStream.on('end', () => {
|
||||||
|
res.json(JSON.stringify(result));
|
||||||
|
});
|
||||||
|
bindingsStream.on('error', (err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.json("Error occured while querying");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
start()
|
||||||
|
|
||||||
|
});
|
102
broker/broker.js
Normal file
102
broker/broker.js
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
const Websocket = require('ws');
|
||||||
|
//const Mqtt = require('mqtt');
|
||||||
|
//const Aedes = require('aedes')(); /* aedes is a stream-based MQTT broker */
|
||||||
|
//const MQTTserver = require('net').createServer(aedes.handle);
|
||||||
|
|
||||||
|
const ChainUtil = require('../chain-util');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const STATE_CLIENT_HELLOING = 0;
|
||||||
|
const STATE_OPERATIONAL = 1;
|
||||||
|
|
||||||
|
function onClientHelloing(parent, socket, data) {
|
||||||
|
const asJson = JSON.parse(data);
|
||||||
|
const owner = asJson.owner;
|
||||||
|
const sensor = asJson.sensor;
|
||||||
|
const signature = asJson.signature;
|
||||||
|
const clientNonce = asJson.clientNonce;
|
||||||
|
|
||||||
|
if (typeof owner !== 'string' || typeof sensor !== 'string' || typeof signature !== 'object' || typeof clientNonce !== 'string') {
|
||||||
|
console.log("Bad client hello");
|
||||||
|
socket.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket.owner = owner;
|
||||||
|
socket.sensor = sensor;
|
||||||
|
socket.clientNonce = clientNonce;
|
||||||
|
const verifyRes = ChainUtil.verifySignature(owner, signature, socket.serverNonce + clientNonce);
|
||||||
|
if (!verifyRes.result) {
|
||||||
|
console.log("Bad client hello signature: " + verifyRes.reason);
|
||||||
|
socket.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ourSig = parent.keyPair.sign(clientNonce + socket.serverNonce);
|
||||||
|
socket.send(JSON.stringify(ourSig));
|
||||||
|
socket.state = STATE_OPERATIONAL;
|
||||||
|
console.log(`Sensor ${socket.owner}:${socket.sensor} is operational`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOperational(parent, socket, data) {
|
||||||
|
parent.onMessage(socket.sensor, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSocketMessage(parent, socket, data) {
|
||||||
|
switch (socket.state) {
|
||||||
|
case STATE_CLIENT_HELLOING: onClientHelloing(parent, socket, data); break;
|
||||||
|
case STATE_OPERATIONAL: onOperational(parent, socket, data); break;
|
||||||
|
default: throw Error("Invalid internal state");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Socket {
|
||||||
|
constructor(parent, socket, serverNonce) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.socket = socket;
|
||||||
|
this.serverNonce = serverNonce;
|
||||||
|
this.state = STATE_CLIENT_HELLOING;
|
||||||
|
this.socket.on('message', (data) => {
|
||||||
|
onSocketMessage(parent, this, data);
|
||||||
|
});
|
||||||
|
this.send(serverNonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data) {
|
||||||
|
this.socket.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConnection(broker, rawSocket) {
|
||||||
|
console.log("Sensor connected");
|
||||||
|
crypto.randomBytes(2048, (err, buf) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(`Couldn't generate server nonce: ${err}`);
|
||||||
|
rawSocket.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new Socket(broker, rawSocket, buf.toString('hex'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class Broker {
|
||||||
|
constructor(keyPair) {
|
||||||
|
//owner:sensor->mqtt channel
|
||||||
|
this.brokering = {};
|
||||||
|
this.keyPair = keyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
start(sensorPort, onMessage) {
|
||||||
|
this.onMessage = onMessage;
|
||||||
|
this.sensorPort = sensorPort;
|
||||||
|
this.server = new Websocket.Server({ port: this.sensorPort });
|
||||||
|
this.server.on('connection', socket => onConnection(this, socket));
|
||||||
|
|
||||||
|
console.log(`Broker listening for sensors on: ${this.sensorPort}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Broker;
|
245
chain-util.js
245
chain-util.js
|
@ -3,6 +3,22 @@ const SHA256 = require('crypto-js/sha256');
|
||||||
const { v1 : uuidV1 } = require ('uuid');
|
const { v1 : uuidV1 } = require ('uuid');
|
||||||
const ec = new EC('secp256k1');
|
const ec = new EC('secp256k1');
|
||||||
|
|
||||||
|
function convertJsonKeyValueToRDFImpl(key, object) {
|
||||||
|
const returning = [];
|
||||||
|
|
||||||
|
for (const key in object) {
|
||||||
|
const value = object[key];
|
||||||
|
|
||||||
|
if (value instanceof Array) {
|
||||||
|
} else if (value instanceof Object) {
|
||||||
|
returning.push({
|
||||||
|
o
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ChainUtil {
|
class ChainUtil {
|
||||||
static genKeyPair() {
|
static genKeyPair() {
|
||||||
return ec.genKeyPair();
|
return ec.genKeyPair();
|
||||||
|
@ -13,11 +29,21 @@ class ChainUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
static hash(data) {
|
static hash(data) {
|
||||||
return SHA256(JSON.stringify(data)).toString();
|
return SHA256(ChainUtil.stableStringify(data)).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
static verifySignature(publicKey, signature, dataHash) {
|
static verifySignature(publicKey, signature, dataHash) {
|
||||||
return ec.keyFromPublic(publicKey, 'hex').verify(dataHash, signature);
|
//TODO, validate signature object
|
||||||
|
if (!ec.keyFromPublic(publicKey, 'hex').verify(dataHash, signature)) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Couldn't verify signature"
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static deserializeKeyPair(serialized) {
|
static deserializeKeyPair(serialized) {
|
||||||
|
@ -27,6 +53,221 @@ class ChainUtil {
|
||||||
static serializeKeyPair(keyPair) {
|
static serializeKeyPair(keyPair) {
|
||||||
return keyPair.getPrivate().toString('hex');
|
return keyPair.getPrivate().toString('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//stable stringify for hashing
|
||||||
|
static stableStringify(object) {
|
||||||
|
|
||||||
|
if (object instanceof Array) {
|
||||||
|
let returning = '[';
|
||||||
|
|
||||||
|
for (let i = 0; i < object.length; i++) {
|
||||||
|
if (typeof object[i] === "undefined" || object[i] === null) {
|
||||||
|
returning += "null";
|
||||||
|
} else {
|
||||||
|
returning += ChainUtil.stableStringify(object[i]);
|
||||||
|
}
|
||||||
|
if (i !== object.length - 1) {
|
||||||
|
returning += ',';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
returning += ']';
|
||||||
|
return returning;
|
||||||
|
}
|
||||||
|
if (!(object instanceof Object)) {
|
||||||
|
return JSON.stringify(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
let returning = '{';
|
||||||
|
|
||||||
|
const keys = Object.keys(object).sort();
|
||||||
|
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const key = keys[i];
|
||||||
|
if (!object.hasOwnProperty(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (typeof object[key] === "undefined" || object[key] === null) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
returning += `"${key}":${ChainUtil.stableStringify(object[key])}`;
|
||||||
|
if (i !== keys.length - 1) {
|
||||||
|
returning += ',';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
returning += '}';
|
||||||
|
return returning;
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateAlways(_) {
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateIsObject(t) {
|
||||||
|
if (typeof t !== 'object') {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Is not an object"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateIsString(t) {
|
||||||
|
if (typeof t === 'string') {
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Is not string"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateIsInteger(t) {
|
||||||
|
if (typeof t !== 'number') {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Is not number"
|
||||||
|
};
|
||||||
|
|
||||||
|
} else if (!Number.isInteger(t)) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Is not integer"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//includes minimum
|
||||||
|
static validateIsIntegerWithMin(t, minimum) {
|
||||||
|
if (typeof t !== 'number') {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Is not number"
|
||||||
|
};
|
||||||
|
} else if (!Number.isInteger(t)) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Is not integer"
|
||||||
|
};
|
||||||
|
} else if (t < minimum) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Is below minimum"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//includes minimum
|
||||||
|
static createValidateIsIntegerWithMin(minimum) {
|
||||||
|
return (t) => {
|
||||||
|
return ChainUtil.validateIsIntegerWithMin(t, minimum);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateIsPublicKey(t) {
|
||||||
|
//TODO
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateIsSignature(t) {
|
||||||
|
//TODO
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateArray(t, memberValidator) {
|
||||||
|
if (!(t instanceof Array)) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Is not an Array"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (const member of t) {
|
||||||
|
const res = memberValidator(member);
|
||||||
|
if (!res.result) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Array member validation failed: " + res.reason
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static createValidateArray(memberValidator) {
|
||||||
|
return function (t) {
|
||||||
|
return ChainUtil.validateArray(t, memberValidator);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateObject(t, memberValidator) {
|
||||||
|
if (!(t instanceof Object)) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Is not an object"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in memberValidator) {
|
||||||
|
const validator = memberValidator[key];
|
||||||
|
|
||||||
|
if (!(key in t)) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Couldn't find key: " + key
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = validator(t[key]);
|
||||||
|
|
||||||
|
if (!res.result) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: `Validator for key '${key}' failed: ${res.reason}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in t) {
|
||||||
|
if (!(key in memberValidator)) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: "Verifying has key not in validators"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static createValidateObject(memberValidator) {
|
||||||
|
return function (t) {
|
||||||
|
return ChainUtil.validateObject(t, memberValidator);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ChainUtil;
|
module.exports = ChainUtil;
|
41
config.js
41
config.js
|
@ -1,6 +1,37 @@
|
||||||
const DIFFICULTY = 3;
|
const fs = require('fs');
|
||||||
const MINE_RATE = 3000;
|
const process = require('process');
|
||||||
const INITIAL_BALANCE = 500;
|
|
||||||
const MINING_REWARD = 50;
|
|
||||||
|
|
||||||
module.exports = { DIFFICULTY, MINE_RATE, INITIAL_BALANCE, MINING_REWARD };
|
class Config {
|
||||||
|
constructor(location, disallow_arg_overide) {
|
||||||
|
//possible race if deleted after check, but we live with it I guess
|
||||||
|
|
||||||
|
const looking = location;
|
||||||
|
|
||||||
|
if (typeof disallow_arg_overide === undefined || disallow_arg_overide === null || !disallow_arg_overide) {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length > 0) {
|
||||||
|
looking = args[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(looking)) {
|
||||||
|
const rawSettings = fs.readFileSync(looking, 'utf8');
|
||||||
|
this.settings = JSON.parse(rawSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get(config) {
|
||||||
|
if (this.settings.hasOwnProperty(config.key)) {
|
||||||
|
const value = this.settings[config.key];
|
||||||
|
if (config.hasOwnProperty('transform')) {
|
||||||
|
return config.transform(value);
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return config.default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Config;
|
46
constants.js
Normal file
46
constants.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
const DIFFICULTY = 3;
|
||||||
|
const MINE_RATE = 3000;
|
||||||
|
const MINING_REWARD = 50;
|
||||||
|
|
||||||
|
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;
|
||||||
|
const DEFAULT_PORT_MINER_TX_SHARE = DEFAULT_PORT_MINER_BASE + 3;
|
||||||
|
const DEFAULT_PORT_MINER_TX_RECV = DEFAULT_PORT_MINER_BASE + 4;
|
||||||
|
|
||||||
|
|
||||||
|
const DEFAULT_PORT_WALLET_BASE = 4000;
|
||||||
|
const DEFAULT_PORT_WALLET_API = DEFAULT_PORT_WALLET_BASE + 1;
|
||||||
|
const DEFAULT_PORT_WALLET_CHAIN = DEFAULT_PORT_WALLET_BASE + 2;
|
||||||
|
|
||||||
|
const DEFAULT_PORT_BROKER_BASE = 5000;
|
||||||
|
const DEFAULT_PORT_BROKER_API = DEFAULT_PORT_BROKER_BASE + 1;
|
||||||
|
const DEFAULT_PORT_BROKER_CHAIN = DEFAULT_PORT_BROKER_BASE + 2;
|
||||||
|
const DEFAULT_PORT_BROKER_SENSOR_HANDSHAKE = DEFAULT_PORT_BROKER_BASE + 3;
|
||||||
|
const DEFAULT_PORT_BROKER_SENSOR_MQTT = DEFAULT_PORT_BROKER_BASE + 4;
|
||||||
|
const DEFAULT_PORT_BROKER_CLIENT_MQTT = DEFAULT_PORT_BROKER_BASE + 5;
|
||||||
|
|
||||||
|
const DEFAULT_PORT_SENSOR_BASE = 6000;
|
||||||
|
const DEFAULT_PORT_SENSOR_API = DEFAULT_PORT_SENSOR_BASE + 1;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
DIFFICULTY,
|
||||||
|
MINE_RATE,
|
||||||
|
MINING_REWARD,
|
||||||
|
|
||||||
|
DEFAULT_PORT_MINER_API,
|
||||||
|
DEFAULT_PORT_MINER_CHAIN,
|
||||||
|
DEFAULT_PORT_MINER_TX_SHARE,
|
||||||
|
DEFAULT_PORT_MINER_TX_RECV,
|
||||||
|
|
||||||
|
DEFAULT_PORT_WALLET_API,
|
||||||
|
DEFAULT_PORT_WALLET_CHAIN,
|
||||||
|
|
||||||
|
DEFAULT_PORT_BROKER_API,
|
||||||
|
DEFAULT_PORT_BROKER_CHAIN,
|
||||||
|
DEFAULT_PORT_BROKER_SENSOR_HANDSHAKE,
|
||||||
|
DEFAULT_PORT_BROKER_SENSOR_MQTT,
|
||||||
|
DEFAULT_PORT_BROKER_CLIENT_MQTT,
|
||||||
|
|
||||||
|
DEFAULT_PORT_SENSOR_API
|
||||||
|
};
|
|
@ -28,147 +28,145 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const LoggerPretty = require("@comunica/logger-pretty").LoggerPretty;
|
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const P2pServer = require('./p2p-server');
|
const P2pServer = require('../p2p-server');
|
||||||
const Wallet = require('../wallet');
|
const QueryEngine = require('@comunica/query-sparql-rdfjs').QueryEngine;
|
||||||
const TransactionPool = require('../wallet/transaction-pool');
|
|
||||||
const QueryEngine = require('@comunica/query-sparql').QueryEngine;
|
|
||||||
const ChainUtil = require('../chain-util');
|
|
||||||
|
|
||||||
const jsonld = require('jsonld');
|
const Blockchain = require('../blockchain/blockchain');
|
||||||
var mqtt = require('mqtt');
|
const Miner = require('./miner');
|
||||||
var aedes = require('aedes')(); /* aedes is a stream-based MQTT broker */
|
|
||||||
var MQTTserver = require('net').createServer(aedes.handle);
|
|
||||||
const fs = require('fs'); /* file system (fs) module allows you to work with
|
|
||||||
the file system on your computer*/
|
|
||||||
const multer = require('multer');/* Multer is a node.js middleware for handling multipart/form-data
|
|
||||||
, which is primarily used for uploading files.*/
|
|
||||||
'use strict';/* "use strict" is to indicate that the code should be executed in "strict mode".
|
'use strict';/* "use strict" is to indicate that the code should be executed in "strict mode".
|
||||||
With strict mode, you can not, for example, use undeclared variables.*/
|
With strict mode, you can not, for example, use undeclared variables.*/
|
||||||
|
|
||||||
const SETTINGS_STORAGE_LOCATION = "./settings.json";
|
const Config = require('../config');
|
||||||
const SETTING_MINER_PUBLIC_KEY = "miner-public-key";
|
|
||||||
const SETTING_WALLET_PRIVATE_KEY = "wallet-private-key";
|
|
||||||
|
|
||||||
var settings = {};
|
const Payment = require('../blockchain/payment');
|
||||||
|
const Integration = require('../blockchain/integration');
|
||||||
|
const SensorRegistration = require('../blockchain/sensor-registration');
|
||||||
|
const BrokerRegistration = require('../blockchain/broker-registration');
|
||||||
|
const Transaction = require('../blockchain/transaction');
|
||||||
|
|
||||||
//possible race if deleted after check, but we live with it I guess
|
const {
|
||||||
if (fs.existsSync(SETTINGS_STORAGE_LOCATION)) {
|
DEFAULT_PORT_MINER_API,
|
||||||
const rawSettings = fs.readFileSync(SETTINGS_STORAGE_LOCATION, 'utf8');
|
DEFAULT_PORT_MINER_CHAIN,
|
||||||
settings = JSON.parse(rawSettings);
|
DEFAULT_PORT_MINER_TX_SHARE,
|
||||||
|
DEFAULT_PORT_MINER_TX_RECV
|
||||||
|
} = require('../constants');
|
||||||
|
|
||||||
|
const CONFIGS_STORAGE_LOCATION = "./settings.json";
|
||||||
|
|
||||||
|
const config = new Config(CONFIGS_STORAGE_LOCATION);
|
||||||
|
|
||||||
|
const minerPublicKey = config.get({
|
||||||
|
key: "miner-public-key",
|
||||||
|
default: ""
|
||||||
|
});
|
||||||
|
const blockchainLocation = config.get({
|
||||||
|
key: "miner-blockchain-location",
|
||||||
|
default: "./miner_blockchain.json"
|
||||||
|
});
|
||||||
|
const chainServerPort = config.get({
|
||||||
|
key: "miner-chain-server-port",
|
||||||
|
default: DEFAULT_PORT_MINER_CHAIN
|
||||||
|
});
|
||||||
|
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 apiPort = config.get({
|
||||||
|
key: "miner-api-port",
|
||||||
|
default: DEFAULT_PORT_MINER_API
|
||||||
|
});
|
||||||
|
|
||||||
|
const blockchain = Blockchain.loadFromDisk(blockchainLocation);
|
||||||
|
|
||||||
|
function onMined(block) {
|
||||||
|
if (!blockchain.addBlock(block)) {
|
||||||
|
//invalid block, return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
//wallet init
|
|
||||||
var wallet = null;
|
|
||||||
|
|
||||||
if (settings.hasOwnProperty(SETTING_WALLET_PRIVATE_KEY)) {
|
|
||||||
wallet = new Wallet(ChainUtil.deserializeKeyPair(settings[SETTING_WALLET_PRIVATE_KEY]));
|
|
||||||
} else {
|
|
||||||
wallet = new Wallet(ChainUtil.genKeyPair());
|
|
||||||
}
|
|
||||||
|
|
||||||
//miner public key init
|
|
||||||
var minerPublicKey = null;
|
|
||||||
|
|
||||||
if (settings.hasOwnProperty(SETTING_MINER_PUBLIC_KEY)) {
|
|
||||||
minerPublicKey = settings[SETTING_MINER_PUBLIC_KEY];
|
|
||||||
} else {
|
|
||||||
minerPublicKey = wallet.publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tp = new TransactionPool();
|
|
||||||
const p2pServer = new P2pServer(tp, minerPublicKey, './persist_block_chain.json');
|
|
||||||
const myEngine = new QueryEngine();
|
const myEngine = new QueryEngine();
|
||||||
|
|
||||||
function getBlockchain() {
|
|
||||||
return p2pServer.blockchain;
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
//initialising a local storage for storing metadata file initially before storing it in the tripple store
|
// initialising the HTTP PORT to listen
|
||||||
const storage = multer.diskStorage({
|
app.listen(apiPort, () => console.log(`Listening on port ${apiPort}`));
|
||||||
destination: function(req, file, cb) {
|
|
||||||
cb(null, './uploads/');
|
|
||||||
},
|
|
||||||
filename: function(req, file, cb) {
|
|
||||||
cb(null, new Date().toISOString() + file.originalname);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//filtering the type of uploaded Metadata files
|
|
||||||
const fileFilter = (req, file, cb) => {
|
|
||||||
// reject a file
|
|
||||||
if (file.mimetype === 'application/json' || file.mimetype === 'text/plain' || file.mimetype === 'turtle') {
|
|
||||||
cb(null, true);
|
|
||||||
} else {
|
|
||||||
cb(null, false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// defining a storage and setup limits for storing metadata file initially before storing it in the tripple store
|
|
||||||
const upload = multer({
|
|
||||||
storage: storage,
|
|
||||||
limits: {
|
|
||||||
fileSize: 1024 * 1024 * 5
|
|
||||||
},
|
|
||||||
fileFilter: fileFilter
|
|
||||||
});
|
|
||||||
|
|
||||||
// innitialising the HTTP PORT to listen
|
|
||||||
const port = process.env.HTTP_PORT || 3000;
|
|
||||||
app.listen(port, () => console.log(`Listening on port ${port}`));
|
|
||||||
p2pServer.listen();
|
|
||||||
|
|
||||||
//aedes mqtt server intialization
|
//aedes mqtt server intialization
|
||||||
const MQTTport = process.env.MQTT_PORT || 1882;
|
//const MQTTport = process.env.MQTT_PORT || 1882;
|
||||||
MQTTserver.listen(MQTTport, function () {
|
//MQTTserver.listen(MQTTport, function () {
|
||||||
console.log('MQTTserver listening on port', MQTTport)
|
// console.log('MQTTserver listening on port', MQTTport)
|
||||||
})
|
//})
|
||||||
|
|
||||||
app.use('/uploads', express.static('uploads')); // to store uploaded metadata to '/uploads' folder
|
app.use(bodyParser.json());
|
||||||
app.use(bodyParser.json()); //
|
|
||||||
|
|
||||||
//API HELPERS
|
|
||||||
function
|
|
||||||
|
|
||||||
// GET APIs
|
// GET APIs
|
||||||
app.get('/blocks', (req, res) => {
|
app.get('/blocks', (req, res) => {
|
||||||
res.json(bc.chain);
|
res.json(blockchain.chain);
|
||||||
});
|
|
||||||
///////////////
|
|
||||||
app.get('/MetaDataTransactions', (req, res) => {
|
|
||||||
res.json(tp.metadataS);
|
|
||||||
});
|
|
||||||
///////////////
|
|
||||||
app.get('/PaymentTransactions', (req, res) => {
|
|
||||||
res.json(tp.transactions);
|
|
||||||
});
|
});
|
||||||
///////////////
|
///////////////
|
||||||
app.get('/Transactions', (req, res) => {
|
app.get('/Transactions', (req, res) => {
|
||||||
res.json(tp);
|
res.json(miner.txs);
|
||||||
});
|
});
|
||||||
///////////////
|
|
||||||
//app.get('/mine-transactions', (req, res) => {
|
|
||||||
// const block = miner.mine();
|
|
||||||
// console.log(`New block added: ${block.toString()}`);
|
|
||||||
// res.redirect('/blocks');
|
|
||||||
// // res.json("Block mined");
|
|
||||||
//});
|
|
||||||
///////////////
|
|
||||||
app.get('/public-key', (req, res) => {
|
app.get('/public-key', (req, res) => {
|
||||||
res.json({ publicKey: wallet.publicKey });
|
res.json(minerPublicKey);
|
||||||
});
|
});
|
||||||
///////////////
|
///////////////
|
||||||
|
app.get('/MinerBalance', (req, res) => {
|
||||||
|
const balance = blockchain.getBalanceCopy(minerPublicKey);
|
||||||
|
res.json(balance);
|
||||||
|
});
|
||||||
app.get('/Balance', (req, res) => {
|
app.get('/Balance', (req, res) => {
|
||||||
const balance = getBlockchain().getBalanceCopy(wallet.publicKey);
|
const balance = blockchain.getBalanceCopy(req.body.publicKey);
|
||||||
res.json({ Balance: balance.balance });
|
res.json(balance);
|
||||||
});
|
});
|
||||||
app.get('/Balances', (req, res) => {
|
app.get('/Balances', (req, res) => {
|
||||||
const balances = getBlockchain().balances;
|
const balances = blockchain.balances;
|
||||||
res.json(balances);
|
res.json(balances);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -177,128 +175,51 @@ app.get('/Balances', (req, res) => {
|
||||||
app.get('/quads', (req, res) => {
|
app.get('/quads', (req, res) => {
|
||||||
//for (const quad of store)
|
//for (const quad of store)
|
||||||
//console.log(quad);
|
//console.log(quad);
|
||||||
res.json(store);
|
res.json(blockchain.stores);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/IoTdeviceRegistration', (req, res)=> {
|
app.get('/brokers', (req, res) => {
|
||||||
fs.readdir('./uploads', function(err, files) {
|
res.json(blockchain.brokers);
|
||||||
//console.log(files[files.length-2]);
|
|
||||||
var FileName = files[files.length-2];
|
|
||||||
let rawdata = fs.readFileSync(`./uploads/${FileName}`);
|
|
||||||
let SenShaMartDesc = JSON.parse(rawdata);
|
|
||||||
/* the following piece of code is used to genrate JSON object out of name-value pairs submitted
|
|
||||||
let SenShaMartExtNames = ['Name','Geo' ,'IP_URL' , 'Topic_Token', 'Permission', 'RequestDetail',
|
|
||||||
'OrgOwner', 'DepOwner','PrsnOwner', 'PaymentPerKbyte',
|
|
||||||
'PaymentPerMinute','Protocol', 'MessageAttributes', 'Interval',
|
|
||||||
'FurtherDetails']
|
|
||||||
let SenShaMartExtValues = [Name,Geo ,IP_URL , Topic_Token, Permission, RequestDetail,
|
|
||||||
OrgOwner, DepOwner,PrsnOwner, PaymentPerKbyte,
|
|
||||||
PaymentPerMinute,Protocol, MessageAttributes, Interval,
|
|
||||||
FurtherDetails]
|
|
||||||
let SenSHaMArtExt = {};
|
|
||||||
for (let i =0; i <SenShaMartExtNames.length; i++){
|
|
||||||
SenSHaMArtExt[`${SenShaMartExtNames[i]}`]= SenShaMartExtValues[i]
|
|
||||||
|
|
||||||
}
|
|
||||||
//let SenShaMartOnt = SSNmetadata;
|
|
||||||
//SenShaMartOnt.push(SenSHaMArtExt); */
|
|
||||||
//console.log(SenShaMartDesc);
|
|
||||||
jsonld.toRDF(SenShaMartDesc, {format: 'application/n-quads'},
|
|
||||||
(err, nquads) => {
|
|
||||||
//console.log(nquads)
|
|
||||||
var metadata = wallet.createMetadata(
|
|
||||||
nquads);
|
|
||||||
p2pServer.newMetadata(metadata);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
res.json("MetadataTransactionCreated");
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/storeSize', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
size: getBlockchain().store.size
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
app.get('/sensors', (req, res) => {
|
||||||
// POST APIs
|
res.json(blockchain.sensors);
|
||||||
|
});
|
||||||
|
|
||||||
//this doesn't work well with the continious miner
|
|
||||||
//app.post('/mine', (req, res) => {
|
|
||||||
// const block = bc.addBlock(req.body.data);
|
|
||||||
// console.log(`New block added: ${block.toString()}`);
|
|
||||||
|
|
||||||
// p2pServer.newBlock(block);
|
app.get('/ChainServer/sockets', (req, res) => {
|
||||||
|
res.json(chainServer.sockets);
|
||||||
|
});
|
||||||
|
app.post('/ChainServer/connect', (req, res) => {
|
||||||
|
chainServer.connect(req.body.url);
|
||||||
|
res.json("Connecting");
|
||||||
|
});
|
||||||
|
|
||||||
// res.redirect('/blocks');
|
function newTransaction(res, body, type) {
|
||||||
//});
|
const verifyRes = type.verify(body);
|
||||||
///////////////
|
if (!verifyRes.result) {
|
||||||
app.post('/PaymentTransaction', (req, res) => {
|
res.json(`Failed to verify ${type.name}: ${verifyRes.reason}`);
|
||||||
if (!req.body.hasOwnProperty('recpient')) {
|
|
||||||
res.json({
|
|
||||||
result: false,
|
|
||||||
reason: "Missing \"recipient\" in body"
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!req.body.hasOwnProperty('amount')) {
|
|
||||||
res.json({
|
|
||||||
result: false,
|
|
||||||
reason: "Missing \"amount\" in body"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { recipient, amount } = req.body;
|
|
||||||
const transaction = wallet.createTransaction(recipient, amount, getBlockchain());
|
|
||||||
if (transaction === null) {
|
|
||||||
res.json("Couldn't create transaction");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
p2pServer.newTransaction(transaction);
|
|
||||||
res.json(transaction);
|
|
||||||
});
|
|
||||||
|
|
||||||
///////////////
|
miner.addTransaction(new Transaction(body, type));
|
||||||
app.post('/IoTdevicePaymentTransaction', (req, res) => {
|
res.json("Added to pool");
|
||||||
if (!req.body.hasOwnProperty("Recipient_payment_address")) {
|
}
|
||||||
req.json({
|
|
||||||
result: false,
|
app.post('/Payment', (req, res) => {
|
||||||
reason: "Missing \"Recipient_
|
newTransaction(res, req.body, Payment);
|
||||||
}
|
|
||||||
}
|
|
||||||
const { Recipient_payment_address, Amount_of_money, Payment_method,
|
|
||||||
Further_details} = req.body;
|
|
||||||
if (Payment_method == "SensorCoin") {
|
|
||||||
//create coin transaction doesn't exist yet
|
|
||||||
const PaymentTransaction = wallet.createCoinTransaction(
|
|
||||||
Recipient_payment_address, Amount_of_money, bc, tp);
|
|
||||||
p2pServer.broadcastCoinTransaction(PaymentTransaction);
|
|
||||||
res.json("PaymentTransactionCreated");
|
|
||||||
}
|
|
||||||
else if (Payment_method == "Bitcoin") {
|
|
||||||
res.redirect('/BitcoinTransaction')
|
|
||||||
}
|
|
||||||
else if (Payment_method == "PayPal") {
|
|
||||||
res.redirect('/PayPalTransaction')
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
///////////////
|
|
||||||
app.post("/UploadMetafile", upload.single('file'), (req, res) => {
|
app.post('/Integration', (req, res) => {
|
||||||
// recipient: req.body.recipient,
|
newTransaction(res, req.body, Integration);
|
||||||
// amount : req.body.amount,
|
|
||||||
// const Geo = req.body.Geo;
|
|
||||||
// const IPSO = req.body.IPSO;
|
|
||||||
// const Type = req.body.Type;
|
|
||||||
// const Permission = req.body.Permission;
|
|
||||||
// const OrgOwner = req.body.OrgOwner;
|
|
||||||
const file = req.file;
|
|
||||||
//file : req.body.file
|
|
||||||
|
|
||||||
res.status(201).json({
|
|
||||||
message: 'Uploading Metadata was successful',
|
|
||||||
MetadataFile : file
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/BrokerRegistration', (req, res) => {
|
||||||
|
newTransaction(res, req.body, BrokerRegistration);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/SensorRegistration', (req, res) => {
|
||||||
|
newTransaction(res, req.body, SensorRegistration);
|
||||||
});
|
});
|
||||||
|
|
||||||
/////////////////////
|
/////////////////////
|
||||||
|
@ -310,16 +231,17 @@ app.post('/sparql', (req, res) => {
|
||||||
const bindingsStream = await myEngine.queryBindings(
|
const bindingsStream = await myEngine.queryBindings(
|
||||||
req.body.query,
|
req.body.query,
|
||||||
{
|
{
|
||||||
log: new LoggerPretty({ level: 'trace' }),
|
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
sources: [getBlockchain().store]
|
sources: blockchain.stores
|
||||||
});
|
});
|
||||||
bindingsStream.on('data', (binding) => {
|
bindingsStream.on('data', (binding) => {
|
||||||
console.log(binding.toString());
|
const pushing = {};
|
||||||
result.push(binding);
|
for (const [key, value] of binding) {
|
||||||
|
pushing[key.value] = value.value;
|
||||||
|
}
|
||||||
|
result.push(pushing);
|
||||||
});
|
});
|
||||||
bindingsStream.on('end', () => {
|
bindingsStream.on('end', () => {
|
||||||
console.log('end');
|
|
||||||
res.json(JSON.stringify(result));
|
res.json(JSON.stringify(result));
|
||||||
});
|
});
|
||||||
bindingsStream.on('error', (err) => {
|
bindingsStream.on('error', (err) => {
|
||||||
|
@ -335,7 +257,7 @@ app.post('/sparql', (req, res) => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////Integration///////////////////////////////////////////////////////////
|
/* ///////////////////////////////////////////////////////////Integration///////////////////////////////////////////////////////////
|
||||||
DistributedBrokers = ["mqtt.eclipse.org", "test.mosquitto.org","broker.hivemq.com"];
|
DistributedBrokers = ["mqtt.eclipse.org", "test.mosquitto.org","broker.hivemq.com"];
|
||||||
DistributedBrokersPorts = [1883,1883,1883];
|
DistributedBrokersPorts = [1883,1883,1883];
|
||||||
function makeTopic(length) {
|
function makeTopic(length) {
|
||||||
|
@ -367,9 +289,10 @@ app.post('/IoTdeviceIntegration-Control', (req, res) => {
|
||||||
MassagiesRecived.push(false);
|
MassagiesRecived.push(false);
|
||||||
data =bc.chain.map (a => a.data);
|
data =bc.chain.map (a => a.data);
|
||||||
MetaANDTransFound = false;
|
MetaANDTransFound = false;
|
||||||
for (let j= data.length-1; j>0; j-- ){/** this for loop load
|
for (let j= data.length-1; j>0; j-- ){
|
||||||
Blockchain and search for metadata and payment transaction that match
|
//this for loop load
|
||||||
the provided MetadataID and TransactionID */
|
//Blockchain and search for metadata and payment transaction that match
|
||||||
|
//the provided MetadataID and TransactionID
|
||||||
var metadata = data[j][1];
|
var metadata = data[j][1];
|
||||||
var transaction = data [j][0];
|
var transaction = data [j][0];
|
||||||
var pickedMetadata = lodash.find(metadata, x=>
|
var pickedMetadata = lodash.find(metadata, x=>
|
||||||
|
@ -404,11 +327,10 @@ app.post('/IoTdeviceIntegration-Control', (req, res) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.get ('/IoTdataObtainingAndForward', (req, res) => {
|
app.get ('/IoTdataObtainingAndForward', (req, res) => {
|
||||||
console.log (`transaction of IoT Application ${i} approved`)
|
console.log (`transaction of IoT Application ${i} approved`)
|
||||||
BrokerRandomNumber = (Math.floor(
|
BrokerRandomNumber = (Math.floor(
|
||||||
Math.random()*DistributedBrokers.length)+1)-1 /** collect
|
Math.random()*DistributedBrokers.length)+1)-1 //collect a random number to select a random broker
|
||||||
a random number to select a random broker*/
|
|
||||||
MiddlewareBroker = DistributedBrokers[BrokerRandomNumber];
|
MiddlewareBroker = DistributedBrokers[BrokerRandomNumber];
|
||||||
MiddlewareTopic = makeTopic(5);// generate random topic
|
MiddlewareTopic = makeTopic(5);// generate random topic
|
||||||
MiddlewarePort = DistributedBrokersPorts[BrokerRandomNumber];
|
MiddlewarePort = DistributedBrokersPorts[BrokerRandomNumber];
|
||||||
|
@ -416,8 +338,7 @@ app.post('/IoTdeviceIntegration-Control', (req, res) => {
|
||||||
configurationMessage = {"host/broker":MiddlewareBroker,
|
configurationMessage = {"host/broker":MiddlewareBroker,
|
||||||
"topic":MiddlewareTopic,
|
"topic":MiddlewareTopic,
|
||||||
"port":MiddlewarePort,
|
"port":MiddlewarePort,
|
||||||
"duration":Duration} /** add pk of the node
|
"duration":Duration} //add pk of the node connect to the IoT device and send the configuration massage
|
||||||
connect to the IoT device and send the configuration massage*/
|
|
||||||
var IoTDeviceClient = mqtt.connect(IoTDeviceBroker);
|
var IoTDeviceClient = mqtt.connect(IoTDeviceBroker);
|
||||||
MiddlewareClients.push(mqtt.connect(`mqtt://${MiddlewareBroker}`))
|
MiddlewareClients.push(mqtt.connect(`mqtt://${MiddlewareBroker}`))
|
||||||
var MiddlewareClient = MiddlewareClients[i]
|
var MiddlewareClient = MiddlewareClients[i]
|
||||||
|
@ -432,8 +353,7 @@ app.post('/IoTdeviceIntegration-Control', (req, res) => {
|
||||||
IoTDeviceClient.on("message", (topic, message) => {
|
IoTDeviceClient.on("message", (topic, message) => {
|
||||||
console.log(message.toString())
|
console.log(message.toString())
|
||||||
IoTDeviceClient.end(true)});
|
IoTDeviceClient.end(true)});
|
||||||
/** connect the randomly choosed mqtt middlware broker to
|
//connect the randomly choosed mqtt middlware broker to listen to the transmitted massagies
|
||||||
* listen to the transmitted massagies */
|
|
||||||
MiddlewareClient.on("connect", ack => {
|
MiddlewareClient.on("connect", ack => {
|
||||||
console.log("connected!");
|
console.log("connected!");
|
||||||
console.log(MiddlewareBroker)
|
console.log(MiddlewareBroker)
|
||||||
|
@ -441,21 +361,19 @@ app.post('/IoTdeviceIntegration-Control', (req, res) => {
|
||||||
MiddlewareClient.subscribe(MiddlewareTopic, err => {
|
MiddlewareClient.subscribe(MiddlewareTopic, err => {
|
||||||
console.log(err); });});
|
console.log(err); });});
|
||||||
MiddlewareTracking.push({index:i,
|
MiddlewareTracking.push({index:i,
|
||||||
TrackingTopic:MiddlewareTopic})/** this used to track the connection
|
TrackingTopic:MiddlewareTopic}) //this used to track the connection in case there are multiple conection at the same time
|
||||||
in case there are multiple conection at the same time */
|
MiddlewareClient.on("message", (topic, message) => {
|
||||||
MiddlewareClient.on("message", (topic, message) => {/** call back,
|
//call back,
|
||||||
will run each time a massage recived, I did it in a way if there are
|
//will run each time a massage recived, I did it in a way if there are
|
||||||
multiple connections, it will run for all the massagies, then truck the
|
//multiple connections, it will run for all the massagies, then truck the
|
||||||
massagies by MiddlwareTracking Array */
|
//massagies by MiddlwareTracking Array
|
||||||
var MiddlewareFound = MiddlewareTracking.filter(function(item) {
|
var MiddlewareFound = MiddlewareTracking.filter(function(item) {
|
||||||
return item.TrackingTopic == topic;});
|
return item.TrackingTopic == topic;});
|
||||||
console.log(MiddlewareFound);
|
console.log(MiddlewareFound);
|
||||||
console.log(message.toString());
|
console.log(message.toString());
|
||||||
MiddlewareIndex = MiddlewareFound[0].index/** this is the index of
|
MiddlewareIndex = MiddlewareFound[0].index// this is the index of the connection or the Middleware
|
||||||
the connection or the Middleware*/
|
|
||||||
console.log(MiddlewareIndex)
|
console.log(MiddlewareIndex)
|
||||||
MassageCounter[MiddlewareIndex]++;/** this used to track the number
|
MassageCounter[MiddlewareIndex]++;//this used to track the number of recived massagies of each connection
|
||||||
of recived massagies of each connection */
|
|
||||||
console.log(Date.now()-StartSending[MiddlewareIndex])
|
console.log(Date.now()-StartSending[MiddlewareIndex])
|
||||||
if (Date.now() - StartSending[MiddlewareIndex] >=
|
if (Date.now() - StartSending[MiddlewareIndex] >=
|
||||||
(Durations[MiddlewareIndex]*1000)
|
(Durations[MiddlewareIndex]*1000)
|
||||||
|
@ -463,18 +381,17 @@ app.post('/IoTdeviceIntegration-Control', (req, res) => {
|
||||||
console.log("sending time finished")
|
console.log("sending time finished")
|
||||||
if (MassageCounter[MiddlewareIndex] > 0.75*(
|
if (MassageCounter[MiddlewareIndex] > 0.75*(
|
||||||
Durations[MiddlewareIndex]/Intervals[MiddlewareIndex])
|
Durations[MiddlewareIndex]/Intervals[MiddlewareIndex])
|
||||||
){/** which means most of massagies have been sent */
|
){// which means most of massagies have been sent
|
||||||
console.log("massages recived")
|
console.log("massages recived")
|
||||||
MassagiesRecived[MiddlewareIndex] = true;}
|
MassagiesRecived[MiddlewareIndex] = true;}
|
||||||
if (MassagiesRecived[MiddlewareIndex]){/** if massagies recived,
|
if (MassagiesRecived[MiddlewareIndex]){// if massagies recived, pay the 10% as service fees
|
||||||
pay the 10% as service fees */
|
|
||||||
const PaymentTransaction = wallet.createPaymentTransaction(
|
const PaymentTransaction = wallet.createPaymentTransaction(
|
||||||
NodeAddress,(0.1*paymentAmount[MiddlewareIndex]) , bc, tp);
|
NodeAddress,(0.1*paymentAmount[MiddlewareIndex]) , bc, tp);
|
||||||
p2pServer.broadcastPaymentTransaction(PaymentTransaction);
|
p2pServer.broadcastPaymentTransaction(PaymentTransaction);
|
||||||
console.log("amount paid to the IoT device")
|
console.log("amount paid to the IoT device")
|
||||||
console.log(MiddlewareIndex)
|
console.log(MiddlewareIndex)
|
||||||
MiddlewareClient = MiddlewareClients[MiddlewareIndex];
|
MiddlewareClient = MiddlewareClients[MiddlewareIndex];
|
||||||
/** disconnect the middleware mqtt broker */
|
//disconnect the middleware mqtt broker
|
||||||
MiddlewareClient.end(true)}
|
MiddlewareClient.end(true)}
|
||||||
else{// if massagies not recived, pay the IoT application back
|
else{// if massagies not recived, pay the IoT application back
|
||||||
res.redirect('/IoTapplicationCompensationTransaction')}};});
|
res.redirect('/IoTapplicationCompensationTransaction')}};});
|
||||||
|
@ -686,4 +603,4 @@ i++;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});*/
|
169
miner/miner.js
Normal file
169
miner/miner.js
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
const Block = require('../blockchain/block');
|
||||||
|
|
||||||
|
const Payment = require('../blockchain/payment');
|
||||||
|
const Integration = require('../blockchain/integration');
|
||||||
|
const SensorRegistration = require('../blockchain/sensor-registration');
|
||||||
|
const BrokerRegistration = require('../blockchain/broker-registration');
|
||||||
|
|
||||||
|
const ITERATIONS = 1;
|
||||||
|
|
||||||
|
const STATE_RUNNING = 0;
|
||||||
|
const STATE_INTERRUPTED = 1;
|
||||||
|
|
||||||
|
function mine(miner) {
|
||||||
|
if (miner.state !== STATE_RUNNING) {
|
||||||
|
this.startMine();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const difficulty = Block.adjustDifficulty(miner.lastBlock, timestamp);
|
||||||
|
|
||||||
|
for (let i = 0; i < ITERATIONS; ++i) {
|
||||||
|
const hash = Block.hash(
|
||||||
|
timestamp,
|
||||||
|
miner.lastBlock.hash,
|
||||||
|
miner.reward,
|
||||||
|
miner.txs.payments.mining,
|
||||||
|
miner.txs.sensorRegistrations.mining,
|
||||||
|
miner.txs.brokerRegistrations.mining,
|
||||||
|
miner.txs.integrations.mining,
|
||||||
|
miner.nonce,
|
||||||
|
difficulty);
|
||||||
|
|
||||||
|
if (hash.substring(0, difficulty) === '0'.repeat(difficulty)) {
|
||||||
|
//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(
|
||||||
|
timestamp,
|
||||||
|
miner.lastBlock.hash,
|
||||||
|
hash,
|
||||||
|
miner.reward,
|
||||||
|
miner.txs.payments.mining,
|
||||||
|
miner.txs.sensorRegistrations.mining,
|
||||||
|
miner.txs.brokerRegistrations.mining,
|
||||||
|
miner.txs.integrations.mining,
|
||||||
|
miner.nonce,
|
||||||
|
difficulty));
|
||||||
|
miner.state = STATE_INTERRUPTED;
|
||||||
|
setImmediate(() => { startMine(miner) });
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
//failure
|
||||||
|
miner.nonce++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setImmediate(() => { mine(miner) });
|
||||||
|
}
|
||||||
|
|
||||||
|
function startMine(miner) {
|
||||||
|
//only continue if state is waiting or restarting
|
||||||
|
if (miner.state !== STATE_INTERRUPTED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
miner.minedStartTime = process.hrtime.bigint();
|
||||||
|
|
||||||
|
//TODO make sure these transactions actually work as a collective instead of individually
|
||||||
|
miner.txs.payments.mining = [...miner.txs.payments.pool];
|
||||||
|
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.nonce = 0;
|
||||||
|
miner.state = STATE_RUNNING;
|
||||||
|
|
||||||
|
mine(miner);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findTx(tx) {
|
||||||
|
return t => t.input === tx.input && t.counter === tx.counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFromBlock(miner, txs, blockTxs) {
|
||||||
|
for (const tx of blockTxs) {
|
||||||
|
const foundIndex = txs.pool.findIndex(findTx(tx));
|
||||||
|
|
||||||
|
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;
|
||||||
|
this.state = STATE_INTERRUPTED;
|
||||||
|
this.lastBlock = null;
|
||||||
|
this.reward = reward;
|
||||||
|
|
||||||
|
this.minedStartTime = null;
|
||||||
|
|
||||||
|
this.txs = {
|
||||||
|
payments: {
|
||||||
|
pool: [],
|
||||||
|
mining: []
|
||||||
|
},
|
||||||
|
integrations: {
|
||||||
|
pool: [],
|
||||||
|
mining: []
|
||||||
|
},
|
||||||
|
sensorRegistrations: {
|
||||||
|
pool: [],
|
||||||
|
mining: []
|
||||||
|
},
|
||||||
|
brokerRegistrations: {
|
||||||
|
pool: [],
|
||||||
|
mining: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
startMine(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTransaction(tx) {
|
||||||
|
const verifyRes = tx.type.verify(tx.transaction);
|
||||||
|
if (!verifyRes.result) {
|
||||||
|
console.log("Couldn't add tx to miner, tx couldn't be verified: " + verifyRes.reason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let txs = null;
|
||||||
|
|
||||||
|
switch (tx.type) {
|
||||||
|
case Payment: txs = this.txs.payments; break;
|
||||||
|
case Integration: txs = this.txs.integrations; break;
|
||||||
|
case SensorRegistration: txs = this.txs.sensorRegistrations; break;
|
||||||
|
case BrokerRegistration: txs = this.txs.brokerRegistrations; break;
|
||||||
|
default: throw new Error(`unknown tx type: ${tx.type.name()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundIndex = txs.pool.findIndex(findTx(tx.transaction));
|
||||||
|
|
||||||
|
if (foundIndex !== -1) {
|
||||||
|
txs.pool[foundIndex] = tx.transaction;
|
||||||
|
if (txs.mining.some(findTx(tx.transaction))) {
|
||||||
|
this.state = STATE_INTERRUPTED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
txs.pool.push(tx.transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNewBlock(block) {
|
||||||
|
clearFromBlock(this, this.txs.payments, Block.getPayments(block));
|
||||||
|
clearFromBlock(this, this.txs.integrations, Block.getIntegrations(block));
|
||||||
|
clearFromBlock(this, this.txs.sensorRegistrations, Block.getSensorRegistrations(block));
|
||||||
|
clearFromBlock(this, this.txs.brokerRegistrations, Block.getBrokerRegistrations(block));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Miner;
|
||||||
|
|
164
p2p-server.js
Normal file
164
p2p-server.js
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
const Websocket = require('ws');
|
||||||
|
|
||||||
|
function messageHandler(p2pServer, socket) {
|
||||||
|
socket.on('message', (data) => {
|
||||||
|
p2pServer.onData(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConnection(p2pServer, socket) {
|
||||||
|
p2pServer.sockets.push(socket);
|
||||||
|
console.log(`${p2pServer.name} had a socket connect`);
|
||||||
|
|
||||||
|
p2pServer.onConnect(socket);
|
||||||
|
|
||||||
|
messageHandler(p2pServer, socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DEAD CODE STORAGE
|
||||||
|
*
|
||||||
|
*
|
||||||
|
|
||||||
|
this.miner = new Miner(this.blockchain, rewardPublicKey, this);
|
||||||
|
*
|
||||||
|
* socket.on('message', message => {
|
||||||
|
const data = JSON.parse(message);
|
||||||
|
switch (data.type) {
|
||||||
|
case MESSAGE_TYPES.chain:
|
||||||
|
newChain(p2pServer, data.data);
|
||||||
|
break;
|
||||||
|
case MESSAGE_TYPES.payment:
|
||||||
|
this.newTransaction(new Transaction(data.transaction, Payment), false);
|
||||||
|
break;
|
||||||
|
case MESSAGE_TYPES.integration:
|
||||||
|
this.newTransaction(new Transaction(data.transaction, Integration), false);
|
||||||
|
break;
|
||||||
|
case MESSAGE_TYPES.sensorRegistration:
|
||||||
|
this.newTransaction(new Transaction(data.transaction, SensorRegistration), false);
|
||||||
|
break;
|
||||||
|
case MESSAGE_TYPES.brokerRegistration:
|
||||||
|
this.newTransaction(new Transaction(data.transaction, BrokerRegistration), false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(`Unknown type '${data.type}' recved from socket ${socket}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*
|
||||||
|
* const MESSAGE_TYPES = {
|
||||||
|
chain: 'CHAIN',
|
||||||
|
payment: 'PAYMENT',
|
||||||
|
integration: 'INTEGRATION',
|
||||||
|
sensorRegistration: 'SENSORREGISTRATION',
|
||||||
|
brokerRegistration: 'BROKERREGISTRATION'
|
||||||
|
};
|
||||||
|
|
||||||
|
console.error(`Couldn't persist chain, aborting: ${err}`);
|
||||||
|
process.exit(-1);
|
||||||
|
|
||||||
|
function newChain(p2pServer, chain, persist) {
|
||||||
|
const replaceResult = p2pServer.blockchain.replaceChain(chain);
|
||||||
|
if (!replaceResult.result) {
|
||||||
|
//failed to replace
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < replaceResult.chainDifference; i++) {
|
||||||
|
p2pServer.miner.onNewBlock(p2pServer.blockchain.chain[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
p2pServer.miner.interrupt();
|
||||||
|
|
||||||
|
if (typeof persist === "undefined" || persist) {
|
||||||
|
persistChain(p2pServer, p2pServer.blockchain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncChains(p2pServer) {
|
||||||
|
const serializedBlockchain = p2pServer.blockchain.serialize();
|
||||||
|
|
||||||
|
for (const socket of p2pServer.sockets) {
|
||||||
|
send(socket, serializedBlockchain, MESSAGE_TYPES.chain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function broadcastTransaction(p2pServer, tx) {
|
||||||
|
let type = null;
|
||||||
|
|
||||||
|
switch (tx.type) {
|
||||||
|
case Payment: type = MESSAGE_TYPES.payment; break;
|
||||||
|
case Integration: type = MESSAGE_TYPES.integration; break;
|
||||||
|
case BrokerRegistration: type = MESSAGE_TYPES.brokerRegistration; break;
|
||||||
|
case SensorRegistration: type = MESSAGE_TYPES.sensorRegistration; break;
|
||||||
|
default: throw Error("Unknown tx type");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const socket of p2pServer.sockets) {
|
||||||
|
send(socket, tx.transaction, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newTransaction(transaction, broadcast) {
|
||||||
|
if (!transaction.verify(transaction.transaction)) {
|
||||||
|
console.log("Couldn't add transaction to p2pServer, couldn't verify");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.miner.addTransaction(transaction);
|
||||||
|
|
||||||
|
if (broadcast === undefined || broadcast) {
|
||||||
|
broadcastTransaction(this, transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blockMined(block) {
|
||||||
|
if (!this.blockchain.addBlock(block)) {
|
||||||
|
//invalid block, return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.miner.onNewBlock(block);
|
||||||
|
persistChain(this, this.blockchain);
|
||||||
|
syncChains(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class P2pServer {
|
||||||
|
constructor(name) {
|
||||||
|
this.name = name;
|
||||||
|
this.sockets = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
start(port, peers, onConnect, onData) {
|
||||||
|
this.port = port;
|
||||||
|
this.onConnect = onConnect;
|
||||||
|
this.onData = onData;
|
||||||
|
this.server = new Websocket.Server({ port: port });
|
||||||
|
this.server.on('connection', socket => onConnection(this, socket));
|
||||||
|
|
||||||
|
for (const peer of peers) {
|
||||||
|
this.connect(peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Listening for peer-to-peer connections on: ${port}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(to) {
|
||||||
|
const socket = new Websocket(to);
|
||||||
|
|
||||||
|
socket.on('open', () => onConnection(this, socket));
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast(data) {
|
||||||
|
for (const socket of this.sockets) {
|
||||||
|
P2pServer.send(socket, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static send(socket, data) {
|
||||||
|
socket.send(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = P2pServer;
|
|
@ -2,12 +2,12 @@
|
||||||
"name": "senshamartproject",
|
"name": "senshamartproject",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A novel Marketplace for sharing sensors in decentralized environment",
|
"description": "A novel Marketplace for sharing sensors in decentralized environment",
|
||||||
"main": "app/index.js",
|
"main": "miner/miner-app.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest --watchAll",
|
"test": "jest --watchAll",
|
||||||
"dev-test": "nodemon dev-test",
|
"dev-test": "nodemon dev-test",
|
||||||
"start": "node ./app",
|
"start": "node ./miner/miner-app",
|
||||||
"dev": "nodemon ./app"
|
"dev": "nodemon ./miner/miner-app"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"testEnvironment": "node"
|
"testEnvironment": "node"
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
"nodemon": "^2.0.20"
|
"nodemon": "^2.0.20"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@comunica/query-sparql": "^2.5.2",
|
"@comunica/query-sparql-rdfjs": "^2.5.2",
|
||||||
"aedes": "^0.42.5",
|
"aedes": "^0.42.5",
|
||||||
"body-parser": "^1.20.1",
|
"body-parser": "^1.20.1",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
|
|
54
sensor/sensor-app.js
Normal file
54
sensor/sensor-app.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
//SENSOR
|
||||||
|
const express = require('express');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
|
||||||
|
const Config = require('../config');
|
||||||
|
const ChainUtil = require('../chain-util');
|
||||||
|
const Sensor = require('./sensor');
|
||||||
|
|
||||||
|
const {
|
||||||
|
DEFAULT_PORT_BROKER_SENSOR_HANDSHAKE,
|
||||||
|
DEFAULT_PORT_SENSOR_API,
|
||||||
|
} = require('../constants');
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const CONFIGS_STORAGE_LOCATION = "./settings.json";
|
||||||
|
|
||||||
|
const config = new Config(CONFIGS_STORAGE_LOCATION);
|
||||||
|
|
||||||
|
const keyPair = config.get({
|
||||||
|
key: "sensor-keypair",
|
||||||
|
default: ChainUtil.genKeyPair(),
|
||||||
|
transform: ChainUtil.deserializeKeyPair
|
||||||
|
});
|
||||||
|
const apiPort = config.get({
|
||||||
|
key: "sensor-api-port",
|
||||||
|
default: DEFAULT_PORT_SENSOR_API
|
||||||
|
});
|
||||||
|
const sensorId = config.get({
|
||||||
|
key: "sensor-id",
|
||||||
|
default: "Test sensor"
|
||||||
|
});
|
||||||
|
const brokerLocation = config.get({
|
||||||
|
key: "sensor-broker-location",
|
||||||
|
default: "ws://127.0.0.1:" + DEFAULT_PORT_BROKER_SENSOR_HANDSHAKE
|
||||||
|
});
|
||||||
|
const brokerPublicKey = config.get({
|
||||||
|
key: "sensor-broker-publickey",
|
||||||
|
default: null
|
||||||
|
});
|
||||||
|
|
||||||
|
const sensor = new Sensor(keyPair, sensorId, brokerLocation, brokerPublicKey);
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
|
|
||||||
|
app.listen(apiPort, () => console.log(`Listening on port ${apiPort}`));
|
||||||
|
|
||||||
|
app.post('/send', (req, res) => {
|
||||||
|
console.log(`sending: ${JSON.stringify(req.body)}`);
|
||||||
|
sensor.send(JSON.stringify(req.body));
|
||||||
|
res.json("sent");
|
||||||
|
});
|
108
sensor/sensor.js
Normal file
108
sensor/sensor.js
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
const Websocket = require('ws');
|
||||||
|
|
||||||
|
const ChainUtil = require('../chain-util');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const STATE_SERVER_HELLOING = 0;
|
||||||
|
const STATE_SERVER_FINNING = 1;
|
||||||
|
const STATE_OPERATIONAL = 2;
|
||||||
|
|
||||||
|
function onServerHelloing(sensor, data) {
|
||||||
|
const serverNonce = data.toString();
|
||||||
|
|
||||||
|
if (typeof serverNonce !== 'string') {
|
||||||
|
console.log("Bad server hello");
|
||||||
|
sensor.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sensor.serverNonce = serverNonce;
|
||||||
|
|
||||||
|
crypto.randomBytes(2048, (err, buf) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(`Couldn't generate client nonce: ${err}`);
|
||||||
|
sensor.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sensor.clientNonce = buf.toString('hex');
|
||||||
|
|
||||||
|
sensor.socket.send(JSON.stringify({
|
||||||
|
owner: sensor.keyPair.getPublic().encode('hex'),
|
||||||
|
sensor: sensor.sensorId,
|
||||||
|
signature: sensor.keyPair.sign(sensor.serverNonce + sensor.clientNonce),
|
||||||
|
clientNonce: sensor.clientNonce
|
||||||
|
}));
|
||||||
|
sensor.state = STATE_SERVER_FINNING;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onServerFinning(sensor, data) {
|
||||||
|
const signature = JSON.parse(data);
|
||||||
|
if (typeof signature !== 'object') {
|
||||||
|
console.log("Bad server fin");
|
||||||
|
sensor.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sensor.brokerPublicKey !== null) {
|
||||||
|
const verifyRes = ChainUtil.verifySignature(sensor.brokerPublicKey, data, sensor.clientNonce + sensor.serverNonce);
|
||||||
|
if (!verifyRes.result) {
|
||||||
|
console.log("Bad server fin singature: " + verifyRes.reason);
|
||||||
|
sensor.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Broker authed, operational");
|
||||||
|
} else {
|
||||||
|
console.log("No broker public key stored, blindly trusting the broker");
|
||||||
|
}
|
||||||
|
|
||||||
|
sensor.state = STATE_OPERATIONAL;
|
||||||
|
for (const msg of sensor.queue) {
|
||||||
|
sensor.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOperational(_, _) {
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSocketMessage(sensor, data) {
|
||||||
|
switch (sensor.state) {
|
||||||
|
case STATE_SERVER_HELLOING: onServerHelloing(sensor, data); break;
|
||||||
|
case STATE_SERVER_FINNING: onServerFinning(sensor, data); break;
|
||||||
|
case STATE_OPERATIONAL: onOperational(sensor, data); break;
|
||||||
|
default: throw Error("Invalid internal state");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConnection(sensor) {
|
||||||
|
sensor.socket.on('message', (data) => {
|
||||||
|
onSocketMessage(sensor, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class Sensor {
|
||||||
|
constructor(keyPair, sensorId, brokerLocation, brokerPublicKey) {
|
||||||
|
this.keyPair = keyPair;
|
||||||
|
this.sensorId = sensorId;
|
||||||
|
this.brokerPublicKey = brokerPublicKey;
|
||||||
|
this.state = STATE_SERVER_HELLOING;
|
||||||
|
this.queue = [];
|
||||||
|
|
||||||
|
this.socket = new Websocket(brokerLocation);
|
||||||
|
this.socket.on('open', (_) => onConnection(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data) {
|
||||||
|
if (this.state != STATE_OPERATIONAL) {
|
||||||
|
this.queue.push(data);
|
||||||
|
} else {
|
||||||
|
this.socket.send(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Sensor;
|
|
@ -1,3 +1,6 @@
|
||||||
{
|
{
|
||||||
"wallet-private-key": "557360924eae1b0a7ff4727c4226300788b2e98e50b91195f1048e671d4d5d6e"
|
"miner-public-key": "041171cd24b7ddb430cb0764c117ab26170af80a664210edd4b901d17a44857d68dfd92552487480efa9d64a3e57dd9853404044b593c8cb593def5234e3954c60",
|
||||||
|
"wallet-keypair": "edfb811d963164da2c37de34ae6c30fd36b87551ffb8c1dba98dd63d1926a90a",
|
||||||
|
"broker-keypair": "edfb811d963164da2c37de34ae6c30fd36b87551ffb8c1dba98dd63d1926a90a",
|
||||||
|
"broker-name": "broker1"
|
||||||
}
|
}
|
|
@ -1,92 +0,0 @@
|
||||||
const Transaction = require('./transaction');
|
|
||||||
const { INITIAL_BALANCE } = require('../config');
|
|
||||||
const Metadata = require('./metadata');
|
|
||||||
const ChainUtil = require('../chain-util');
|
|
||||||
|
|
||||||
class Wallet {
|
|
||||||
constructor(keyPair) {
|
|
||||||
this.keyPair = keyPair;
|
|
||||||
this.publicKey = this.keyPair.getPublic().encode('hex');
|
|
||||||
this.counter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
return `Wallet -
|
|
||||||
publicKey: ${this.publicKey.toString()}
|
|
||||||
balance : ${this.balance}`
|
|
||||||
}
|
|
||||||
|
|
||||||
sign(dataHash) {
|
|
||||||
return this.keyPair.sign(dataHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
createTransaction(recipient, amount, blockchain) {
|
|
||||||
const balance = blockchain.getBalanceCopy(this.publicKey);
|
|
||||||
|
|
||||||
if (balance.counter > this.counter) {
|
|
||||||
this.counter = balance.counter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount > balance.balance) {
|
|
||||||
console.log(`Amount: ${amount} exceceds current balance: ${balance.balance}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const counterToUse = this.counter + 1;
|
|
||||||
this.counter++;
|
|
||||||
|
|
||||||
const newTransaction = new Transaction(this.publicKey, counterToUse, [Transaction.createOutput(recipient, amount)]);
|
|
||||||
newTransaction.addSignature(this.sign(Transaction.hashToSign(newTransaction)));
|
|
||||||
return newTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
createMetadata(SSNmetadata) {
|
|
||||||
return Metadata.newMetadata(this, SSNmetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
//calculateBalance(blockchain) {
|
|
||||||
// let balance = this.balance;
|
|
||||||
// let transactions = [];
|
|
||||||
// blockchain.chain.forEach(block => block.data.forEach(transaction => {
|
|
||||||
// transactions.push(transaction);
|
|
||||||
// }));
|
|
||||||
// console.log("transactions of balance")
|
|
||||||
// console.log(transactions);
|
|
||||||
// const PaymentTransactions = transactions[0];
|
|
||||||
// console.log("Payment transactions ")
|
|
||||||
// console.log(PaymentTransactions);
|
|
||||||
// const walletInputTs = PaymentTransactions.filter(transaction => transaction.input.address === this.publicKey);
|
|
||||||
|
|
||||||
// let startTime = 0;
|
|
||||||
|
|
||||||
// if (walletInputTs.length > 0) {
|
|
||||||
// const recentInputT = walletInputTs.reduce(
|
|
||||||
// (prev, current) => prev.input.timestamp > current.input.timestamp ? prev : current
|
|
||||||
// );
|
|
||||||
|
|
||||||
// balance = recentInputT.outputs.find(output => output.address === this.publicKey).amount;
|
|
||||||
// startTime = recentInputT.input.timestamp;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// PaymentTransactions.forEach(transaction => {
|
|
||||||
// if (transaction.input.timestamp > startTime) {
|
|
||||||
// transaction.outputs.find(output => {
|
|
||||||
// if (output.address === this.publicKey) {
|
|
||||||
// balance += output.amount;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return balance;
|
|
||||||
//}
|
|
||||||
|
|
||||||
//static blockchainWallet() {
|
|
||||||
// const blockchainWallet = new this(ChainUtil.genKeyPair());
|
|
||||||
// blockchainWallet.address = 'blockchain-wallet';
|
|
||||||
// return blockchainWallet;
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Wallet;
|
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
const ChainUtil = require('../chain-util');
|
|
||||||
|
|
||||||
class Metadata {
|
|
||||||
constructor() {
|
|
||||||
this.id = null;
|
|
||||||
this.Signiture = null;
|
|
||||||
// this.Name = null;
|
|
||||||
// this.Geo = null;
|
|
||||||
|
|
||||||
// this.GeospatialLocation = [];
|
|
||||||
// this.Owenership = null;
|
|
||||||
// this.Cost = null;
|
|
||||||
// this.Identifications = null;
|
|
||||||
// this.Integration = null;
|
|
||||||
|
|
||||||
// this.IP_URL = null;
|
|
||||||
// this.Topic_Token = null;
|
|
||||||
// this.Permission = null;
|
|
||||||
// this.RequestDetail = null;
|
|
||||||
// this.OrgOwner = null;
|
|
||||||
// this.DepOwner = null;
|
|
||||||
// this.PrsnOwner = null;
|
|
||||||
// this.MetaHash = null;
|
|
||||||
// this.PaymentPerKbyte = null;
|
|
||||||
// this.PaymentPerMinute = null;
|
|
||||||
// this.Protocol = null;
|
|
||||||
// this.MessageAttributes= {};
|
|
||||||
// this.Interval = null;
|
|
||||||
// this.FurtherDetails = null;
|
|
||||||
this.SSNmetadata = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MetadataOfIoTDevice(senderWallet, SSNmetadata) {
|
|
||||||
const metadata = new this();
|
|
||||||
metadata.id = ChainUtil.id();
|
|
||||||
// metadata.Name = Name;
|
|
||||||
// metadata.Geo = Geo;
|
|
||||||
// metadata.IP_URL = IP_URL;
|
|
||||||
// metadata.Topic_Token = Topic_Token;
|
|
||||||
// metadata.Permission = Permission;
|
|
||||||
// metadata.RequestDetail = RequestDetail
|
|
||||||
// metadata.OrgOwner = OrgOwner;
|
|
||||||
// metadata.DepOwner = DepOwner;
|
|
||||||
// metadata.PrsnOwner = PrsnOwner;
|
|
||||||
// metadata.PaymentPerKbyte = PaymentPerKbyte ;
|
|
||||||
// metadata.PaymentPerMinute = PaymentPerMinute;
|
|
||||||
// metadata.Protocol = Protocol;
|
|
||||||
// metadata.MessageAttributes = MessageAttributes;
|
|
||||||
|
|
||||||
|
|
||||||
// metadata.MessageAttributes['DeviceID'] = metadata.id;
|
|
||||||
// metadata.MessageAttributes['DeviceName'] = Name;
|
|
||||||
// metadata.MessageAttributes['Sensors'] =[{"SensorName":"","Value":"" , "Unit":""}];
|
|
||||||
// metadata.MessageAttributes['TimeStamp'] = "";
|
|
||||||
|
|
||||||
|
|
||||||
// metadata.Interval = Intrval;
|
|
||||||
// metadata.FurtherDetails = FurtherDetails;
|
|
||||||
metadata.SSNmetadata = SSNmetadata;
|
|
||||||
metadata.MetaHash = ChainUtil.hash(SSNmetadata);
|
|
||||||
Metadata.signMetadata(metadata, senderWallet);
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
static newMetadata(senderWallet,SSNmetadata){
|
|
||||||
return Metadata.MetadataOfIoTDevice(senderWallet, SSNmetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
static signMetadata (metadata, senderWallet) {
|
|
||||||
metadata.Signiture = {
|
|
||||||
timestamp: Date.now(),
|
|
||||||
address: senderWallet.publicKey,
|
|
||||||
signature: senderWallet.sign(ChainUtil.hash(metadata.SSNmetadata))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static verifyMetadata(metadata) {
|
|
||||||
return ChainUtil.verifySignature(
|
|
||||||
metadata.Signiture.address,
|
|
||||||
metadata.Signiture.signature,
|
|
||||||
ChainUtil.hash(metadata.SSNmetadata)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Metadata;
|
|
|
@ -1,95 +0,0 @@
|
||||||
const Transaction = require('../wallet/transaction');
|
|
||||||
const Metadata = require('../wallet/metadata');
|
|
||||||
const Block = require('../blockchain/block');
|
|
||||||
|
|
||||||
const Return = {
|
|
||||||
add: 1,
|
|
||||||
update: 2,
|
|
||||||
error: 3
|
|
||||||
};
|
|
||||||
|
|
||||||
class TransactionPool {
|
|
||||||
constructor() {
|
|
||||||
this.transactions = [];
|
|
||||||
this.metadatas = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
//returns true on update, false on add
|
|
||||||
updateOrAddTransaction(transaction) {
|
|
||||||
if (!Transaction.verify(transaction)) {
|
|
||||||
console.log("Couldn't update or add transaction, transaction couldn't be verified");
|
|
||||||
return Return.error;
|
|
||||||
}
|
|
||||||
const foundIndex = this.transactions.findIndex(t => t.input === transaction.input && t.counter === transaction.counter);
|
|
||||||
|
|
||||||
if (foundIndex !== -1) {
|
|
||||||
this.transactions[foundIndex] = transaction;
|
|
||||||
return Return.update;
|
|
||||||
} else {
|
|
||||||
this.transactions.push(transaction);
|
|
||||||
return Return.add;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateOrAddMetadata(metadata) {
|
|
||||||
if (!Metadata.verifyMetadata(metadata)) {
|
|
||||||
console.log("Couldn't update metdata, metadata couldn't be verified");
|
|
||||||
return Return.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const foundIndex = this.metadatas.findIndex(t => t.id === metadata.id);
|
|
||||||
|
|
||||||
if (foundIndex !== -1) {
|
|
||||||
this.metadatas[foundIndex] = metadata;
|
|
||||||
return Return.update;
|
|
||||||
} else {
|
|
||||||
this.metadatas.push(metadata);
|
|
||||||
return Return.add;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
existingTransaction(address) {
|
|
||||||
return this.transactions.find(t => t.input.address === address);
|
|
||||||
}
|
|
||||||
|
|
||||||
existingMetadata(address) {
|
|
||||||
return this.metadatas.find(t => t.Signiture.address === address);
|
|
||||||
}
|
|
||||||
|
|
||||||
//we could check for possible double spends here
|
|
||||||
validTransactionsCopy() {
|
|
||||||
return [...this.transactions];
|
|
||||||
}
|
|
||||||
|
|
||||||
validMetadatasCopy(){
|
|
||||||
return [...this.metadatas];
|
|
||||||
}
|
|
||||||
|
|
||||||
clearFromBlock(block) {
|
|
||||||
const blockTransactions = Block.getTransactions(block);
|
|
||||||
const blockMetadatas = Block.getMetadatas(block);
|
|
||||||
|
|
||||||
for (const transaction of blockTransactions) {
|
|
||||||
const foundTransaction = this.transactions.findIndex(t => t.id === transaction.id);
|
|
||||||
|
|
||||||
if (foundTransaction !== -1) {
|
|
||||||
this.transactions.splice(foundTransaction, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const metadata of blockMetadatas) {
|
|
||||||
const foundMetadata = this.metadatas.findIndex(m => m.id === metadata.id);
|
|
||||||
|
|
||||||
if (foundMetadata !== -1) {
|
|
||||||
this.metadatas.splice(foundMetadata, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clearAll() {
|
|
||||||
this.transactions = [];
|
|
||||||
this.metadatas = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = TransactionPool;
|
|
||||||
module.exports.Return = Return;
|
|
|
@ -1,86 +0,0 @@
|
||||||
const TransactionPool = require('./transaction-pool');
|
|
||||||
const Transaction = require('./transaction');
|
|
||||||
const Metadata = require('./metadata')
|
|
||||||
const Wallet = require('./index');
|
|
||||||
const Blockchain = require('../blockchain');
|
|
||||||
|
|
||||||
describe('TransactionPool', () => {
|
|
||||||
let tp, wallet, transaction, metadata, bc;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
tp = new TransactionPool();
|
|
||||||
wallet = new Wallet();
|
|
||||||
wallet2 =new Wallet();
|
|
||||||
bc = new Blockchain();
|
|
||||||
transaction = wallet.createTransaction('r4nd-4dr355', 30, bc, tp);
|
|
||||||
// senderWallet = 'address';
|
|
||||||
// Name = 'IoT_Lab_Temp_Sensor'
|
|
||||||
// Geo = [1.045,0.0135]
|
|
||||||
// IP_URL = 'www.IoT-locationbar.com/sensors/temp'
|
|
||||||
// Topic_Token = 'ACCESS_TOKEN'
|
|
||||||
// Permission = 'Public'
|
|
||||||
// RequestDetail = 'Null'
|
|
||||||
// OrgOwner = 'Swinburne_University'
|
|
||||||
// DepOwner = 'Computer_Science'
|
|
||||||
// PrsnOwner = 'Anas_Dawod'
|
|
||||||
// PaymentPerKbyte = 10
|
|
||||||
// PaymentPerMinute = 5
|
|
||||||
// Protocol = 'MQTT'
|
|
||||||
// MessageAttributes = 'null'
|
|
||||||
// Interval = 10
|
|
||||||
// FurtherDetails = 'null'
|
|
||||||
// SSNmetadata = 'null'
|
|
||||||
|
|
||||||
metadata = wallet.createMetadata('IoT_Lab_Temp_Sensor',[1.045,0.0135],"www.IoT-locationbar.com/sensors/temp" ,'ACCESS_TOKEN' , 'Public',
|
|
||||||
'Null', 'Swinburne_University', 'Computer_Science','Anas_Dawod', 10,
|
|
||||||
5, 'MQTT', 'null', 10,
|
|
||||||
'FurtherDetails', 'SSNmetadata',tp);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds a transaction to the pool', () => {
|
|
||||||
expect(tp.transactions.find(t => t.id === transaction.id)).toEqual(transaction);
|
|
||||||
});
|
|
||||||
it('adds a metadata to the pool', () => {
|
|
||||||
expect(tp.metadataS.find(t => t.id === metadata.id)).toEqual(metadata);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates a transaction in the pool', () => {
|
|
||||||
const oldTransaction = JSON.stringify(transaction);
|
|
||||||
const newTransaction = transaction.update(wallet, 'foo-4ddr355', 40);
|
|
||||||
tp.updateOrAddTransaction(newTransaction);
|
|
||||||
|
|
||||||
expect(JSON.stringify(tp.transactions.find(t => t.id === newTransaction.id)))
|
|
||||||
.not.toEqual(oldTransaction);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('clears transactions and metadata', () => {
|
|
||||||
tp.clear();
|
|
||||||
expect(tp.transactions).toEqual([]);
|
|
||||||
expect(tp.metadataS).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('mixing valid and corrupt transactions', () => {
|
|
||||||
let validTransactions;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
validTransactions = [...tp.transactions];
|
|
||||||
for (let i=0; i<6; i++) {
|
|
||||||
wallet = new Wallet();
|
|
||||||
transaction = wallet.createTransaction('r4nd-4dr355', 30, bc, tp);
|
|
||||||
if (i%2==0) {
|
|
||||||
transaction.input.amount = 99999;
|
|
||||||
} else {
|
|
||||||
validTransactions.push(transaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows a difference between valid and corrupt transactions', () => {
|
|
||||||
expect(JSON.stringify(tp.transactions)).not.toEqual(JSON.stringify(validTransactions));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('grabs valid transactions', () => {
|
|
||||||
expect(tp.validTransactions()).toEqual(validTransactions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,80 +0,0 @@
|
||||||
const ChainUtil = require('../chain-util');
|
|
||||||
const { MINING_REWARD } = require('../config');
|
|
||||||
|
|
||||||
class Transaction {
|
|
||||||
constructor(senderPublicKey, counter, outputs) {
|
|
||||||
this.input = senderPublicKey;
|
|
||||||
this.signature = null;
|
|
||||||
this.counter = counter;
|
|
||||||
this.outputs = outputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
addSignature(signature) {
|
|
||||||
if (!ChainUtil.verifySignature(
|
|
||||||
this.input,
|
|
||||||
signature,
|
|
||||||
Transaction.hashToSign(this))) {
|
|
||||||
console.log("Tried to add an invalid signature to a transaction");
|
|
||||||
throw new Error("Tried to add an invalid signature to a transaction");
|
|
||||||
}
|
|
||||||
this.signature = signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
static hashToSign(transaction) {
|
|
||||||
return ChainUtil.hash({
|
|
||||||
counter: transaction.counter,
|
|
||||||
outputs: transaction.outputs
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static createOutput(recipient, amount) {
|
|
||||||
return {
|
|
||||||
publicKey: recipient,
|
|
||||||
amount: amount
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//update(senderWallet, recipients) {
|
|
||||||
// const senderOutput = this.outputs.find(output => output.address === senderWallet.publicKey);
|
|
||||||
|
|
||||||
// if (amount > senderOutput.amount) {
|
|
||||||
// console.log(`Amount: ${amount} exceeds balance.`);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// senderOutput.amount = senderOutput.amount - amount;
|
|
||||||
// this.outputs.push({ amount, address: recipient });
|
|
||||||
// Transaction.signTransaction(this, senderWallet);
|
|
||||||
|
|
||||||
// return this;
|
|
||||||
//}
|
|
||||||
//static signTransaction(transaction, senderWallet) {
|
|
||||||
// transaction.input = {
|
|
||||||
// timestamp: Date.now(),
|
|
||||||
// address: senderWallet.publicKey,
|
|
||||||
// signature: senderWallet.sign(ChainUtil.hash(transaction.outputs))
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
static verify(transaction) {
|
|
||||||
if (transaction.outputs.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const output of transaction.outputs) {
|
|
||||||
if (!output.hasOwnProperty('amount')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!output.hasOwnProperty('publicKey')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ChainUtil.verifySignature(
|
|
||||||
transaction.input,
|
|
||||||
transaction.signature,
|
|
||||||
Transaction.hashToSign(transaction)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Transaction;
|
|
287
wallet/wallet-app.js
Normal file
287
wallet/wallet-app.js
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
//WALLET
|
||||||
|
const express = require('express');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
const P2pServer = require('../p2p-server');
|
||||||
|
|
||||||
|
const N3 = require('n3');
|
||||||
|
|
||||||
|
const Wallet = require('./wallet');
|
||||||
|
const Config = require('../config');
|
||||||
|
const ChainUtil = require('../chain-util');
|
||||||
|
|
||||||
|
const QueryEngine = require('@comunica/query-sparql-rdfjs').QueryEngine;
|
||||||
|
const Blockchain = require('../blockchain/blockchain');
|
||||||
|
|
||||||
|
const {
|
||||||
|
DEFAULT_PORT_WALLET_API,
|
||||||
|
DEFAULT_PORT_WALLET_CHAIN,
|
||||||
|
DEFAULT_PORT_MINER_CHAIN
|
||||||
|
} = require('../constants');
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const CONFIGS_STORAGE_LOCATION = "./settings.json";
|
||||||
|
|
||||||
|
const config = new Config(CONFIGS_STORAGE_LOCATION);
|
||||||
|
|
||||||
|
const wallet = new Wallet(config.get({
|
||||||
|
key: "wallet-keypair",
|
||||||
|
default: ChainUtil.genKeyPair(),
|
||||||
|
transform: ChainUtil.deserializeKeyPair
|
||||||
|
}));
|
||||||
|
const apiPort = config.get({
|
||||||
|
key: "wallet-api-port",
|
||||||
|
default: DEFAULT_PORT_WALLET_API
|
||||||
|
});
|
||||||
|
const blockchainLocation = config.get({
|
||||||
|
key: "wallet-blockchain-location",
|
||||||
|
default: "./wallet_blockchain.json"
|
||||||
|
});
|
||||||
|
const chainServerPort = config.get({
|
||||||
|
key: "wallet-chain-server-port",
|
||||||
|
default: DEFAULT_PORT_WALLET_CHAIN
|
||||||
|
});
|
||||||
|
const chainServerPeers = config.get({
|
||||||
|
key: "wallet-chain-server-peers",
|
||||||
|
default: ["ws://127.0.0.1:" + DEFAULT_PORT_MINER_CHAIN]
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockchain.saveToDisk(blockchainLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chainServer = new P2pServer("Chain-server");
|
||||||
|
|
||||||
|
chainServer.start(chainServerPort, chainServerPeers, (_) => { }, onChainServerRecv);
|
||||||
|
const app = express();
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
|
app.listen(apiPort, () => console.log(`Listening on port ${apiPort}`));
|
||||||
|
|
||||||
|
app.get('/ChainServer/sockets', (req, res) => {
|
||||||
|
res.json(chainServer.sockets);
|
||||||
|
});
|
||||||
|
app.post('/ChainServer/connect', (req, res) => {
|
||||||
|
chainServer.connect(req.body.url);
|
||||||
|
res.json("Connecting");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/public-key', (req, res) => {
|
||||||
|
res.json(wallet.publicKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/key-pair', (req, res) => {
|
||||||
|
res.json(ChainUtil.serializeKeyPair(wallet.keyPair));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/MyBalance', (req, res) => {
|
||||||
|
res.json(blockchain.getBalanceCopy(wallet.publicKey));
|
||||||
|
});
|
||||||
|
app.get('/Balance', (req, res) => {
|
||||||
|
const balance = blockchain.getBalanceCopy(req.body.publicKey);
|
||||||
|
res.json(balance);
|
||||||
|
});
|
||||||
|
app.get('/Balances', (req, res) => {
|
||||||
|
const balances = blockchain.balances;
|
||||||
|
res.json(balances);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/Payment', (req, res) => {
|
||||||
|
res.json(wallet.createPayment(
|
||||||
|
req.body.rewardAmount,
|
||||||
|
req.body.outputs,
|
||||||
|
blockchain));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/Integration', (req, res) => {
|
||||||
|
res.json(wallet.createIntegration(
|
||||||
|
req.body.rewardAmount,
|
||||||
|
req.body.outputs,
|
||||||
|
blockchain));
|
||||||
|
});
|
||||||
|
|
||||||
|
function extToRdf(triples, sensorId, parentString, obj) {
|
||||||
|
for (const key in obj) {
|
||||||
|
const value = obj[key];
|
||||||
|
|
||||||
|
const type = typeof value;
|
||||||
|
|
||||||
|
switch (typeof value) {
|
||||||
|
case "string":
|
||||||
|
triples.push({
|
||||||
|
s: sensorId,
|
||||||
|
p: parentString + key,
|
||||||
|
o: value
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "object":
|
||||||
|
extToRdf(triples, sensorId, parentString + key + '/', value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("Unsupported value type: " + type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const brokerRegistrationValidators = {
|
||||||
|
ssnMetadata: ChainUtil.validateIsString,
|
||||||
|
rewardAmount: ChainUtil.createValidateIsIntegerWithMin(0),
|
||||||
|
extMetadata: ChainUtil.validateIsObject
|
||||||
|
};
|
||||||
|
|
||||||
|
app.post('/BrokerRegistration', (req, res) => {
|
||||||
|
const validateRes = ChainUtil.validateObject(req.body, brokerRegistrationValidators);
|
||||||
|
|
||||||
|
if (!validateRes.result) {
|
||||||
|
res.json(validateRes.reason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const brokers = [];
|
||||||
|
const triples = [];
|
||||||
|
|
||||||
|
const parser = new N3.Parser();
|
||||||
|
parser.parse(
|
||||||
|
req.body.ssnMetadata,
|
||||||
|
(error, quad, prefixes) => {
|
||||||
|
if (error) {
|
||||||
|
res.json(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (quad) {
|
||||||
|
triples.push({
|
||||||
|
s: quad.subject.id,
|
||||||
|
p: quad.predicate.id,
|
||||||
|
o: quad.object.id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (quad.predicate.id === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
|
||||||
|
&& quad.object.id === "SSM/Broker") {
|
||||||
|
brokers.push(quad.subject.id);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//quad is null, we come here, and we are finished parsing
|
||||||
|
if (brokers.length === 0) {
|
||||||
|
res.json("Couldn't find a defined broker");
|
||||||
|
return;
|
||||||
|
} else if (brokers.length > 1) {
|
||||||
|
res.json("Found multiple defined brokers");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
extToRdf(triples, brokers[0], "", req.body.extMetadata);
|
||||||
|
|
||||||
|
try {
|
||||||
|
res.json(wallet.createBrokerRegistration(
|
||||||
|
triples,
|
||||||
|
req.body.rewardAmount,
|
||||||
|
blockchain));
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
res.json(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const sensorRegistrationValidators = {
|
||||||
|
ssnMetadata: ChainUtil.validateIsString,
|
||||||
|
rewardAmount: ChainUtil.createValidateIsIntegerWithMin(0),
|
||||||
|
extMetadata: ChainUtil.validateIsObject
|
||||||
|
};
|
||||||
|
|
||||||
|
app.post('/SensorRegistration', (req, res) => {
|
||||||
|
const validateRes = ChainUtil.validateObject(req.body, sensorRegistrationValidators);
|
||||||
|
|
||||||
|
if (!validateRes.result) {
|
||||||
|
res.json(validateRes.reason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sensors = [];
|
||||||
|
const triples = [];
|
||||||
|
|
||||||
|
const parser = new N3.Parser();
|
||||||
|
parser.parse(
|
||||||
|
req.body.ssnMetadata,
|
||||||
|
(error, quad, prefixes) => {
|
||||||
|
if (error) {
|
||||||
|
res.json(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (quad) {
|
||||||
|
triples.push({
|
||||||
|
s: quad.subject.id,
|
||||||
|
p: quad.predicate.id,
|
||||||
|
o: quad.object.id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (quad.predicate.id === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
|
||||||
|
&& quad.object.id === "http://www.w3.org/ns/sosa/Sensor") {
|
||||||
|
sensors.push(quad.subject.id);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//quad is null, we come here, and we are finished parsing
|
||||||
|
if (sensors.length === 0) {
|
||||||
|
res.json("Couldn't find a defined sensor");
|
||||||
|
return;
|
||||||
|
} else if (sensors.length > 1) {
|
||||||
|
res.json("Found multiple defined sensors");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
extToRdf(triples, sensors[0], "", req.body.extMetadata);
|
||||||
|
|
||||||
|
try {
|
||||||
|
res.json(wallet.createSensorRegistration(
|
||||||
|
triples,
|
||||||
|
req.body.rewardAmount,
|
||||||
|
blockchain));
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
res.json(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const myEngine = new QueryEngine();
|
||||||
|
|
||||||
|
app.post('/sparql', (req, res) => {
|
||||||
|
const start = async function () {
|
||||||
|
try {
|
||||||
|
let result = [];
|
||||||
|
const bindingsStream = await myEngine.queryBindings(
|
||||||
|
req.body.query,
|
||||||
|
{
|
||||||
|
readOnly: true,
|
||||||
|
sources: blockchain.stores
|
||||||
|
});
|
||||||
|
bindingsStream.on('data', (binding) => {
|
||||||
|
result.push(binding);
|
||||||
|
});
|
||||||
|
bindingsStream.on('end', () => {
|
||||||
|
res.json(JSON.stringify(result));
|
||||||
|
});
|
||||||
|
bindingsStream.on('error', (err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.json("Error occured while querying");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
start()
|
||||||
|
|
||||||
|
});
|
133
wallet/wallet.js
Normal file
133
wallet/wallet.js
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
const Payment = require('../blockchain/payment');
|
||||||
|
const Integration = require('../blockchain/integration');
|
||||||
|
const SensorRegistration = require('../blockchain/sensor-registration');
|
||||||
|
const BrokerRegistration = require('../blockchain/broker-registration');
|
||||||
|
const Transaction = require('../blockchain/transaction');
|
||||||
|
|
||||||
|
//TODO: keep track of issued transactions, so we don't accidently try and double spend
|
||||||
|
class Wallet {
|
||||||
|
constructor(keyPair) {
|
||||||
|
this.keyPair = keyPair;
|
||||||
|
this.publicKey = this.keyPair.getPublic().encode('hex');
|
||||||
|
this.counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sign(dataHash) {
|
||||||
|
return this.keyPair.sign(dataHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: API for multiple outputs
|
||||||
|
//returns Transaction
|
||||||
|
createPayment(rewardAmount, outputs, blockchain) {
|
||||||
|
const balance = blockchain.getBalanceCopy(this.publicKey);
|
||||||
|
|
||||||
|
if (balance.counter > this.counter) {
|
||||||
|
this.counter = balance.counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalAmount = 0;
|
||||||
|
for (const output of outputs) {
|
||||||
|
totalAmount += output.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalAmount + rewardAmount > balance.balance) {
|
||||||
|
console.log(`Total amount: ${totalAmount} + reward amount: ${rewardAmount} exceeds current balance: ${balance.balance}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const counterToUse = this.counter + 1;
|
||||||
|
this.counter++;
|
||||||
|
|
||||||
|
return new Payment(this.keyPair, counterToUse, outputs, rewardAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPaymentAsTransaction(rewardAmount, outputs, blockchain) {
|
||||||
|
return new Transaction(
|
||||||
|
this.createPayment(rewardAmount, outputs, blockchain),
|
||||||
|
Payment);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: API for multiple sensors
|
||||||
|
//returns Transaction
|
||||||
|
createIntegration(rewardAmount, outputs, blockchain) {
|
||||||
|
const balance = blockchain.getBalanceCopy(this.publicKey);
|
||||||
|
|
||||||
|
if (balance.counter > this.counter) {
|
||||||
|
this.counter = balance.counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalAmount = 0;
|
||||||
|
for (const output of outputs) {
|
||||||
|
totalAmount += output.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalAmount + rewardAmount > balance.balance) {
|
||||||
|
console.log(`Total amount: ${totalAmount} + reward amount: ${rewardAmount} exceeds current known balance: ${balance.balance}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const counterToUse = this.counter + 1;
|
||||||
|
this.counter++;
|
||||||
|
|
||||||
|
return new Integration(this.keyPair, counterToUse, outputs, rewardAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
createIntegrationAsTransaction(rewardAmount, outputs, blockchain) {
|
||||||
|
return new Transaction(
|
||||||
|
this.createIntegration(rewardAmount, outputs, blockchain),
|
||||||
|
Integration);
|
||||||
|
}
|
||||||
|
|
||||||
|
//returns Transaction
|
||||||
|
createBrokerRegistration(metadata, rewardAmount, blockchain) {
|
||||||
|
const balance = blockchain.getBalanceCopy(this.publicKey);
|
||||||
|
|
||||||
|
if (balance.counter > this.counter) {
|
||||||
|
this.counter = balance.counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewardAmount > balance.balance) {
|
||||||
|
console.log(`Reward amount: ${rewardAmount} exceeds current balance: ${balance.balance}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const counterToUse = this.counter + 1;
|
||||||
|
this.counter++;
|
||||||
|
|
||||||
|
return new BrokerRegistration(this.keyPair, counterToUse, metadata, rewardAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
createBrokerRegistrationAsTransaction(metadata, rewardAmount, blockchain) {
|
||||||
|
return new Transaction(
|
||||||
|
this.createBrokerRegistration(metadata, rewardAmount, blockchain),
|
||||||
|
BrokerRegistration);
|
||||||
|
}
|
||||||
|
|
||||||
|
//return Transaction
|
||||||
|
createSensorRegistration(metadata, rewardAmount, blockchain) {
|
||||||
|
const balance = blockchain.getBalanceCopy(this.publicKey);
|
||||||
|
|
||||||
|
if (balance.counter > this.counter) {
|
||||||
|
this.counter = balance.counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewardAmount > balance.balance) {
|
||||||
|
console.log(`Reward amount: ${rewardAmount} exceeds current balance: ${balance.balance}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const counterToUse = this.counter + 1;
|
||||||
|
this.counter++;
|
||||||
|
|
||||||
|
return new SensorRegistration(this.keyPair, counterToUse, metadata, rewardAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
createSensorRegistrationAsTransaction(metadata, rewardAmount, blockchain) {
|
||||||
|
return new Transaction(
|
||||||
|
this.createSensorRegistration(metadata, rewardAmount, blockchain),
|
||||||
|
SensorRegistration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Wallet;
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
const Wallet = require('./index');
|
const Wallet = require('./index');
|
||||||
const TransactionPool = require('./transaction-pool');
|
|
||||||
const Blockchain = require('../blockchain');
|
const Blockchain = require('../blockchain');
|
||||||
|
const ChainUtil = require('../chain-util');
|
||||||
const { INITIAL_BALANCE } = require('../config');
|
const { INITIAL_BALANCE } = require('../config');
|
||||||
|
|
||||||
describe('Wallet', () => {
|
describe('Wallet', () => {
|
||||||
let wallet, tp, bc;
|
let wallet, bc;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wallet = new Wallet();
|
wallet = new Wallet(ChainUtil.genKeyPair());
|
||||||
tp = new TransactionPool();
|
|
||||||
bc = new Blockchain();
|
bc = new Blockchain();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -28,6 +27,7 @@ describe('Wallet', () => {
|
||||||
tp.updateOrAddTransaction(transaction);
|
tp.updateOrAddTransaction(transaction);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//?
|
||||||
it('doubles the `sendAmount` subtracted from the wallet balance', () => {
|
it('doubles the `sendAmount` subtracted from the wallet balance', () => {
|
||||||
expect(transaction.outputs.find(output => output.address === wallet.publicKey).amount)
|
expect(transaction.outputs.find(output => output.address === wallet.publicKey).amount)
|
||||||
.toEqual(wallet.balance - sendAmount * 2);
|
.toEqual(wallet.balance - sendAmount * 2);
|
Loading…
Add table
Add a link
Reference in a new issue