v6 initial commit, maybe more to follow

This commit is contained in:
Josip Milovac 2022-11-22 13:30:19 +11:00
parent 7f91be86c0
commit d6a32870bc
34 changed files with 16875 additions and 0 deletions

BIN
wallet/.DS_Store vendored Normal file

Binary file not shown.

67
wallet/CoinTransaction.js Normal file
View file

@ -0,0 +1,67 @@
const ChainUtil = require('../chain-util');
const { MINING_REWARD } = require('../config');
class CoinTransaction {
constructor() {
this.id = ChainUtil.id();
this.input = null;
this.outputs = [];
}
update(senderWallet, recipient, amount) {
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 });
CoinTransaction.signCoinTransaction(this, senderWallet);
return this;
}
static CoinTransactionWithOutputs(senderWallet, outputs) {
const cointransaction = new this();
cointransaction.outputs.push(...outputs);
CoinTransaction.signCoinTransaction(cointransaction, senderWallet);
return cointransaction;
}
static newCoinTransaction(senderWallet, recipient, amount) {
if (amount > senderWallet.balance) {
console.log(`Amount: ${amount} exceeds balance.`);
return;
}
return CoinTransaction.CoinTransactionWithOutputs(senderWallet, [
{ amount: senderWallet.balance - amount, address: senderWallet.publicKey},
{ amount, address: recipient }]);
}
static rewardCoinTransaction(minerWallet, blockchainWallet) {
return CoinTransaction.CoinTransactionWithOutputs(blockchainWallet, [{
amount: MINING_REWARD, address: minerWallet.publicKey
}]);
}
static signCoinTransaction(cointransaction, senderWallet) {
cointransaction.input = {
timestamp: Date.now(),
amount: senderWallet.balance,
address: senderWallet.publicKey,
signature: senderWallet.sign(ChainUtil.hash(cointransaction.outputs))
}
}
static verifyCoinTransaction(cointransaction) {
return ChainUtil.verifySignature(
cointransaction.input.address,
cointransaction.input.signature,
ChainUtil.hash(cointransaction.outputs)
);
}
}
module.exports = CoinTransaction;

View file

@ -0,0 +1,100 @@
const ChainUtil = require('../chain-util');
class MetaDataTransaction {
constructor() {
this.id = null; // if there is a problem in the metadata transaction, change null with ChainUtil.id();
this.Signiture = null;
this.Name = null;
this.Geo = [];
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;
// this.Geo = null;
// this.Std = null;
// this.name= null;
// this.MetaHash= null;
// this.file=null;
}
// update(senderWallet, Geo, URI, Name,Permission, OrgOwner, SSNmetadata) {
// this.Geo = Geo;
// this.URI = URI;
// this.Name = Name;
// this.Permission = Permission;
// this.OrgOwner = OrgOwner;
// this.PrsnOwner = senderWallet.publicKey;
// this.MetaHash = ChainUtil.hash(SSNmetadata);
// this.SSNmetadata = SSNmetadata;
// MetaDatatransaction.signMetaDataTransaction(this, senderWallet);
// return this;
// }
static MetaDataTransactionWithIoT(senderWallet, Name,Geo ,IP_URL , Topic_Token, Permission, RequestDetail, OrgOwner, DepOwner,PrsnOwner, PaymentPerKbyte, PaymentPerMinute, Protocol, MessageAttributes, Intrval, FurtherDetails, SSNmetadata) {
const metaDataTransaction = new this();
metaDataTransaction.id = ChainUtil.id();
metaDataTransaction.Name = Name;
metaDataTransaction.Geo = Geo;
metaDataTransaction.IP_URL = IP_URL;
metaDataTransaction.Topic_Token = Topic_Token;
metaDataTransaction.Permission = Permission;
metaDataTransaction.RequestDetail = RequestDetail
metaDataTransaction.OrgOwner = OrgOwner;
metaDataTransaction.DepOwner = DepOwner;
metaDataTransaction.PrsnOwner = PrsnOwner;
metaDataTransaction.PaymentPerKbyte = PaymentPerKbyte ;
metaDataTransaction.PaymentPerMinute = PaymentPerMinute;
metaDataTransaction.Protocol = Protocol;
metaDataTransaction.MessageAttributes = MessageAttributes;
metaDataTransaction.MessageAttributes['DeviceID'] = metaDataTransaction.id;
metaDataTransaction.MessageAttributes['DeviceName'] = Name;
metaDataTransaction.MessageAttributes['Sensors'] =[{"SensorName":"","Value":"" , "Unit":""}];
metaDataTransaction.MessageAttributes['TimeStamp'] = "";
metaDataTransaction.Interval = Intrval;
metaDataTransaction.FurtherDetails = FurtherDetails;
metaDataTransaction.SSNmetadata = SSNmetadata;
metaDataTransaction.MetaHash = ChainUtil.hash(SSNmetadata);
MetaDataTransaction.signMetaDataTransaction(metaDataTransaction, senderWallet);
return metaDataTransaction;
}
static newMetaDataTransaction(senderWallet,Name,Geo ,IP_URL , Topic_Token, Permission, RequestDetail, OrgOwner, DepOwner,PrsnOwner, PaymentPerKbyte, PaymentPerMinute, Protocol, MessageAttributes, Interval, FurtherDetails, SSNmetadata){
return MetaDataTransaction.MetaDataTransactionWithIoT(senderWallet, Name,Geo ,IP_URL , Topic_Token, Permission, RequestDetail, OrgOwner, DepOwner,PrsnOwner, PaymentPerKbyte, PaymentPerMinute, Protocol, MessageAttributes, Interval, FurtherDetails, SSNmetadata
);
}
static signMetaDataTransaction (metaDataTransaction, senderWallet) {
metaDataTransaction.Signiture = {
timestamp: Date.now(),
address: senderWallet.publicKey,
signature: senderWallet.sign(ChainUtil.hash(metaDataTransaction.SSNmetadata))
}
}
static verifyMetaDataTransaction(metaDataTransaction) {
return ChainUtil.verifySignature(
metaDataTransaction.Signiture.address,
metaDataTransaction.Signiture.signature,
ChainUtil.hash(metaDataTransaction.SSNmetadata)
);
}
}
module.exports = MetaDataTransaction;

