miner mines continuously,
moved to an account model, transactions working, querying working, more checking of valid data
This commit is contained in:
parent
59bb42be11
commit
9847b2056b
12 changed files with 663 additions and 365 deletions
101
app/index.js
101
app/index.js
|
@ -27,16 +27,17 @@
|
||||||
* to monitor the node
|
* to monitor the node
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
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 Blockchain = require('../blockchain');
|
|
||||||
const P2pServer = require('./p2p-server');
|
const P2pServer = require('./p2p-server');
|
||||||
const Wallet = require('../wallet');
|
const Wallet = require('../wallet');
|
||||||
const TransactionPool = require('../wallet/transaction-pool');
|
const TransactionPool = require('../wallet/transaction-pool');
|
||||||
const QueryEngine = require('@comunica/query-sparql').QueryEngine;
|
const QueryEngine = require('@comunica/query-sparql').QueryEngine;
|
||||||
const ChainUtil = require('../chain-util');
|
const ChainUtil = require('../chain-util');
|
||||||
|
|
||||||
const N3 = require('n3');
|
|
||||||
const jsonld = require('jsonld');
|
const jsonld = require('jsonld');
|
||||||
var mqtt = require('mqtt');
|
var mqtt = require('mqtt');
|
||||||
var aedes = require('aedes')(); /* aedes is a stream-based MQTT broker */
|
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".
|
'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 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 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();
|
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 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
|
//filtering the type of uploaded Metadata files
|
||||||
const fileFilter = (req, file, cb) => {
|
const fileFilter = (req, file, cb) => {
|
||||||
// reject a file
|
// 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);
|
cb(null, true);
|
||||||
} else {
|
} else {
|
||||||
cb(null, false);
|
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('/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(bc.chain);
|
||||||
|
@ -131,7 +164,12 @@ app.get('/public-key', (req, res) => {
|
||||||
});
|
});
|
||||||
///////////////
|
///////////////
|
||||||
app.get('/Balance', (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) => {
|
(err, nquads) => {
|
||||||
//console.log(nquads)
|
//console.log(nquads)
|
||||||
var metadata = wallet.createMetadata(
|
var metadata = wallet.createMetadata(
|
||||||
nquads, tp);
|
nquads);
|
||||||
p2pServer.newMetadata(metadata);
|
p2pServer.newMetadata(metadata);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
res.json("MetadataTransactionCreated");
|
res.json("MetadataTransactionCreated");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/storeSize', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
size: getBlockchain().store.size
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// POST APIs
|
// POST APIs
|
||||||
|
|
||||||
//this doesn't work well with the continious miner
|
//this doesn't work well with the continious miner
|
||||||
//app.post('/mine', (req, res) => {
|
//app.post('/mine', (req, res) => {
|
||||||
// const block = bc.addBlock(req.body.data);
|
// const block = bc.addBlock(req.body.data);
|
||||||
|
@ -190,18 +235,38 @@ app.get('/IoTdeviceRegistration', (req, res)=> {
|
||||||
//});
|
//});
|
||||||
///////////////
|
///////////////
|
||||||
app.post('/PaymentTransaction', (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 { recipient, amount } = req.body;
|
||||||
const transaction = wallet.createTransaction(recipient, amount, bc, tp);
|
const transaction = wallet.createTransaction(recipient, amount, getBlockchain());
|
||||||
if (transaction === null) {
|
if (transaction === null) {
|
||||||
res.json("Couldn't create transaction");
|
res.json("Couldn't create transaction");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
p2pServer.newTransaction(transaction);
|
p2pServer.newTransaction(transaction);
|
||||||
res.redirect('/transactions');
|
res.json(transaction);
|
||||||
});
|
});
|
||||||
|
|
||||||
///////////////
|
///////////////
|
||||||
app.post('/IoTdevicePaymentTransaction', (req, res) => {
|
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,
|
const { Recipient_payment_address, Amount_of_money, Payment_method,
|
||||||
Further_details} = req.body;
|
Further_details} = req.body;
|
||||||
if (Payment_method == "SensorCoin") {
|
if (Payment_method == "SensorCoin") {
|
||||||
|
@ -239,24 +304,22 @@ app.post("/UploadMetafile", upload.single('file'), (req, res) => {
|
||||||
/////////////////////
|
/////////////////////
|
||||||
//Start of comunica sparql query code
|
//Start of comunica sparql query code
|
||||||
app.post('/sparql', (req, res) => {
|
app.post('/sparql', (req, res) => {
|
||||||
console.log(req.body);
|
|
||||||
const start = async function () {
|
const start = async function () {
|
||||||
try {
|
try {
|
||||||
let result = [];
|
let result = [];
|
||||||
const bindingsStream = await myEngine.queryBindings(
|
const bindingsStream = await myEngine.queryBindings(
|
||||||
req.body,
|
req.body.query,
|
||||||
{
|
{
|
||||||
|
log: new LoggerPretty({ level: 'trace' }),
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
sources: [{
|
sources: [getBlockchain().store]
|
||||||
type: 'rdfjsSource',
|
|
||||||
value: p2pServer.store
|
|
||||||
}]
|
|
||||||
});
|
});
|
||||||
bindingsStream.on('data', (binding) => {
|
bindingsStream.on('data', (binding) => {
|
||||||
console.log(binding.toString());
|
console.log(binding.toString());
|
||||||
result.push(binding);
|
result.push(binding);
|
||||||
});
|
});
|
||||||
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) => {
|
||||||
|
|
90
app/miner.js
90
app/miner.js
|
@ -1,22 +1,26 @@
|
||||||
const Wallet = require('../wallet');
|
|
||||||
const Transaction = require('../wallet/transaction');
|
|
||||||
const Block = require('../blockchain/block');
|
const Block = require('../blockchain/block');
|
||||||
|
|
||||||
|
const ITERATIONS = 1;
|
||||||
|
|
||||||
class Miner {
|
class Miner {
|
||||||
static STATE_WAITING = 0;
|
static STATE_RUNNING = 0;
|
||||||
static STATE_RUNNING = 1;
|
static STATE_INTERRUPTED = 1;
|
||||||
static STATE_INTERRUPTED = 2;
|
|
||||||
static STATE_RESTARTING = 3;
|
|
||||||
|
|
||||||
constructor(blockchain, transactionPool, wallet, p2pServer) {
|
constructor(blockchain, transactionPool, reward, p2pServer) {
|
||||||
this.blockchain = blockchain;
|
this.blockchain = blockchain;
|
||||||
this.transactionPool = transactionPool;
|
this.transactionPool = transactionPool;
|
||||||
this.wallet = wallet;
|
|
||||||
this.p2pServer = p2pServer;
|
this.p2pServer = p2pServer;
|
||||||
this.state = Miner.STATE_WAITING;
|
this.state = Miner.STATE_INTERRUPTED;
|
||||||
this.mining = [[], []];
|
|
||||||
this.lastBlock = null;
|
this.lastBlock = null;
|
||||||
|
|
||||||
|
this.minedStartTime = null;
|
||||||
|
|
||||||
|
this.mining = {};
|
||||||
|
this.mining.transactions = [];
|
||||||
|
this.mining.reward = reward;
|
||||||
|
this.mining.metadatas = [];
|
||||||
|
|
||||||
|
this.startMine();
|
||||||
}
|
}
|
||||||
|
|
||||||
interrupt() {
|
interrupt() {
|
||||||
|
@ -26,65 +30,77 @@ class Miner {
|
||||||
}
|
}
|
||||||
|
|
||||||
interruptIfContainsTransaction(transaction) {
|
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;
|
this.state = Miner.STATE_INTERRUPTED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
interruptIfContainsMetadata(metadata) {
|
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;
|
this.state = Miner.STATE_INTERRUPTED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startMine() {
|
startMine() {
|
||||||
//only continue if state is waiting or restarting
|
//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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validTransactions = this.transactionPool.validTransactions();
|
this.minedStartTime = process.hrtime.bigint();
|
||||||
const validMetadataS = this.transactionPool.validMetadataS();
|
|
||||||
|
|
||||||
if (validTransactions.length === 0 && validMetadataS.length === 0) {
|
this.mining.transactions = this.transactionPool.validTransactionsCopy();
|
||||||
this.state = Miner.STATE_WAITING;
|
this.mining.metadatas = this.transactionPool.validMetadatasCopy();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
validTransactions.push(
|
|
||||||
Transaction.rewardTransaction(this.wallet, Wallet.blockchainWallet())
|
|
||||||
);
|
|
||||||
|
|
||||||
this.lastBlock = this.blockchain.chain[this.blockchain.chain.length - 1];
|
this.lastBlock = this.blockchain.chain[this.blockchain.chain.length - 1];
|
||||||
|
|
||||||
|
this.nonce = 0;
|
||||||
this.state = Miner.STATE_RUNNING;
|
this.state = Miner.STATE_RUNNING;
|
||||||
|
|
||||||
this.mining = [validTransactions, validMetadataS];
|
|
||||||
this.nonce = 0;
|
|
||||||
this.mine();
|
this.mine();
|
||||||
}
|
}
|
||||||
|
|
||||||
mine() {
|
mine() {
|
||||||
if (this.state !== Miner.STATE_RUNNING) {
|
if (this.state !== Miner.STATE_RUNNING) {
|
||||||
this.state = Miner.STATE_RESTARTING;
|
this.state = Miner.STATE_RESTARTING;
|
||||||
startMine();
|
this.startMine();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const difficulty = Block.adjustDifficulty(this.lastBlock, timestamp);
|
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)) {
|
for (let i = 0; i < ITERATIONS; ++i) {
|
||||||
//success
|
const hash = Block.hash(
|
||||||
this.p2pServer.newBlock(new Block(timestamp, this.lastBlock.hash, hash, this.mining, this.nonce, difficulty));
|
timestamp,
|
||||||
this.state = Miner.STATE_RESTARTING;
|
this.lastBlock.hash,
|
||||||
setImmediate(() => { this.startMine() });
|
this.mining.reward,
|
||||||
} else {
|
this.mining.transactions,
|
||||||
//failure
|
this.mining.metadatas,
|
||||||
this.nonce++;
|
this.nonce,
|
||||||
setImmediate(() => { this.mine() });
|
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;
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
const Websocket = require('ws');
|
const Websocket = require('ws');
|
||||||
const N3 = require('n3');
|
|
||||||
const DataFactory = require('n3').DataFactory;
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const process = require('process');
|
const process = require('process');
|
||||||
const Miner = require('./miner');
|
const Miner = require('./miner');
|
||||||
const Transaction = require('../wallet/transaction');
|
const Transaction = require('../wallet/transaction');
|
||||||
const TransactionPool = require('../wallet/transaction-pool');
|
const TransactionPool = require('../wallet/transaction-pool');
|
||||||
const Metadata = require('../wallet/metadata');
|
const Metadata = require('../wallet/metadata');
|
||||||
|
const Blockchain = require('../blockchain');
|
||||||
|
|
||||||
const P2P_PORT = process.env.P2P_PORT || 5000;
|
const P2P_PORT = process.env.P2P_PORT || 5000;
|
||||||
const peers = process.env.PEERS ? process.env.PEERS.split(',') : [];
|
const peers = process.env.PEERS ? process.env.PEERS.split(',') : [];
|
||||||
|
@ -18,22 +18,26 @@ const MESSAGE_TYPES = {
|
||||||
};
|
};
|
||||||
|
|
||||||
class P2pServer {
|
class P2pServer {
|
||||||
constructor(blockchain, transactionPool, wallet, chainStorageLocation) {
|
constructor(transactionPool, rewardPublicKey, chainStorageLocation) {
|
||||||
this.blockchain = blockchain;
|
this.blockchain = new Blockchain();
|
||||||
this.transactionPool = transactionPool;
|
this.transactionPool = transactionPool;
|
||||||
this.sockets = [];
|
this.sockets = [];
|
||||||
this.store = new N3.Store();
|
|
||||||
this.chainStorageLocation = chainStorageLocation;
|
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
|
//possible race if deleted after check, but we live with it I guess
|
||||||
if (fs.existsSync(this.chainStorageLocation)) {
|
if (fs.existsSync(this.chainStorageLocation)) {
|
||||||
const rawPersistedChain = fs.readFileSync(this.chainStorageLocation, 'utf8');
|
const rawPersistedChain = fs.readFileSync(this.chainStorageLocation, 'utf8');
|
||||||
const chain = JSON.parse(rawPersistedChain);
|
const deserialized = Blockchain.deserialize(rawPersistedChain);
|
||||||
this.newChain(chain, false);
|
if (deserialized === null) {
|
||||||
|
console.log(`Couldn't deserialize chain at '${this.chainStorageLocation}', starting from genesis`);
|
||||||
|
} else {
|
||||||
|
this.blockchain = deserialized;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("Didn't find a persisted chain, starting from genesis");
|
console.log("Didn't find a persisted chain, starting from genesis");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.miner = new Miner(this.blockchain, this.transactionPool, rewardPublicKey, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
listen() {
|
listen() {
|
||||||
|
@ -106,7 +110,7 @@ class P2pServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
newTransaction(transaction, broadcast) {
|
newTransaction(transaction, broadcast) {
|
||||||
if (!Transaction.verifyTransaction(transaction)) {
|
if (!Transaction.verify(transaction)) {
|
||||||
console.log("Couldn't add transaction to p2pServer, couldn't verify");
|
console.log("Couldn't add transaction to p2pServer, couldn't verify");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -128,32 +132,32 @@ class P2pServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newBlock(block) {
|
blockMined(block) {
|
||||||
if (!this.blockchain.addBlock(block)) {
|
if (!this.blockchain.addBlock(block)) {
|
||||||
//invalid block, return
|
//invalid block, return
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.onNewBlock(block);
|
this.transactionPool.clearFromBlock(block);
|
||||||
this.persistChain(this.blockchain.chain);
|
this.miner.interrupt();
|
||||||
|
this.persistChain(this.blockchain);
|
||||||
this.syncChains();
|
this.syncChains();
|
||||||
}
|
}
|
||||||
|
|
||||||
newChain(chain, persist) {
|
newChain(chain, persist) {
|
||||||
const oldChain = this.blockchain.chain;
|
const replaceResult = this.blockchain.replaceChain(chain);
|
||||||
const divergence = this.blockchain.replaceChain(chain);
|
if (!replaceResult.result) {
|
||||||
|
|
||||||
if (divergence === null) {
|
|
||||||
//failed to replace
|
//failed to replace
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < replaceResult.chainDifference; i++) {
|
||||||
|
this.transactionPool.clearFromBlock(this.blockchain.chain[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.miner.interrupt();
|
||||||
|
|
||||||
if (typeof persist === "undefined" || persist) {
|
if (typeof persist === "undefined" || persist) {
|
||||||
this.persistChain(chain);
|
this.persistChain(this.blockchain);
|
||||||
}
|
|
||||||
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]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,70 +165,33 @@ class P2pServer {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
this.chainStorageLocation,
|
this.chainStorageLocation,
|
||||||
JSON.stringify(chain));
|
chain.serialize());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Couldn't persist chain, aborting");
|
console.error(`Couldn't persist chain, aborting: ${err}`);
|
||||||
process.exit(-1);
|
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) {
|
sendChain(socket) {
|
||||||
socket.send(JSON.stringify({
|
socket.send(JSON.stringify({
|
||||||
type: MESSAGE_TYPES.chain,
|
type: MESSAGE_TYPES.chain,
|
||||||
chain: this.blockchain.chain
|
chain: this.blockchain.serialize()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
//sendTransaction(socket, transaction) {
|
sendTransaction(socket, transaction) {
|
||||||
// socket.send(JSON.stringify({
|
socket.send(JSON.stringify({
|
||||||
// type: MESSAGE_TYPES.transaction,
|
type: MESSAGE_TYPES.transaction,
|
||||||
// transaction
|
transaction
|
||||||
// }));
|
}));
|
||||||
//}
|
}
|
||||||
|
|
||||||
//sendMetadata(socket, metadata) {
|
sendMetadata(socket, metadata) {
|
||||||
// socket.send(JSON.stringify({
|
socket.send(JSON.stringify({
|
||||||
// type: MESSAGE_TYPES.metadata,
|
type: MESSAGE_TYPES.metadata,
|
||||||
// metadata
|
metadata
|
||||||
// }));
|
}));
|
||||||
//}
|
}
|
||||||
syncChains() {
|
syncChains() {
|
||||||
this.sockets.forEach(socket => this.sendChain(socket));
|
this.sockets.forEach(socket => this.sendChain(socket));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
const ChainUtil = require('../chain-util');
|
const ChainUtil = require('../chain-util');
|
||||||
const { DIFFICULTY, MINE_RATE } = require('../config');
|
const { DIFFICULTY, MINE_RATE } = require('../config');
|
||||||
|
|
||||||
|
function concatIfNotUndefined(concatTo, concatting) {
|
||||||
|
if (typeof concatting !== "undefined") {
|
||||||
|
concatTo += `${concatting}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Block {
|
class Block {
|
||||||
constructor(timestamp, lastHash, hash, data, nonce, difficulty) {
|
constructor(timestamp, lastHash, hash, reward, transactions, metadatas, nonce, difficulty) {
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.lastHash = lastHash;
|
this.lastHash = lastHash;
|
||||||
this.hash = hash;
|
this.hash = hash;
|
||||||
this.data = data;
|
this.reward = reward;
|
||||||
|
this.transactions = transactions;
|
||||||
|
this.metadatas = metadatas;
|
||||||
this.nonce = nonce;
|
this.nonce = nonce;
|
||||||
if (difficulty === undefined) {
|
if (difficulty === undefined) {
|
||||||
this.difficulty = DIFFICULTY;
|
this.difficulty = DIFFICULTY;
|
||||||
|
@ -15,54 +23,90 @@ class Block {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getTransactions(block) {
|
||||||
|
if (typeof block.transactions !== "undefined" && block.transactions !== null) {
|
||||||
|
return block.transactions;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getMetadatas(block) {
|
||||||
|
if (typeof block.metadatas !== "undefined" && block.metadatas !== null) {
|
||||||
|
return block.metadatas;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `Block -
|
return `Block -
|
||||||
Timestamp : ${this.timestamp}
|
Timestamp : ${this.timestamp}
|
||||||
Last Hash : ${this.lastHash.substring(0, 10)}
|
Last Hash : ${this.lastHash.substring(0, 10)}
|
||||||
Hash : ${this.hash.substring(0, 10)}
|
Hash : ${this.hash.substring(0, 10)}
|
||||||
Nonce : ${this.nonce}
|
Nonce : ${this.nonce}
|
||||||
Difficulty: ${this.difficulty}
|
Difficulty : ${this.difficulty}
|
||||||
Data : ${this.data}`;
|
Reward : ${this.reward}
|
||||||
|
Transactions : ${this.transactions}
|
||||||
|
Metadatas : ${this.metadatas}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static genesis() {
|
static genesis() {
|
||||||
return new this('Genesis time', '-----', 'f1r57-h45h', [], 0, DIFFICULTY);
|
return new this('Genesis time', '-----', 'f1r57-h45h', null, null, null, 0, DIFFICULTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
//returns false if hash doesn't match
|
static hash(timestamp, lastHash, reward, transactions, metadatas, nonce, difficulty) {
|
||||||
static checkHash(hash, timestamp, lastHash, data, nonce, difficulty) {
|
//backwards compatible hashing:
|
||||||
const computedHash = Block.hash(timestamp, lastHash, data, nonce, difficulty);
|
//if we add a new type of thing to the chain, the hash of previous blocks won't change as if will be undefined
|
||||||
|
let hashing = `${timestamp}${lastHash}${nonce}${difficulty}`;
|
||||||
|
concatIfNotUndefined(hashing, reward);
|
||||||
|
concatIfNotUndefined(hashing, transactions);
|
||||||
|
concatIfNotUndefined(hashing, metadatas);
|
||||||
|
|
||||||
if (computedHash !== hash) {
|
return ChainUtil.hash(hashing).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static blockHash(block) {
|
||||||
|
return Block.hash(
|
||||||
|
block.timestamp,
|
||||||
|
block.lastHash,
|
||||||
|
block.reward,
|
||||||
|
block.transactions,
|
||||||
|
block.metadatas,
|
||||||
|
block.nonce,
|
||||||
|
block.difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
//returns false if block's hash doesn't match internals
|
||||||
|
static checkHash(block) {
|
||||||
|
|
||||||
|
const computedHash = Block.hash(
|
||||||
|
block.timestamp,
|
||||||
|
block.lastHash,
|
||||||
|
block.reward,
|
||||||
|
block.transactions,
|
||||||
|
block.metadatas,
|
||||||
|
block.nonce,
|
||||||
|
block.difficulty);
|
||||||
|
|
||||||
|
if (computedHash !== block.hash) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hash.substring(0, difficulty) !== '0'.repeat(difficulty)) {
|
if (block.hash.substring(0, block.difficulty) !== '0'.repeat(block.difficulty)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static hash(timestamp, lastHash, data, nonce, difficulty) {
|
|
||||||
return ChainUtil.hash(`${timestamp}${lastHash}${data}${nonce}${difficulty}`).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
static blockHash(block) {
|
|
||||||
const { timestamp, lastHash, data, nonce, difficulty } = block;
|
|
||||||
return Block.hash(timestamp, lastHash, data, nonce, difficulty);
|
|
||||||
}
|
|
||||||
|
|
||||||
//returns false if block's hash doesn't match internals
|
|
||||||
static checkBlock(block) {
|
|
||||||
return Block.checkHash(block.hash, block.timestamp, block.lastHash, block.data, block.nonce, block.difficulty);
|
|
||||||
}
|
|
||||||
|
|
||||||
static adjustDifficulty(lastBlock, currentTime) {
|
static adjustDifficulty(lastBlock, currentTime) {
|
||||||
let { difficulty } = lastBlock;
|
let prevDifficulty = lastBlock.difficulty;
|
||||||
difficulty = lastBlock.timestamp + MINE_RATE > currentTime ?
|
if (lastBlock.timestamp + MINE_RATE > currentTime) {
|
||||||
difficulty + 1 : difficulty - 1;
|
return prevDifficulty + 1;
|
||||||
return Math.max(0, difficulty);
|
} else {
|
||||||
|
return Math.max(0, prevDifficulty - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,81 +1,276 @@
|
||||||
const Block = require('./block');
|
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 key in metadatas) {
|
||||||
|
const metadata = metadatas[key];
|
||||||
|
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(block.hash)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Blockchain {
|
class Blockchain {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.chain = [Block.genesis()];
|
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
|
//adds an existing block to the blockchain, returns false if the block can't be added, true if it was added
|
||||||
addBlock(newBlock) {
|
addBlock(newBlock) {
|
||||||
if (newBlock.lastHash !== this.chain[this.chain.length - 1].hash) {
|
const verifyResult = verifyBlock(this.balances, this.lastBlock(), newBlock);
|
||||||
console.log("Tried to add invalid block, last hash didn't match our last hash");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
//how to check if new block's timestamp is believable
|
|
||||||
if (newBlock.difficulty !== Block.adjustDifficulty(this.chain[this.chain.length - 1], newBlock.timestamp)) {
|
|
||||||
console.log("Tried to add invalid block, difficulty is incorrect");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!Block.checkBlock(newBlock)) {
|
|
||||||
console.log("Tried to add invalid block, block's hash doesn't match its contents");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!verifyResult.result) {
|
||||||
|
console.log(`Couldn't add block: ${verifyResult.reason}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//all seems to be good, persist
|
||||||
this.chain.push(newBlock);
|
this.chain.push(newBlock);
|
||||||
|
|
||||||
console.log("Added new block: ");
|
for (const publicKey in verifyResult.changedBalances) {
|
||||||
|
this.balances[publicKey] = verifyResult.changedBalances[publicKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
addBlockMetadata(this, newBlock);
|
||||||
|
|
||||||
|
//console.log("Added new block");
|
||||||
//console.log(newBlock);
|
//console.log(newBlock);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
isValidChain(chain) {
|
static isValidChain(chain) {
|
||||||
if (chain.length === 0) {
|
const res = verifyChain(chain);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (JSON.stringify(chain[0]) !== JSON.stringify(Block.genesis())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i=1; i<chain.length; i++) {
|
return res.result;
|
||||||
const block = chain[i];
|
|
||||||
const lastBlock = chain[i-1];
|
|
||||||
|
|
||||||
if (block.lastHash !== lastBlock.hash ||
|
|
||||||
block.hash !== Block.blockHash(block)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!Block.checkBlock(block)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//return null on fail, returns the index of where they differ
|
//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) {
|
replaceChain(newChain) {
|
||||||
if (newChain.length <= this.chain.length) {
|
if (newChain.length <= this.chain.length) {
|
||||||
console.log('Received chain is not longer than the current chain.');
|
return {
|
||||||
return false;
|
result: false,
|
||||||
} else if (!this.isValidChain(newChain)) {
|
reason: "Received chain is not longer than the current chain."
|
||||||
console.log('The received chain is not valid.');
|
};
|
||||||
return false;
|
}
|
||||||
|
const verifyResult = verifyChain(newChain);
|
||||||
|
if (!verifyResult.result) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason: `The received chain is not valid: ${verifyResult.reason}`
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Replacing blockchain with the new chain.');
|
//Replacing blockchain with the new chain
|
||||||
|
|
||||||
const oldChain = this.chain;
|
const oldChain = this.chain;
|
||||||
this.chain = newChain;
|
this.chain = newChain;
|
||||||
|
|
||||||
//find where they differ
|
//find where they differ
|
||||||
for (let i = 1; i < oldChain.length; ++i) {
|
const chainDifference = findChainDifference(oldChain, newChain);
|
||||||
if (oldChain[i].hash !== newChain[i].hash) {
|
console.log(`chain difference was ${chainDifference}`);
|
||||||
return i;
|
|
||||||
}
|
//fix metadata
|
||||||
|
for (let i = oldChain.length - 1; i >= chainDifference; i--) {
|
||||||
|
this.store.deleteGraph(oldChain[i].hash);
|
||||||
}
|
}
|
||||||
//if they didn't differ in the length of the old chain, must be one after
|
for (let i = chainDifference; i < newChain.length; ++i) {
|
||||||
return oldChain.length;
|
addBlockMetadata(this, newChain[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//fix balance
|
||||||
|
this.balances = verifyResult.balances;
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: true,
|
||||||
|
chainDifference: chainDifference,
|
||||||
|
oldChain: oldChain
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,14 @@ class ChainUtil {
|
||||||
static verifySignature(publicKey, signature, dataHash) {
|
static verifySignature(publicKey, signature, dataHash) {
|
||||||
return ec.keyFromPublic(publicKey, 'hex').verify(dataHash, signature);
|
return ec.keyFromPublic(publicKey, 'hex').verify(dataHash, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static deserializeKeyPair(serialized) {
|
||||||
|
return ec.keyFromPrivate(serialized, 'hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
static serializeKeyPair(keyPair) {
|
||||||
|
return keyPair.getPrivate().toString('hex');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ChainUtil;
|
module.exports = ChainUtil;
|
3
settings.json
Normal file
3
settings.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"wallet-private-key": "557360924eae1b0a7ff4727c4226300788b2e98e50b91195f1048e671d4d5d6e"
|
||||||
|
}
|
|
@ -5,9 +5,9 @@ const ChainUtil = require('../chain-util');
|
||||||
|
|
||||||
class Wallet {
|
class Wallet {
|
||||||
constructor(keyPair) {
|
constructor(keyPair) {
|
||||||
this.balance = INITIAL_BALANCE;
|
|
||||||
this.keyPair = keyPair;
|
this.keyPair = keyPair;
|
||||||
this.publicKey = this.keyPair.getPublic().encode('hex');
|
this.publicKey = this.keyPair.getPublic().encode('hex');
|
||||||
|
this.counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
|
@ -20,63 +20,72 @@ class Wallet {
|
||||||
return this.keyPair.sign(dataHash);
|
return this.keyPair.sign(dataHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
createTransaction(recipient, amount, blockchain, transactionPool) {
|
createTransaction(recipient, amount, blockchain) {
|
||||||
this.balance = this.calculateBalance(blockchain);
|
const balance = blockchain.getBalanceCopy(this.publicKey);
|
||||||
|
|
||||||
if (amount > this.balance) {
|
if (balance.counter > this.counter) {
|
||||||
console.log(`Amount: ${amount} exceceds current balance: ${this.balance}`);
|
this.counter = balance.counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount > balance.balance) {
|
||||||
|
console.log(`Amount: ${amount} exceceds current balance: ${balance.balance}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Transaction.newTransaction(this, recipient, amount);
|
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) {
|
createMetadata(SSNmetadata) {
|
||||||
return Metadata.newMetadata(this, SSNmetadata);
|
return Metadata.newMetadata(this, SSNmetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateBalance(blockchain) {
|
//calculateBalance(blockchain) {
|
||||||
let balance = this.balance;
|
// let balance = this.balance;
|
||||||
let transactions = [];
|
// let transactions = [];
|
||||||
blockchain.chain.forEach(block => block.data.forEach(transaction => {
|
// blockchain.chain.forEach(block => block.data.forEach(transaction => {
|
||||||
transactions.push(transaction);
|
// transactions.push(transaction);
|
||||||
}));
|
// }));
|
||||||
console.log("transactions of balance")
|
// console.log("transactions of balance")
|
||||||
console.log(transactions);
|
// console.log(transactions);
|
||||||
const PaymentTransactions = transactions[0];
|
// const PaymentTransactions = transactions[0];
|
||||||
console.log("Payment transactions ")
|
// console.log("Payment transactions ")
|
||||||
console.log(PaymentTransactions);
|
// console.log(PaymentTransactions);
|
||||||
const walletInputTs = PaymentTransactions.filter(transaction => transaction.input.address === this.publicKey);
|
// const walletInputTs = PaymentTransactions.filter(transaction => transaction.input.address === this.publicKey);
|
||||||
|
|
||||||
let startTime = 0;
|
// let startTime = 0;
|
||||||
|
|
||||||
if (walletInputTs.length > 0) {
|
// if (walletInputTs.length > 0) {
|
||||||
const recentInputT = walletInputTs.reduce(
|
// const recentInputT = walletInputTs.reduce(
|
||||||
(prev, current) => prev.input.timestamp > current.input.timestamp ? prev : current
|
// (prev, current) => prev.input.timestamp > current.input.timestamp ? prev : current
|
||||||
);
|
// );
|
||||||
|
|
||||||
balance = recentInputT.outputs.find(output => output.address === this.publicKey).amount;
|
// balance = recentInputT.outputs.find(output => output.address === this.publicKey).amount;
|
||||||
startTime = recentInputT.input.timestamp;
|
// startTime = recentInputT.input.timestamp;
|
||||||
}
|
// }
|
||||||
|
|
||||||
PaymentTransactions.forEach(transaction => {
|
// PaymentTransactions.forEach(transaction => {
|
||||||
if (transaction.input.timestamp > startTime) {
|
// if (transaction.input.timestamp > startTime) {
|
||||||
transaction.outputs.find(output => {
|
// transaction.outputs.find(output => {
|
||||||
if (output.address === this.publicKey) {
|
// if (output.address === this.publicKey) {
|
||||||
balance += output.amount;
|
// balance += output.amount;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
return balance;
|
// return balance;
|
||||||
}
|
//}
|
||||||
|
|
||||||
static blockchainWallet() {
|
//static blockchainWallet() {
|
||||||
const blockchainWallet = new this(ChainUtil.genKeyPair());
|
// const blockchainWallet = new this(ChainUtil.genKeyPair());
|
||||||
blockchainWallet.address = 'blockchain-wallet';
|
// blockchainWallet.address = 'blockchain-wallet';
|
||||||
return blockchainWallet;
|
// return blockchainWallet;
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Wallet;
|
module.exports = Wallet;
|
||||||
|
|
|
@ -18,12 +18,14 @@ describe('Wallet', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sendAmount = 50;
|
sendAmount = 50;
|
||||||
recipient = 'r4nd0m-4ddr355';
|
recipient = 'r4nd0m-4ddr355';
|
||||||
transaction = wallet.createTransaction(recipient, sendAmount, bc, tp);
|
transaction = wallet.createTransaction(recipient, sendAmount, bc);
|
||||||
|
tp.updateOrAddTransaction(transaction);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and doing the same transaction', () => {
|
describe('and doing the same transaction', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wallet.createTransaction(recipient, sendAmount, bc, tp);
|
transaction = wallet.createTransaction(recipient, sendAmount, bc);
|
||||||
|
tp.updateOrAddTransaction(transaction);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('doubles the `sendAmount` subtracted from the wallet balance', () => {
|
it('doubles the `sendAmount` subtracted from the wallet balance', () => {
|
||||||
|
@ -46,7 +48,8 @@ describe('Wallet', () => {
|
||||||
addBalance = 100;
|
addBalance = 100;
|
||||||
repeatAdd = 3;
|
repeatAdd = 3;
|
||||||
for (let i=0; i<repeatAdd; i++) {
|
for (let i=0; i<repeatAdd; i++) {
|
||||||
senderWallet.createTransaction(wallet.publicKey, addBalance, bc, tp);
|
const transaction = senderWallet.createTransaction(wallet.publicKey, addBalance, bc);
|
||||||
|
tp.updateOrAddTransaction(transaction);
|
||||||
}
|
}
|
||||||
bc.addBlock(tp.transactions);
|
bc.addBlock(tp.transactions);
|
||||||
});
|
});
|
||||||
|
@ -66,14 +69,16 @@ describe('Wallet', () => {
|
||||||
tp.clear();
|
tp.clear();
|
||||||
subtractBalance = 60;
|
subtractBalance = 60;
|
||||||
recipientBalance = wallet.calculateBalance(bc);
|
recipientBalance = wallet.calculateBalance(bc);
|
||||||
wallet.createTransaction(senderWallet.publicKey, subtractBalance, bc, tp);
|
const transaction = wallet.createTransaction(senderWallet.publicKey, subtractBalance, bc);
|
||||||
|
tp.updateOrAddTransaction(transaction);
|
||||||
bc.addBlock(tp.transactions);
|
bc.addBlock(tp.transactions);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and the sender sends another transaction to the recipient', () => {
|
describe('and the sender sends another transaction to the recipient', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tp.clear();
|
tp.clear();
|
||||||
senderWallet.createTransaction(wallet.publicKey, addBalance, bc, tp);
|
const transaction = senderWallet.createTransaction(wallet.publicKey, addBalance, bc);
|
||||||
|
tp.updateOrAddTransaction(transaction);
|
||||||
bc.addBlock(tp.transactions);
|
bc.addBlock(tp.transactions);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -63,9 +63,7 @@ class Metadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
static newMetadata(senderWallet,SSNmetadata){
|
static newMetadata(senderWallet,SSNmetadata){
|
||||||
return Metadata.MetadataOfIoTDevice(senderWallet, SSNmetadata
|
return Metadata.MetadataOfIoTDevice(senderWallet, SSNmetadata);
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static signMetadata (metadata, senderWallet) {
|
static signMetadata (metadata, senderWallet) {
|
||||||
|
@ -76,14 +74,13 @@ class Metadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static verifyMetadata(metadata) {
|
||||||
static verifyMetadata(metadata) {
|
return ChainUtil.verifySignature(
|
||||||
return ChainUtil.verifySignature(
|
metadata.Signiture.address,
|
||||||
metadata.Signiture.address,
|
metadata.Signiture.signature,
|
||||||
metadata.Signiture.signature,
|
ChainUtil.hash(metadata.SSNmetadata)
|
||||||
ChainUtil.hash(metadata.SSNmetadata)
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Metadata;
|
module.exports = Metadata;
|
|
@ -1,5 +1,6 @@
|
||||||
const Transaction = require('../wallet/transaction');
|
const Transaction = require('../wallet/transaction');
|
||||||
const Metadata = require('../wallet/metadata')
|
const Metadata = require('../wallet/metadata');
|
||||||
|
const Block = require('../blockchain/block');
|
||||||
|
|
||||||
const Return = {
|
const Return = {
|
||||||
add: 1,
|
add: 1,
|
||||||
|
@ -10,16 +11,16 @@ const Return = {
|
||||||
class TransactionPool {
|
class TransactionPool {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.transactions = [];
|
this.transactions = [];
|
||||||
this.metadataS =[];
|
this.metadatas = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
//returns true on update, false on add
|
//returns true on update, false on add
|
||||||
updateOrAddTransaction(transaction) {
|
updateOrAddTransaction(transaction) {
|
||||||
if (!Transaction.verifyTransaction(transaction)) {
|
if (!Transaction.verify(transaction)) {
|
||||||
console.log("Couldn't update or add transaction, transaction couldn't be verified");
|
console.log("Couldn't update or add transaction, transaction couldn't be verified");
|
||||||
return Return.error;
|
return Return.error;
|
||||||
}
|
}
|
||||||
const foundIndex = this.transactions.findIndex(t => t.id === transaction.id);
|
const foundIndex = this.transactions.findIndex(t => t.input === transaction.input && t.counter === transaction.counter);
|
||||||
|
|
||||||
if (foundIndex !== -1) {
|
if (foundIndex !== -1) {
|
||||||
this.transactions[foundIndex] = transaction;
|
this.transactions[foundIndex] = transaction;
|
||||||
|
@ -36,13 +37,13 @@ class TransactionPool {
|
||||||
return Return.error;
|
return Return.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundIndex = this.metadataS.findIndex(t => t.id === metadata.id);
|
const foundIndex = this.metadatas.findIndex(t => t.id === metadata.id);
|
||||||
|
|
||||||
if (foundIndex !== -1) {
|
if (foundIndex !== -1) {
|
||||||
this.metadataS[foundIndex] = metadata;
|
this.metadatas[foundIndex] = metadata;
|
||||||
return Return.update;
|
return Return.update;
|
||||||
} else {
|
} else {
|
||||||
this.metadataS.push(metadata);
|
this.metadatas.push(metadata);
|
||||||
return Return.add;
|
return Return.add;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,62 +53,41 @@ class TransactionPool {
|
||||||
}
|
}
|
||||||
|
|
||||||
existingMetadata(address) {
|
existingMetadata(address) {
|
||||||
return this.metadataS.find(t => t.Signiture.address === address);
|
return this.metadatas.find(t => t.Signiture.address === address);
|
||||||
}
|
}
|
||||||
|
|
||||||
validTransactions() {
|
//we could check for possible double spends here
|
||||||
return this.transactions.filter(transaction => {
|
validTransactionsCopy() {
|
||||||
const outputTotal = transaction.outputs.reduce((total, output) => {
|
return [...this.transactions];
|
||||||
return total + output.amount;
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
if (transaction.input.amount !== outputTotal) {
|
|
||||||
console.log(`Invalid transaction from ${transaction.input.address}.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Transaction.verifyTransaction(transaction)) {
|
|
||||||
console.log(`Invalid signature from ${transaction.input.address}.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return transaction;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validMetadataS(){
|
validMetadatasCopy(){
|
||||||
return this.metadataS.filter(metadata => {
|
return [...this.metadatas];
|
||||||
if (!Metadata.verifyMetadata(metadata)) {
|
|
||||||
console.log(`Invalid signature from ${metadata.Signiture.address}.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return metadata;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearFromBlock(block) {
|
clearFromBlock(block) {
|
||||||
const transactions = block.data[0];
|
const blockTransactions = Block.getTransactions(block);
|
||||||
const metadatas = block.data[1];
|
const blockMetadatas = Block.getMetadatas(block);
|
||||||
for (const transaction of transactions) {
|
|
||||||
|
for (const transaction of blockTransactions) {
|
||||||
const foundTransaction = this.transactions.findIndex(t => t.id === transaction.id);
|
const foundTransaction = this.transactions.findIndex(t => t.id === transaction.id);
|
||||||
|
|
||||||
if (foundTransaction !== -1) {
|
if (foundTransaction !== -1) {
|
||||||
this.transactions.splice(foundTransaction, 1);
|
this.transactions.splice(foundTransaction, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const metadata of blockMetadatas) {
|
||||||
for (const metadata of metadatas) {
|
const foundMetadata = this.metadatas.findIndex(m => m.id === metadata.id);
|
||||||
const foundMetadata = this.metadataS.findIndex(m => m.id === metadata.id);
|
|
||||||
|
|
||||||
if (foundMetadata !== -1) {
|
if (foundMetadata !== -1) {
|
||||||
this.metadataS.splice(foundMetadata, 1);
|
this.metadatas.splice(foundMetadata, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAll() {
|
clearAll() {
|
||||||
this.transactions = [];
|
this.transactions = [];
|
||||||
this.metadataS = [];
|
this.metadatas = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,66 +2,77 @@ const ChainUtil = require('../chain-util');
|
||||||
const { MINING_REWARD } = require('../config');
|
const { MINING_REWARD } = require('../config');
|
||||||
|
|
||||||
class Transaction {
|
class Transaction {
|
||||||
constructor() {
|
constructor(senderPublicKey, counter, outputs) {
|
||||||
this.id = ChainUtil.id();
|
this.input = senderPublicKey;
|
||||||
this.input = null;
|
this.signature = null;
|
||||||
this.outputs = [];
|
this.counter = counter;
|
||||||
|
this.outputs = outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(senderWallet, recipient, amount) {
|
addSignature(signature) {
|
||||||
const senderOutput = this.outputs.find(output => output.address === senderWallet.publicKey);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
if (amount > senderOutput.amount) {
|
static hashToSign(transaction) {
|
||||||
console.log(`Amount: ${amount} exceeds balance.`);
|
return ChainUtil.hash({
|
||||||
return;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
senderOutput.amount = senderOutput.amount - amount;
|
|
||||||
this.outputs.push({ amount, address: recipient });
|
|
||||||
Transaction.signTransaction(this, senderWallet);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
static transactionWithOutputs(senderWallet, outputs) {
|
|
||||||
const transaction = new this();
|
|
||||||
transaction.outputs.push(...outputs);
|
|
||||||
Transaction.signTransaction(transaction, senderWallet);
|
|
||||||
return transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
static newTransaction(senderWallet, recipient, amount) {
|
|
||||||
if (amount > senderWallet.balance) {
|
|
||||||
console.log(`Amount: ${amount} exceeds balance.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Transaction.transactionWithOutputs(senderWallet, [
|
|
||||||
{ amount: senderWallet.balance - amount, address: senderWallet.publicKey },
|
|
||||||
{ amount, address: recipient }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static rewardTransaction(minerWallet, blockchainWallet) {
|
|
||||||
return Transaction.transactionWithOutputs(blockchainWallet, [{
|
|
||||||
amount: MINING_REWARD, address: minerWallet.publicKey
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static signTransaction(transaction, senderWallet) {
|
|
||||||
transaction.input = {
|
|
||||||
timestamp: Date.now(),
|
|
||||||
amount: senderWallet.balance,
|
|
||||||
address: senderWallet.publicKey,
|
|
||||||
signature: senderWallet.sign(ChainUtil.hash(transaction.outputs))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static verifyTransaction(transaction) {
|
|
||||||
return ChainUtil.verifySignature(
|
return ChainUtil.verifySignature(
|
||||||
transaction.input.address,
|
transaction.input,
|
||||||
transaction.input.signature,
|
transaction.signature,
|
||||||
ChainUtil.hash(transaction.outputs)
|
Transaction.hashToSign(transaction)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue