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
|
@ -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,109 +0,0 @@
|
|||
const Transaction = require('./transaction');
|
||||
const Metadata = require('./metadata');
|
||||
const Wallet = require('./index');
|
||||
const { MINING_REWARD } = require('../config');
|
||||
|
||||
describe('Transaction & Metadata', () => {
|
||||
let transaction, metadata, wallet, recipient, amount,
|
||||
senderWallet,Name,Geo ,IP_URL , Topic_Token, Permission,
|
||||
RequestDetail, OrgOwner, DepOwner,PrsnOwner, PaymentPerKbyte,
|
||||
PaymentPerMinute, Protocol, MessageAttributes, Interval,
|
||||
FurtherDetails, SSNmetadata;
|
||||
|
||||
beforeEach(() => {
|
||||
wallet = new Wallet();
|
||||
amount = 50;
|
||||
recipient = 'r3c1p13nt';
|
||||
senderWallet = new Wallet();
|
||||
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'
|
||||
transaction = Transaction.newTransaction(wallet, recipient, amount);
|
||||
metadata = Metadata.newMetadata(senderWallet,Name,Geo ,IP_URL , Topic_Token, Permission,
|
||||
RequestDetail, OrgOwner, DepOwner,PrsnOwner, PaymentPerKbyte,
|
||||
PaymentPerMinute, Protocol, MessageAttributes, Interval,
|
||||
FurtherDetails, SSNmetadata)
|
||||
});
|
||||
|
||||
it('outputs the `amount` subtracted from the wallet balance', () => {
|
||||
expect(transaction.outputs.find(output => output.address === wallet.publicKey).amount)
|
||||
.toEqual(wallet.balance - amount);
|
||||
});
|
||||
|
||||
it('outputs the `amount` added to the recipient', () => {
|
||||
expect(transaction.outputs.find(output => output.address === recipient).amount)
|
||||
.toEqual(amount);
|
||||
});
|
||||
|
||||
it('inputs the balance of the wallet', () => {
|
||||
expect(transaction.input.amount).toEqual(wallet.balance);
|
||||
});
|
||||
|
||||
it('validates a valid transaction', () => {
|
||||
expect(Transaction.verifyTransaction(transaction)).toBe(true);
|
||||
});
|
||||
|
||||
it('validates a valid metadata', () => {
|
||||
expect(Metadata.verifyMetadata(metadata)).toBe(true);
|
||||
});
|
||||
|
||||
it('invalidates a corrupt transaction', () => {
|
||||
transaction.outputs[0].amount = 50000;
|
||||
expect(Transaction.verifyTransaction(transaction)).toBe(false);
|
||||
});
|
||||
|
||||
describe('transacting with an amount that exceeds the balance', () => {
|
||||
beforeEach(() => {
|
||||
amount = 50000;
|
||||
transaction = Transaction.newTransaction(wallet, recipient, amount);
|
||||
});
|
||||
|
||||
it('does not create the transaction', () => {
|
||||
expect(transaction).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and updating a transaction', () => {
|
||||
let nextAmount, nextRecipient;
|
||||
|
||||
beforeEach(() => {
|
||||
nextAmount = 20;
|
||||
nextRecipient = 'n3xt-4ddr355';
|
||||
transaction = transaction.update(wallet, nextRecipient, nextAmount);
|
||||
});
|
||||
|
||||
it(`subtracts the next amount from the sender's output`, () => {
|
||||
expect(transaction.outputs.find(output => output.address === wallet.publicKey).amount)
|
||||
.toEqual(wallet.balance - amount - nextAmount);
|
||||
});
|
||||
|
||||
it('outputs an amount for the next recipient', () => {
|
||||
expect(transaction.outputs.find(output => output.address === nextRecipient).amount)
|
||||
.toEqual(nextAmount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('creating a reward transaction', () => {
|
||||
beforeEach(() => {
|
||||
transaction = Transaction.rewardTransaction(wallet, Wallet.blockchainWallet());
|
||||
});
|
||||
|
||||
it(`reward the miner's wallet`, () => {
|
||||
expect(transaction.outputs.find(output => output.address === wallet.publicKey).amount)
|
||||
.toEqual(MINING_REWARD);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
|
@ -1,79 +0,0 @@
|
|||
const Transaction = require('./transaction');
|
||||
const Wallet = require('./index');
|
||||
const { MINING_REWARD } = require('../config');
|
||||
|
||||
describe('Transaction', () => {
|
||||
let transaction, wallet, recipient, amount;
|
||||
|
||||
beforeEach(() => {
|
||||
wallet = new Wallet();
|
||||
amount = 50;
|
||||
recipient = 'r3c1p13nt';
|
||||
transaction = Transaction.newTransaction(wallet, recipient, amount);
|
||||
});
|
||||
|
||||
it('outputs the `amount` subtracted from the wallet balance', () => {
|
||||
expect(transaction.outputs.find(output => output.address === wallet.publicKey).amount)
|
||||
.toEqual(wallet.balance - amount);
|
||||
});
|
||||
|
||||
it('outputs the `amount` added to the recipient', () => {
|
||||
expect(transaction.outputs.find(output => output.address === recipient).amount)
|
||||
.toEqual(amount);
|
||||
});
|
||||
|
||||
it('inputs the balance of the wallet', () => {
|
||||
expect(transaction.input.amount).toEqual(wallet.balance);
|
||||
});
|
||||
|
||||
it('validates a valid transaction', () => {
|
||||
expect(Transaction.verifyTransaction(transaction)).toBe(true);
|
||||
});
|
||||
|
||||
it('invalidates a corrupt transaction', () => {
|
||||
transaction.outputs[0].amount = 50000;
|
||||
expect(Transaction.verifyTransaction(transaction)).toBe(false);
|
||||
});
|
||||
|
||||
describe('transacting with an amount that exceeds the balance', () => {
|
||||
beforeEach(() => {
|
||||
amount = 50000;
|
||||
transaction = Transaction.newTransaction(wallet, recipient, amount);
|
||||
});
|
||||
|
||||
it('does not create the transaction', () => {
|
||||
expect(transaction).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and updating a transaction', () => {
|
||||
let nextAmount, nextRecipient;
|
||||
|
||||
beforeEach(() => {
|
||||
nextAmount = 20;
|
||||
nextRecipient = 'n3xt-4ddr355';
|
||||
transaction = transaction.update(wallet, nextRecipient, nextAmount);
|
||||
});
|
||||
|
||||
it(`subtracts the next amount from the sender's output`, () => {
|
||||
expect(transaction.outputs.find(output => output.address === wallet.publicKey).amount)
|
||||
.toEqual(wallet.balance - amount - nextAmount);
|
||||
});
|
||||
|
||||
it('outputs an amount for the next recipient', () => {
|
||||
expect(transaction.outputs.find(output => output.address === nextRecipient).amount)
|
||||
.toEqual(nextAmount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('creating a reward transaction', () => {
|
||||
beforeEach(() => {
|
||||
transaction = Transaction.rewardTransaction(wallet, Wallet.blockchainWallet());
|
||||
});
|
||||
|
||||
it(`reward the miner's wallet`, () => {
|
||||
expect(transaction.outputs.find(output => output.address === wallet.publicKey).amount)
|
||||
.toEqual(MINING_REWARD);
|
||||
});
|
||||
});
|
||||
});
|
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 TransactionPool = require('./transaction-pool');
|
||||
const Blockchain = require('../blockchain');
|
||||
const ChainUtil = require('../chain-util');
|
||||
const { INITIAL_BALANCE } = require('../config');
|
||||
|
||||
describe('Wallet', () => {
|
||||
let wallet, tp, bc;
|
||||
let wallet, bc;
|
||||
|
||||
beforeEach(() => {
|
||||
wallet = new Wallet();
|
||||
tp = new TransactionPool();
|
||||
wallet = new Wallet(ChainUtil.genKeyPair());
|
||||
bc = new Blockchain();
|
||||
});
|
||||
|
||||
|
@ -28,6 +27,7 @@ describe('Wallet', () => {
|
|||
tp.updateOrAddTransaction(transaction);
|
||||
});
|
||||
|
||||
//?
|
||||
it('doubles the `sendAmount` subtracted from the wallet balance', () => {
|
||||
expect(transaction.outputs.find(output => output.address === wallet.publicKey).amount)
|
||||
.toEqual(wallet.balance - sendAmount * 2);
|
Loading…
Add table
Add a link
Reference in a new issue