99
wallet/index.js Normal file
View file

@ -0,0 +1,99 @@
const ChainUtil = require('../chain-util');
const CoinTransaction = require('./CoinTransaction');
const { INITIAL_BALANCE } = require('../config');
const MetaDataTransaction = require('./MetaDataTransaction');
const transactionPool = require('./transaction-pool');
class Wallet {
constructor() {
this.balance = INITIAL_BALANCE;
this.keyPair = ChainUtil.genKeyPair();
this.publicKey = this.keyPair.getPublic().encode('hex');
}
toString() {
return `Wallet -
publicKey: ${this.publicKey.toString()}
balance : ${this.balance}`
}
sign(dataHash) {
return this.keyPair.sign(dataHash);
}
createCoinTransaction(recipient, amount, blockchain, transactionPool) {
// this.balance = this.calculateBalance(blockchain);
if (amount > this.balance) {
console.log(`Amount: ${amount} exceceds current balance: ${this.balance}`);
return;
}
let cointransaction = transactionPool.existingPaymentTransaction(this.publicKey);
if (cointransaction) {
cointransaction.update(this, recipient, amount);
} else { //this should be the original one
//just for test i make the transaction not to update if the sender is the same
cointransaction = CoinTransaction.newCoinTransaction(this, recipient, amount);
transactionPool.updateOrAddPaymentTransaction(cointransaction);
}
return cointransaction;
}
createMetaDataTransaction(Name,Geo ,IP_URL , Topic_Token, Permission, RequestDetail, OrgOwner, DepOwner,PrsnOwner, PaymentPerKbyte, PaymentPerMinute, Protocol, MessageAttributes, Interval, FurtherDetails, SSNmetadata, transactionPool){
/* let metaData = metaDataPool.existingMetaData(this.publicKey);
if (metaData) {
metaData.update(this, Geo, Std, Name,MetaHash,file);
} else {*/
const metaDataTransaction= MetaDataTransaction.newMetaDataTransaction(this, Name,Geo ,IP_URL , Topic_Token, Permission, RequestDetail, OrgOwner, DepOwner,PrsnOwner, PaymentPerKbyte, PaymentPerMinute, Protocol, MessageAttributes,Interval, FurtherDetails, SSNmetadata);
transactionPool.updateOrAddMetaDataTransaction(metaDataTransaction);
//}
return metaDataTransaction;
}
calculateBalance(blockchain) {
let balance = this.balance;
let cointransactions = [];
blockchain.chain.forEach(block => block.data.forEach(cointransaction => {
cointransactions.push(cointransaction);
}));
const walletInputTs = cointransactions
.filter(cointransaction => cointransaction.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;
}
cointransactions.forEach(cointransaction => {
if (cointransaction.input.timestamp > startTime) {
cointransaction.outputs.find(output => {
if (output.address === this.publicKey) {
balance += output.amount;
}
});
}
});
return balance;
}
static blockchainWallet() {
const blockchainWallet = new this();
blockchainWallet.address = 'blockchain-wallet';
return blockchainWallet;
}
}
module.exports = Wallet;

