miner mines continuously,

moved to an account model,
transactions working,
querying working,
more checking of valid data
This commit is contained in:
Josip Milovac 2023-01-10 17:11:56 +11:00
parent 59bb42be11
commit 9847b2056b
12 changed files with 663 additions and 365 deletions

View file

@ -27,16 +27,17 @@
* to monitor the node
*
*/
const LoggerPretty = require("@comunica/logger-pretty").LoggerPretty;
const express = require('express');
const bodyParser = require('body-parser');
const Blockchain = require('../blockchain');
const P2pServer = require('./p2p-server');
const Wallet = require('../wallet');
const TransactionPool = require('../wallet/transaction-pool');
const QueryEngine = require('@comunica/query-sparql').QueryEngine;
const ChainUtil = require('../chain-util');
const N3 = require('n3');
const jsonld = require('jsonld');
var mqtt = require('mqtt');
var aedes = require('aedes')(); /* aedes is a stream-based MQTT broker */
@ -48,17 +49,46 @@ const multer = require('multer');/* Multer is a node.js middleware for
'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.*/
const SETTINGS_STORAGE_LOCATION = "./settings.json";
const SETTING_MINER_PUBLIC_KEY = "miner-public-key";
const SETTING_WALLET_PRIVATE_KEY = "wallet-private-key";
var settings = {};
//possible race if deleted after check, but we live with it I guess
if (fs.existsSync(SETTINGS_STORAGE_LOCATION)) {
const rawSettings = fs.readFileSync(SETTINGS_STORAGE_LOCATION, 'utf8');
settings = JSON.parse(rawSettings);
}
const app = express();
const bc = new Blockchain();
//currently gen a new keypair per run, we probably want to load this from something else in the future
const wallet = new Wallet(ChainUtil.genKeyPair());
const tp = new TransactionPool();
const p2pServer = new P2pServer(bc, tp, wallet, './persist_block_chain.json');
const parser = new N3.Parser(); //({format: 'application/n-quads'});
//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();
function getBlockchain() {
return p2pServer.blockchain;
}
app.use(bodyParser.json());
//initialising a local storage for storing metadata file initially before storing it in the tripple store
@ -73,7 +103,7 @@ const storage = multer.diskStorage({
//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.mimettype === 'turtle') {
if (file.mimetype === 'application/json' || file.mimetype === 'text/plain' || file.mimetype === 'turtle') {
cb(null, true);
} else {
cb(null, false);
@ -102,6 +132,9 @@ MQTTserver.listen(MQTTport, function () {
app.use('/uploads', express.static('uploads')); // to store uploaded metadata to '/uploads' folder
app.use(bodyParser.json()); //
//API HELPERS
function
// GET APIs
app.get('/blocks', (req, res) => {
res.json(bc.chain);
@ -131,7 +164,12 @@ app.get('/public-key', (req, res) => {
});
///////////////
app.get('/Balance', (req, res) => {
res.json({ Balance: wallet.balance });
const balance = getBlockchain().getBalanceCopy(wallet.publicKey);
res.json({ Balance: balance.balance });
});
app.get('/Balances', (req, res) => {
const balances = getBlockchain().balances;
res.json(balances);
});
///////////////
@ -170,15 +208,22 @@ app.get('/IoTdeviceRegistration', (req, res)=> {
(err, nquads) => {
//console.log(nquads)
var metadata = wallet.createMetadata(
nquads, tp);
nquads);
p2pServer.newMetadata(metadata);
});
});
res.json("MetadataTransactionCreated");
});
app.get('/storeSize', (req, res) => {
res.json({
size: getBlockchain().store.size
});
});
//////////////////////////////////////////////////
// POST APIs
//this doesn't work well with the continious miner
//app.post('/mine', (req, res) => {
// const block = bc.addBlock(req.body.data);
@ -190,18 +235,38 @@ app.get('/IoTdeviceRegistration', (req, res)=> {
//});
///////////////
app.post('/PaymentTransaction', (req, res) => {
if (!req.body.hasOwnProperty('recpient')) {
res.json({
result: false,
reason: "Missing \"recipient\" in body"
});
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, bc, tp);
const transaction = wallet.createTransaction(recipient, amount, getBlockchain());
if (transaction === null) {
res.json("Couldn't create transaction");
return;
}
p2pServer.newTransaction(transaction);
res.redirect('/transactions');
res.json(transaction);
});
///////////////
app.post('/IoTdevicePaymentTransaction', (req, res) => {
if (!req.body.hasOwnProperty("Recipient_payment_address")) {
req.json({
result: false,
reason: "Missing \"Recipient_
}
}
const { Recipient_payment_address, Amount_of_money, Payment_method,
Further_details} = req.body;
if (Payment_method == "SensorCoin") {
@ -239,24 +304,22 @@ app.post("/UploadMetafile", upload.single('file'), (req, res) => {
/////////////////////
//Start of comunica sparql query code
app.post('/sparql', (req, res) => {
console.log(req.body);
const start = async function () {
try {
let result = [];
const bindingsStream = await myEngine.queryBindings(
req.body,
req.body.query,
{
log: new LoggerPretty({ level: 'trace' }),
readOnly: true,
sources: [{
type: 'rdfjsSource',
value: p2pServer.store
}]
sources: [getBlockchain().store]
});
bindingsStream.on('data', (binding) => {
console.log(binding.toString());
result.push(binding);
});
bindingsStream.on('end', () => {
console.log('end');
res.json(JSON.stringify(result));
});
bindingsStream.on('error', (err) => {

View file

@ -1,22 +1,26 @@
const Wallet = require('../wallet');
const Transaction = require('../wallet/transaction');
const Block = require('../blockchain/block');
const ITERATIONS = 1;
class Miner {
static STATE_WAITING = 0;
static STATE_RUNNING = 1;
static STATE_INTERRUPTED = 2;
static STATE_RESTARTING = 3;
static STATE_RUNNING = 0;
static STATE_INTERRUPTED = 1;
constructor(blockchain, transactionPool, wallet, p2pServer) {
constructor(blockchain, transactionPool, reward, p2pServer) {
this.blockchain = blockchain;
this.transactionPool = transactionPool;
this.wallet = wallet;
this.p2pServer = p2pServer;
this.state = Miner.STATE_WAITING;
this.mining = [[], []];
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() {
@ -26,65 +30,77 @@ class Miner {
}
interruptIfContainsTransaction(transaction) {
if (this.state === Miner.STATE_RUNNING && this.mining[0].find(t => t.id === transaction.id)) {
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[1].find(t => t.id === metadata.id)) {
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_WAITING && this.state !== Miner.STATE_RESTARTING) {
if (this.state !== Miner.STATE_INTERRUPTED && this.state !== Miner.STATE_RESTARTING) {
return;
}
const validTransactions = this.transactionPool.validTransactions();
const validMetadataS = this.transactionPool.validMetadataS();
this.minedStartTime = process.hrtime.bigint();
if (validTransactions.length === 0 && validMetadataS.length === 0) {
this.state = Miner.STATE_WAITING;
return;
}
validTransactions.push(
Transaction.rewardTransaction(this.wallet, Wallet.blockchainWallet())
);
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.mining = [validTransactions, validMetadataS];
this.nonce = 0;
this.mine();
}
mine() {
if (this.state !== Miner.STATE_RUNNING) {
this.state = Miner.STATE_RESTARTING;
startMine();
this.startMine();
return;
}
const timestamp = Date.now();
const difficulty = Block.adjustDifficulty(this.lastBlock, timestamp);
const hash = Block.hash(timestamp, this.lastBlock.hash, this.mining, this.nonce, difficulty);
if (hash.substring(0, difficulty) === '0'.repeat(difficulty)) {
//success
this.p2pServer.newBlock(new Block(timestamp, this.lastBlock.hash, hash, this.mining, this.nonce, difficulty));
this.state = Miner.STATE_RESTARTING;
setImmediate(() => { this.startMine() });
} else {
//failure
this.nonce++;
setImmediate(() => { this.mine() });
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;
module.exports = Miner;

View file

@ -1,12 +1,12 @@
const Websocket = require('ws');
const N3 = require('n3');
const DataFactory = require('n3').DataFactory;
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(',') : [];
@ -18,22 +18,26 @@ const MESSAGE_TYPES = {
};
class P2pServer {
constructor(blockchain, transactionPool, wallet, chainStorageLocation) {
this.blockchain = blockchain;
constructor(transactionPool, rewardPublicKey, chainStorageLocation) {
this.blockchain = new Blockchain();
this.transactionPool = transactionPool;
this.sockets = [];
this.store = new N3.Store();
this.chainStorageLocation = chainStorageLocation;
this.miner = new Miner(this.blockchain, this.transactionPool, wallet, this);
//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 chain = JSON.parse(rawPersistedChain);
this.newChain(chain, false);
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() {
@ -106,7 +110,7 @@ class P2pServer {
}
newTransaction(transaction, broadcast) {
if (!Transaction.verifyTransaction(transaction)) {
if (!Transaction.verify(transaction)) {
console.log("Couldn't add transaction to p2pServer, couldn't verify");
return false;
}
@ -128,32 +132,32 @@ class P2pServer {
}
}
newBlock(block) {
blockMined(block) {
if (!this.blockchain.addBlock(block)) {
//invalid block, return
return;
}
this.onNewBlock(block);
this.persistChain(this.blockchain.chain);
this.transactionPool.clearFromBlock(block);
this.miner.interrupt();
this.persistChain(this.blockchain);
this.syncChains();
}
newChain(chain, persist) {
const oldChain = this.blockchain.chain;
const divergence = this.blockchain.replaceChain(chain);
if (divergence === null) {
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(chain);
}
for (let i = divergence; i < oldChain.length; i++) {
this.store.deleteGraph(oldChain[i].hash);
}
for (let i = divergence; i < this.blockchain.chain.length; i++) {
this.onNewBlock(this.blockchain.chain[i]);
this.persistChain(this.blockchain);
}
}
@ -161,70 +165,33 @@ class P2pServer {
try {
fs.writeFileSync(
this.chainStorageLocation,
JSON.stringify(chain));
chain.serialize());
} catch (err) {
console.error("Couldn't persist chain, aborting");
console.error(`Couldn't persist chain, aborting: ${err}`);
process.exit(-1);
}
}
onNewBlock(block) {
//block data is of form [transactions,metadatas]
if (block.data.length != 2) {
//assert?
return;
}
this.transactionPool.clearFromBlock(block);
this.miner.interrupt();
const metadatas = block.data[1];
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) {
this.store.addQuad(DataFactory.quad(
DataFactory.namedNode(quadN.subject.id),
DataFactory.namedNode(quadN.predicate.id),
DataFactory.namedNode(quadN.object.id),
DataFactory.namedNode(block.hash)));
}
});
}
}
sendChain(socket) {
socket.send(JSON.stringify({
type: MESSAGE_TYPES.chain,
chain: this.blockchain.chain
chain: this.blockchain.serialize()
}));
}
//sendTransaction(socket, transaction) {
// socket.send(JSON.stringify({
// type: MESSAGE_TYPES.transaction,
// transaction
// }));
//}
sendTransaction(socket, transaction) {
socket.send(JSON.stringify({
type: MESSAGE_TYPES.transaction,
transaction
}));
}
//sendMetadata(socket, metadata) {
// socket.send(JSON.stringify({
// type: MESSAGE_TYPES.metadata,
// metadata
// }));
//}
sendMetadata(socket, metadata) {
socket.send(JSON.stringify({
type: MESSAGE_TYPES.metadata,
metadata
}));
}
syncChains() {
this.sockets.forEach(socket => this.sendChain(socket));
}