90
wallet/index.test.js Normal file
View file

@ -0,0 +1,90 @@
const Wallet = require('./index');
const TransactionPool = require('./transaction-pool');
const Blockchain = require('../blockchain');
const { INITIAL_BALANCE } = require('../config');
describe('Wallet', () => {
let wallet, tp, bc;
beforeEach(() => {
wallet = new Wallet();
tp = new TransactionPool();
bc = new Blockchain();
});
describe('creating a transaction', () => {
let transaction, sendAmount, recipient;
beforeEach(() => {
sendAmount = 50;
recipient = 'r4nd0m-4ddr355';
Geo = 20;
Std = 9014;
Name = 'temp';
MetaHash = '123abcd';
transaction = wallet.createCoinTransaction(recipient, sendAmount,Geo, Std, Name,MetaHash, bc, tp);
});
describe('and doing the same transaction', () => {
beforeEach(() => {
wallet.createCoinTransaction(recipient, sendAmount,Geo, Std, Name,MetaHash, bc, tp);
});
it('doubles the `sendAmount` subtracted from the wallet balance', () => {
expect(transaction.outputs.find(output => output.address === wallet.publicKey).amount)
.toEqual(wallet.balance - sendAmount * 2);
});
it('clones the `sendAmount` output for the recipient', () => {
expect(transaction.outputs.filter(output => output.address === recipient)
.map(output => output.amount)).toEqual([sendAmount, sendAmount]);
});
});
});
describe('calculating a balance', () => {
let addBalance, repeatAdd, senderWallet;
beforeEach(() => {
senderWallet = new Wallet();
addBalance = 100;
repeatAdd = 3;
for (let i=0; i<repeatAdd; i++) {
senderWallet.createCoinTransaction(wallet.publicKey, addBalance,Geo, Std, Name,MetaHash, bc, tp);
}
bc.addBlock(tp.paymenttransactions);
});
it('calculates the balance for blockchain transactions matching the recipient', () => {
expect(wallet.calculateBalance(bc)).toEqual(INITIAL_BALANCE + (addBalance * repeatAdd));
});
it('calculates the balance for blockchain transactions matching the sender', () => {
expect(senderWallet.calculateBalance(bc)).toEqual(INITIAL_BALANCE - (addBalance * repeatAdd));
});
describe('and the recipient conducts a transaction', () => {
let subtractBalance, recipientBalance;
beforeEach(() => {
tp.clear();
subtractBalance = 60;
recipientBalance = wallet.calculateBalance(bc);
wallet.createCoinTransaction(senderWallet.publicKey, subtractBalance,20,9014,'temp','123abc', bc, tp);
bc.addBlock(tp.paymenttransactions);
});
describe('and the sender sends another transaction to the recipient', () => {
beforeEach(() => {
tp.clear();
senderWallet.createCoinTransaction(wallet.publicKey, addBalance, 20,9014,'temp','123abc',bc, tp);
bc.addBlock(tp.paymenttransactions);
});
it('calculate the recipient balance only using transactions since its most recent one', () => {
expect(wallet.calculateBalance(bc)).toEqual(recipientBalance - subtractBalance + addBalance);
});
});
});
});
});

118
wallet/transaction-pool.js Normal file
View file

@ -0,0 +1,118 @@
const PaymntTransaction = require('./CoinTransaction');
const MetaDataTransaction = require('./MetaDataTransaction');
//const CompTransaction = require('./CompTransaction');
//const IntegrationTransaction = require('./IntegrationTransaction');
const { MaxNumOfPaymentTransactions, MaxNumOfMetadataTransactions,
MaxNumOfCompTransactions, MaxNumOfIntegrationTransactions}
= require('../config');
class TransactionPool {
constructor() {
this.paymenttransactions = [];
this.metaDataTransactions =[];
this.comptransactions = [];
this.integrationTransactions =[];
}
updateOrAddPaymentTransaction(paymenttransaction) {
let paymenttransactionWithId = this.paymenttransactions.find(t =>
t.id === paymenttransaction.id);
if (paymenttransactionWithId) {
this.paymenttransactions[this.paymenttransactions.indexOf
(paymenttransactionWithId)] = paymenttransaction;
} else {
this.paymenttransactions.push(paymenttransaction);
}
}
updateOrAddMetaDataTransaction(metaDataTransaction) {
let metaDataTransactionWithId = this.metaDataTransactions.find(t =>
t.id === metaDataTransaction.id);
if (metaDataTransactionWithId) {
this.metaDataTransactions[this.metaDataTransactions.indexOf
(metaDataTransactionWithId)] = metaDataTransaction;
} else {
this.metaDataTransactions.push(metaDataTransaction);
}
}
updateOrAddCompTransaction(comptransaction) {
let comptransactionWithId = this.comptransactions.find(t =>
t.id === comptransaction.id);
if (comptransactionWithId) {
this.comptransactions[this.comptransactions.indexOf
(comptransactionWithId)] = comptransaction;
} else {
this.comptransactions.push(comptransaction);
} }
updateOrAddIntegrationTransaction(integrationTransaction) {
let integrationTransactionWithId = this.integrationTransaction.find(
t => t.id === integrationTransaction.id);
if (integrationTransactionWithId) {
this.integrationTransactions[this.integrationTransactions.indexOf
(integrationTransactionWithId)] = integrationTransaction;
} else {
this.integrationTransactions.push(integrationTransaction);
}
}
existingPaymentTransaction(address) {
return this.paymenttransactions.find(t =>
t.input.address === address); }
existingMetaDataTransaction(address) {
return this.metaDataTransactions.find(t =>
t.Signiture.address === address);}
existingCompTransaction(address) {
return this.comptransactions.find(t =>
t.input.address === address); }
existingIntegrationTransaction(address) {
return this.integrationTransactions.find(t =>
t.Signiture.address === address);}
validPaymentTransactions() {
return this.paymenttransactions.filter(paymenttransaction => {
const outputTotal = paymenttransaction.outputs.reduce(
(total, output) => {
return total + output.amount;
}, 0);
if (paymenttransaction.input.amount !== outputTotal) {
console.log(`Invalid transaction from
${paymenttransaction.input.address}.`);
return;}
if (!PaymentTransaction.verifyPaymentTransaction(
paymenttransaction)) {
console.log(`Invalid signature from
${paymenttransaction.input.address}.`);
return;}
return paymenttransaction;
});
}
validMetaDataTransactions(){
if (!MetaDataTransaction.verifyMetaDataTransaction(
metaDataTransaction)) {
console.log(`Invalid signature from
${metaDataTransaction.Signiture.address}.`);
return;
}
return metaDataTransaction;
}
validCompTransactions(){
if (!CompTransaction.verifyCompTransaction(
CompTransaction)) {
console.log(`Invalid signature from
${CompTransaction.Signiture.address}.`);
return;
}
return compTransaction;
}
validIntegrationTransactions(){
if (!IntegrationTransaction.verifyIntegrationTransaction(
integrationTransaction)) {
console.log(`Invalid signature from
${integrationTransaction.Signiture.address}.`);
return;
}
return integrationTransaction;
}
clearAll() {
this.cointransactions = [];
this.metaDataTransactions = [];
this.comptransactions = [];
this.integrationTransactions = [];
}
}
module.exports = TransactionPool;

View file

@ -0,0 +1,58 @@
const TransactionPool = require('./transaction-pool');
const Transaction = require('./Cointransaction');
const Wallet = require('./index');
const Blockchain = require('../blockchain');
describe('TransactionPool', () => {
let tp, wallet, transaction, bc;
beforeEach(() => {
tp = new TransactionPool();
wallet = new Wallet();
bc = new Blockchain();
transaction = wallet.createCoinTransaction('r4nd-4dr355', 30,20,9014,'temp','123abc', bc, tp);
});
it('adds a transaction to the pool', () => {
expect(tp.paymenttransactions.find(t => t.id === transaction.id)).toEqual(transaction);
});
it('updates a transaction in the pool', () => {
const oldTransaction = JSON.stringify(transaction);
const newTransaction = transaction.update(wallet, 'foo-4ddr355', 40,20,9014,'temp','123abc');
tp.updateOrAddPaymentTransaction(newTransaction);
expect(JSON.stringify(tp.paymenttransactions.find(t => t.id === newTransaction.id)))
.not.toEqual(oldTransaction);
});
it('clears transactions', () => {
tp.clear();
expect(tp.paymenttransactions).toEqual([]);
});
describe('mixing valid and corrupt transactions', () => {
let validTransactions;
beforeEach(() => {
validTransactions = [...tp.paymenttransactions];
for (let i=0; i<6; i++) {
wallet = new Wallet();
transaction = wallet.createCoinTransaction('r4nd-4dr355', 30,20,9014,'temp','123abc', 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.paymenttransactions)).not.toEqual(JSON.stringify(validTransactions));
});
it('grabs valid transactions', () => {
expect(tp.validTransactions()).toEqual(validTransactions);
});
});
});

View file

@ -0,0 +1,79 @@
const Transaction = require('./Cointransaction');
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);
});
});
});