add persistence and transaction (UTXO)
This commit is contained in:
@ -5,13 +5,18 @@ class Account {
|
|||||||
this.keypair_ = keypair;
|
this.keypair_ = keypair;
|
||||||
this.id_ = id;
|
this.id_ = id;
|
||||||
this.amount_ = 0;
|
this.amount_ = 0;
|
||||||
if (!this.keypair_) { }
|
if (!this.keypair_) {
|
||||||
if (!this.id_) { }
|
// load from file
|
||||||
|
}
|
||||||
|
if (!this.id_) {
|
||||||
|
// load from file
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get_id() { return this.id_; }
|
get_id() { return this.id_; }
|
||||||
get_key() { return this.keypair_; }
|
get_key() { return this.keypair_; }
|
||||||
get_amount() { return this.amount_; }
|
get_amount() { return this.amount_; }
|
||||||
|
set_amount(amount) {this.amount_ = amount;}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Account;
|
module.exports = Account;
|
@ -1,14 +1,14 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
var crypto = require("crypto");
|
var Crypto = require("./crypto");
|
||||||
var ed = require("ed25519");
|
|
||||||
|
|
||||||
class Block extends EventEmitter {
|
class Block extends EventEmitter {
|
||||||
constructor(data, consensus) {
|
constructor(data, consensus) {
|
||||||
super();
|
super();
|
||||||
// body
|
// body
|
||||||
this.transcations_ = data ? data.transactions : [];
|
this.transactions_ = data ? data.transactions : [];
|
||||||
// header
|
// header
|
||||||
this.version_ = 0;
|
this.version_ = 0;
|
||||||
this.height_ = data ? data.previous_block.height + 1 : -1;
|
this.height_ = data ? data.previous_block.height + 1 : -1;
|
||||||
@ -37,7 +37,7 @@ class Block extends EventEmitter {
|
|||||||
get_timestamp() { return this.timestamp_; }
|
get_timestamp() { return this.timestamp_; }
|
||||||
get_signature() { return this.block_signature_; }
|
get_signature() { return this.block_signature_; }
|
||||||
get_publickey() { return this.generator_publickey_; }
|
get_publickey() { return this.generator_publickey_; }
|
||||||
get_transcations() { return this.transcations_; }
|
get_transactions() { return this.transactions_; }
|
||||||
get_consensus_data() { return this.consensus_data_; }
|
get_consensus_data() { return this.consensus_data_; }
|
||||||
set_consensus_data(data) { this.consensus_data_ = data; }
|
set_consensus_data(data) { this.consensus_data_ = data; }
|
||||||
toObject() {
|
toObject() {
|
||||||
@ -51,7 +51,7 @@ class Block extends EventEmitter {
|
|||||||
"hash": this.hash_,
|
"hash": this.hash_,
|
||||||
"block_signature": this.block_signature_,
|
"block_signature": this.block_signature_,
|
||||||
"consensus_data": this.consensus_data_,
|
"consensus_data": this.consensus_data_,
|
||||||
"transcations": this.transcations_
|
"transactions": this.transactions_
|
||||||
};
|
};
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
@ -65,23 +65,20 @@ class Block extends EventEmitter {
|
|||||||
this.hash_ = data.hash;
|
this.hash_ = data.hash;
|
||||||
this.block_signature_ = data.block_signature;
|
this.block_signature_ = data.block_signature;
|
||||||
this.consensus_data_ = data.consensus_data;
|
this.consensus_data_ = data.consensus_data;
|
||||||
this.transcations_ = data.transactions;
|
this.transactions_ = data.transactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
calc_hash(data) {
|
|
||||||
return crypto.createHash('sha256').update(data).digest('hex');
|
|
||||||
}
|
|
||||||
calc_merkle_hash() {
|
calc_merkle_hash() {
|
||||||
// calc merkle root hash according to the transcations in the block
|
// calc merkle root hash according to the transactions in the block
|
||||||
var hashes = [];
|
var hashes = [];
|
||||||
for (var i = 0; i < this.transcations_.length; ++i) {
|
for (var i = 0; i < this.transactions_.length; ++i) {
|
||||||
hashes.push(this.calc_hash(this.transcations_.toString('utf-8')));
|
hashes.push(Crypto.calc_hash(this.transactions_.toString('utf-8')));
|
||||||
}
|
}
|
||||||
while (hashes.length > 1) {
|
while (hashes.length > 1) {
|
||||||
var tmp = [];
|
var tmp = [];
|
||||||
for (var i = 0; i < hashes.length / 2; ++i) {
|
for (var i = 0; i < hashes.length / 2; ++i) {
|
||||||
let data = hashes[i * 2] + hashes[i * 2 + 1];
|
let data = hashes[i * 2] + hashes[i * 2 + 1];
|
||||||
tmp.push(this.calc_hash(data));
|
tmp.push(Crypto.calc_hash(data));
|
||||||
}
|
}
|
||||||
if (hashes.length % 2 === 1) {
|
if (hashes.length % 2 === 1) {
|
||||||
tmp.push(hashes[hashes.length - 1]);
|
tmp.push(hashes[hashes.length - 1]);
|
||||||
@ -93,8 +90,8 @@ class Block extends EventEmitter {
|
|||||||
|
|
||||||
prepare_data() {
|
prepare_data() {
|
||||||
let tx = "";
|
let tx = "";
|
||||||
for (var i = 0; i < this.transcations_.length; ++i) {
|
for (var i = 0; i < this.transactions_.length; ++i) {
|
||||||
tx += this.transcations_[i].toString('utf-8');
|
tx += this.transactions_[i].toString('utf-8');
|
||||||
}
|
}
|
||||||
let data = this.version_.toString()
|
let data = this.version_.toString()
|
||||||
+ this.height_.toString()
|
+ this.height_.toString()
|
||||||
@ -109,11 +106,11 @@ class Block extends EventEmitter {
|
|||||||
}
|
}
|
||||||
// calc the hash of the block
|
// calc the hash of the block
|
||||||
calc_block_hash() {
|
calc_block_hash() {
|
||||||
return this.calc_hash(this.prepare_data());
|
return Crypto.calc_hash(this.prepare_data());
|
||||||
}
|
}
|
||||||
sign(keypair) {
|
sign(keypair) {
|
||||||
var hash = this.calc_block_hash();
|
var hash = this.calc_block_hash();
|
||||||
return ed.Sign(Buffer.from(hash, 'utf-8'), keypair).toString('hex');
|
return Crypto.sign(keypair, hash);
|
||||||
}
|
}
|
||||||
make_proof(consensus, keypair) {
|
make_proof(consensus, keypair) {
|
||||||
let self = this;
|
let self = this;
|
||||||
@ -128,13 +125,7 @@ class Block extends EventEmitter {
|
|||||||
|
|
||||||
static verify_signature(block) {
|
static verify_signature(block) {
|
||||||
var hash = block.hash;
|
var hash = block.hash;
|
||||||
var res = ed.Verify(Buffer.from(hash, 'utf8'), Buffer.from(block.block_signature, 'hex'), Buffer.from(block.generator_publickey, 'hex'));
|
return Crypto.verify_signature(hash, block.block_signature, block.generator_publickey);
|
||||||
return res;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static get_address_by_publickey(publicKey) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,17 +4,22 @@ var Block = require("./block");
|
|||||||
const genesis_block = require("./genesis_block.json");
|
const genesis_block = require("./genesis_block.json");
|
||||||
var Node = require("./network");
|
var Node = require("./network");
|
||||||
var Account = require("./account");
|
var Account = require("./account");
|
||||||
var Transaction = require("./transaction");
|
var Transaction = require("./transaction").Transaction;
|
||||||
|
var TxInput = require("./transaction").TxInput;
|
||||||
|
var TxOutput = require("./transaction").TxOutput;
|
||||||
var Msg = require("./message");
|
var Msg = require("./message");
|
||||||
var MessageType = require("./message").type;
|
var MessageType = require("./message").type;
|
||||||
var Promise = require("bluebird");
|
var Promise = require("bluebird");
|
||||||
|
var level = require("level");
|
||||||
|
var Crypto = require("./crypto");
|
||||||
|
|
||||||
var Pbft = require("./consensus/pbft");
|
var Pbft = require("./consensus/pbft");
|
||||||
let pbft = true;
|
let pbft = false;
|
||||||
class BlockChain {
|
class BlockChain {
|
||||||
constructor(Consensus, keypair, id, is_bad = false) {
|
constructor(Consensus, keypair, id, is_bad = false) {
|
||||||
// todo
|
// todo
|
||||||
this.pending_block_ = {};
|
this.pending_block_ = {};
|
||||||
|
this.tx_pool = {};
|
||||||
this.chain_ = [];
|
this.chain_ = [];
|
||||||
|
|
||||||
this.is_bad_ = is_bad;
|
this.is_bad_ = is_bad;
|
||||||
@ -22,14 +27,26 @@ class BlockChain {
|
|||||||
|
|
||||||
// ///////////////////////////////////////
|
// ///////////////////////////////////////
|
||||||
this.genesis_block_ = genesis_block;
|
this.genesis_block_ = genesis_block;
|
||||||
this.last_block_ = genesis_block;
|
|
||||||
this.save_last_block();
|
|
||||||
|
|
||||||
this.account_ = new Account(keypair, id);
|
this.account_ = new Account(keypair, id);
|
||||||
this.consensus_ = new Consensus(this);
|
this.consensus_ = new Consensus(this);
|
||||||
this.node_ = null;
|
this.node_ = null;
|
||||||
}
|
}
|
||||||
start() {
|
async start() {
|
||||||
|
this.db_ = level(`/tmp/data_${this.get_account_id()}`);
|
||||||
|
try {
|
||||||
|
// load blocks
|
||||||
|
let last = await this.db_.get("last_block");
|
||||||
|
this.last_block_ = JSON.parse(last);
|
||||||
|
console.log(`node: ${this.get_account_id()} last block: ${this.last_block_.height}`);
|
||||||
|
} catch (err) {
|
||||||
|
// empty chain
|
||||||
|
this.last_block_ = genesis_block;
|
||||||
|
this.save_last_block();
|
||||||
|
console.log(`node: ${this.get_account_id()} empty`);
|
||||||
|
}
|
||||||
|
|
||||||
this.node_ = new Node(this.get_account_id());
|
this.node_ = new Node(this.get_account_id());
|
||||||
this.node_.on("message", this.on_data.bind(this));
|
this.node_.on("message", this.on_data.bind(this));
|
||||||
this.node_.start();
|
this.node_.start();
|
||||||
@ -57,18 +74,40 @@ class BlockChain {
|
|||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
save_last_block() {
|
async save_last_block() {
|
||||||
// query from db via hash
|
// query from db via hash
|
||||||
// if not exist, write into db, else do nothing
|
// if not exist, write into db, else do nothing
|
||||||
// todo(tx is also need to store?)
|
|
||||||
if (this.pending_block_[this.last_block_.hash]) {
|
if (this.pending_block_[this.last_block_.hash]) {
|
||||||
delete this.pending_block_[this.last_block_.hash];
|
delete this.pending_block_[this.last_block_.hash];
|
||||||
}
|
}
|
||||||
this.chain_.push(this.last_block_);
|
await this.db_.put(this.last_block_.hash, JSON.stringify(this.last_block_));
|
||||||
|
await this.db_.put("last_block", JSON.stringify(this.last_block_));
|
||||||
|
// console.log(`save block: ${this.last_block_.hash} to db`);
|
||||||
|
|
||||||
|
// tx
|
||||||
|
if (!this.last_block_.transactions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < this.last_block_.transactions.length; ++i) {
|
||||||
|
let tx = this.last_block_.transactions[i];
|
||||||
|
if (this.tx_pool[tx.id]) {
|
||||||
|
delete this.tx_pool[tx.id];
|
||||||
|
// console.log(`node ${this.get_account_id()} delete tx ${tx.id}`);
|
||||||
|
}
|
||||||
|
await this.db_.put(tx.id, JSON.stringify(tx));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
generate_block(keypair, cb) {
|
generate_block(keypair, cb) {
|
||||||
// load transcations
|
// load transactions
|
||||||
var tx = [];
|
var tx = [this.create_coinbase()];
|
||||||
|
var i = 0;
|
||||||
|
for (let key in this.tx_pool) {
|
||||||
|
if (i == 10)
|
||||||
|
break;
|
||||||
|
tx.push(this.tx_pool[key]);
|
||||||
|
i++;
|
||||||
|
console.log(`node ${this.get_account_id()} load tx ${key}`);
|
||||||
|
}
|
||||||
// create block
|
// create block
|
||||||
let block = new Block({
|
let block = new Block({
|
||||||
"keypair": keypair,
|
"keypair": keypair,
|
||||||
@ -79,7 +118,7 @@ class BlockChain {
|
|||||||
let self = this;
|
let self = this;
|
||||||
block.on('block completed', (data) => {
|
block.on('block completed', (data) => {
|
||||||
if (data.height == self.last_block_.height + 1) {
|
if (data.height == self.last_block_.height + 1) {
|
||||||
console.log("block completed");
|
// console.log("block completed");
|
||||||
self.commit_block(data);
|
self.commit_block(data);
|
||||||
|
|
||||||
self.broadcast(Msg.block(data));
|
self.broadcast(Msg.block(data));
|
||||||
@ -113,15 +152,32 @@ class BlockChain {
|
|||||||
get_height() {
|
get_height() {
|
||||||
return this.last_block_.height;
|
return this.last_block_.height;
|
||||||
}
|
}
|
||||||
get_block(hash) {
|
async get_from_db(hash) {
|
||||||
// query block with hash value
|
// query block with hash value
|
||||||
// todo
|
try {
|
||||||
for (var i = 0; i < this.chain_.length; ++i) {
|
let block_data = await this.db_.get(hash);
|
||||||
if (this.chain_[i] == hash) {
|
let block = JSON.parse(block_data);
|
||||||
return this.chain_[i];
|
return block;
|
||||||
}
|
} catch (err) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
}
|
||||||
|
async iterator_back(cb, hash) {
|
||||||
|
if (!hash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let block = await this.get_from_db(hash);
|
||||||
|
let res = cb(block);
|
||||||
|
if (res)
|
||||||
|
await this.iterator_back(cb, block.previous_hash);
|
||||||
|
}
|
||||||
|
async iterator_forward(cb, hash) {
|
||||||
|
if (!hash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let block = await this.get_from_db(hash);
|
||||||
|
await this.iterator_forward(cb, block.previous_hash);
|
||||||
|
cb(block);
|
||||||
}
|
}
|
||||||
get_last_block() {
|
get_last_block() {
|
||||||
return this.last_block_;
|
return this.last_block_;
|
||||||
@ -140,41 +196,89 @@ class BlockChain {
|
|||||||
get_account_keypair() {
|
get_account_keypair() {
|
||||||
return this.account_.get_key();
|
return this.account_.get_key();
|
||||||
}
|
}
|
||||||
|
get_public_key() {
|
||||||
|
return this.get_account_keypair().publicKey.toString('hex');
|
||||||
|
}
|
||||||
broadcast(data) {
|
broadcast(data) {
|
||||||
this.node_.broadcast(data);
|
this.node_.broadcast(data);
|
||||||
}
|
}
|
||||||
list_peers() {
|
list_peers() {
|
||||||
return this.node_.list_peers();
|
return this.node_.list_peers();
|
||||||
}
|
}
|
||||||
|
async verify_transaction(tx) {
|
||||||
|
let input_amount = 0;
|
||||||
|
for (var i = 0; i < tx.input.length; ++i) {
|
||||||
|
let input = tx.input[i];
|
||||||
|
// coinbase
|
||||||
|
if (input.id == null) {
|
||||||
|
// todo check milestone
|
||||||
|
if (tx.output[0].amount == 50) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let vout = null;
|
||||||
|
if (this.tx_pool[input.id]) {
|
||||||
|
vout = this.tx.tx_pool[input.id];
|
||||||
|
} else {
|
||||||
|
vout = await this.get_from_db(input.id);
|
||||||
|
}
|
||||||
|
if (!vout) {
|
||||||
|
// invalid vout
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vout = vout.output[input.index];
|
||||||
|
let res = Crypto.verify_signature(JSON.stringify(vout), input.ScriptSig, vout.ScriptPubKey);
|
||||||
|
if (!res) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input_amount += vout.amount;
|
||||||
|
}
|
||||||
|
let output_amount = 0;
|
||||||
|
for (i = 0; i < tx.output.length; ++i) {
|
||||||
|
output_amount += tx.output[i].amount;
|
||||||
|
}
|
||||||
|
if (input_amount < output_amount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// verify the block is valid
|
// verify the block is valid
|
||||||
verify(block) {
|
async verify(block) {
|
||||||
// verify the block signature
|
// verify the block signature
|
||||||
if (!Block.verify_signature(block))
|
if (!Block.verify_signature(block))
|
||||||
return false;
|
return false;
|
||||||
// verify consensus
|
// verify consensus
|
||||||
if (!this.consensus_.verify(block))
|
if (!this.consensus_.verify(block))
|
||||||
return false;
|
return false;
|
||||||
// verify transcations
|
// verify transactions
|
||||||
let tx = block.transcations;
|
let tx = block.transactions;
|
||||||
for (var i = 0; i < tx.length; ++i) {
|
if (tx) {
|
||||||
// todo (check tx is exist and valid)
|
for (var i = 0; i < tx.length; ++i) {
|
||||||
if (!Transaction.verify(tx[i]))
|
if (!await this.verify_transaction(tx[i]))
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
on_data(msg) {
|
async on_data(msg) {
|
||||||
switch (msg.type) {
|
switch (msg.type) {
|
||||||
case MessageType.Block:
|
case MessageType.Block:
|
||||||
{
|
{
|
||||||
let block = msg.data;
|
let block = msg.data;
|
||||||
// console.log(`node: ${this.get_account_id()} receive block: height ${block.height}`);
|
// console.log(`node: ${this.get_account_id()} receive block: height ${block.height}`);
|
||||||
// check if exist
|
// check if exist
|
||||||
if (this.pending_block_[block.hash] || this.get_block(block.hash))
|
let query = await this.get_from_db(block.hash);
|
||||||
|
if (this.pending_block_[block.hash] || query) {
|
||||||
|
// console.log("block already exists");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
// verify
|
// verify
|
||||||
if (!this.verify(block))
|
if (!await this.verify(block)) {
|
||||||
|
// console.log("verify failed");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.pending_block_[block.hash] = block;
|
this.pending_block_[block.hash] = block;
|
||||||
|
|
||||||
@ -195,6 +299,22 @@ class BlockChain {
|
|||||||
case MessageType.Transaction:
|
case MessageType.Transaction:
|
||||||
{
|
{
|
||||||
// check if exist(pending or in chain) verify, store(into pending) and broadcast
|
// check if exist(pending or in chain) verify, store(into pending) and broadcast
|
||||||
|
let tx = msg.data;
|
||||||
|
if (this.tx_pool[tx.id]) {
|
||||||
|
// already exists
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.tx_pool[tx.id] = tx;
|
||||||
|
// verify transaction
|
||||||
|
let res = await this.verify_transaction(tx);
|
||||||
|
if (!res) {
|
||||||
|
delete this.tx_pool[tx.id];
|
||||||
|
} else {
|
||||||
|
// console.log(`node ${this.get_account_id()} store tx ${tx.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// broadcast
|
||||||
|
this.broadcast(msg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -218,7 +338,7 @@ class BlockChain {
|
|||||||
}
|
}
|
||||||
async fork() {
|
async fork() {
|
||||||
console.log('----------fork----------');
|
console.log('----------fork----------');
|
||||||
// load transcations
|
// load transactions
|
||||||
var tx1 = [{
|
var tx1 = [{
|
||||||
amount: 1000,
|
amount: 1000,
|
||||||
recipient: 'bob',
|
recipient: 'bob',
|
||||||
@ -242,7 +362,7 @@ class BlockChain {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// load transcations
|
// load transactions
|
||||||
var tx2 = [{
|
var tx2 = [{
|
||||||
amount: 1000,
|
amount: 1000,
|
||||||
recipient: 'cracker',
|
recipient: 'cracker',
|
||||||
@ -279,6 +399,91 @@ class BlockChain {
|
|||||||
console.log("fork");
|
console.log("fork");
|
||||||
this.commit_block(block_data1);
|
this.commit_block(block_data1);
|
||||||
}
|
}
|
||||||
|
create_coinbase() {
|
||||||
|
let input = new TxInput(null, -1, `${new Date()} node: ${this.get_account_id()} coinbase tx`);
|
||||||
|
let output = new TxOutput(50, this.get_public_key());
|
||||||
|
let tx = new Transaction([input], [output]);
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get_utxo(cb) {
|
||||||
|
let publicKey = this.get_public_key();
|
||||||
|
let spentTXOs = {};
|
||||||
|
await this.iterator_back((block) => {
|
||||||
|
let txs = block.transactions;
|
||||||
|
// tx
|
||||||
|
for (var i = 0; i < txs.length; ++i) {
|
||||||
|
let tx = txs[i];
|
||||||
|
let transaction_id = tx.id;
|
||||||
|
// output
|
||||||
|
for (var j = 0; j < tx.output.length; ++j) {
|
||||||
|
let output = tx.output[j];
|
||||||
|
// owns
|
||||||
|
if (output.ScriptPubKey == publicKey) {
|
||||||
|
// not spent
|
||||||
|
if (spentTXOs.hasOwnProperty(transaction_id) &&
|
||||||
|
spentTXOs[transaction_id].hasOwnProperty(j)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
if (!cb(transaction_id, j, output)) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// input
|
||||||
|
for (j = 0; j < tx.input.length; ++j) {
|
||||||
|
let input = tx.input[j];
|
||||||
|
// not coinbase
|
||||||
|
if (input.id != null && input.index != -1) {
|
||||||
|
if (!spentTXOs[input.id]) {
|
||||||
|
spentTXOs[input.id] = [];
|
||||||
|
}
|
||||||
|
spentTXOs[input.id].push(input.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
this.get_last_block().hash);
|
||||||
|
}
|
||||||
|
async get_balance() {
|
||||||
|
let value = 0;
|
||||||
|
await this.get_utxo((transaction_id, index, vout) => {
|
||||||
|
value += vout.amount;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
async create_transaction(to, amount) {
|
||||||
|
let value = 0;
|
||||||
|
let input = [];
|
||||||
|
let output = [];
|
||||||
|
let self = this;
|
||||||
|
let tx = null;
|
||||||
|
await this.get_utxo((transaction_id, index, vout) => {
|
||||||
|
value += vout.amount;
|
||||||
|
let signature = Crypto.sign(self.get_account_keypair(), JSON.stringify(vout));
|
||||||
|
input.push(new TxInput(transaction_id, index, signature));
|
||||||
|
if (value >= amount) {
|
||||||
|
output.push(new TxOutput(amount, to));
|
||||||
|
if (value > amount)
|
||||||
|
output.push(new TxOutput(value - amount, self.get_public_key()));
|
||||||
|
tx = new Transaction(input, output);
|
||||||
|
// stop
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (value < amount) {
|
||||||
|
throw new Error("amount is not enough!");
|
||||||
|
}
|
||||||
|
if (tx == null) {
|
||||||
|
throw new Error("create transaction failed!");
|
||||||
|
}
|
||||||
|
this.tx_pool[tx.id] = tx;
|
||||||
|
this.broadcast(Msg.transaction(tx));
|
||||||
|
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = BlockChain;
|
module.exports = BlockChain;
|
23
src/js/crypto.js
Normal file
23
src/js/crypto.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var crypto = require("crypto");
|
||||||
|
var ed = require("ed25519");
|
||||||
|
|
||||||
|
function calc_hash(data) {
|
||||||
|
return crypto.createHash('sha256').update(data).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sign(keypair, data) {
|
||||||
|
return ed.Sign(Buffer.from(data, 'utf-8'), keypair).toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
function verify_signature(data, signature, publickey) {
|
||||||
|
var res = ed.Verify(Buffer.from(data, 'utf-8'), Buffer.from(signature, 'hex'), Buffer.from(publickey, 'hex'));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
calc_hash,
|
||||||
|
sign,
|
||||||
|
verify_signature
|
||||||
|
};
|
@ -8,5 +8,5 @@
|
|||||||
"hash": "d611edb9fd86ee234cdc08d9bf382330d6ccc721cd5e59cf2a01b0a2a8decfff",
|
"hash": "d611edb9fd86ee234cdc08d9bf382330d6ccc721cd5e59cf2a01b0a2a8decfff",
|
||||||
"block_signature": "603b61b14348fb7eb087fe3267e28abacadf3932f0e33958fb016ab60f825e3124bfe6c7198d38f8c91b0a3b1f928919190680e44fbe7289a4202039ffbb2109",
|
"block_signature": "603b61b14348fb7eb087fe3267e28abacadf3932f0e33958fb016ab60f825e3124bfe6c7198d38f8c91b0a3b1f928919190680e44fbe7289a4202039ffbb2109",
|
||||||
"consensus_data": {},
|
"consensus_data": {},
|
||||||
"transcations": []
|
"transactions": []
|
||||||
}
|
}
|
@ -15,5 +15,6 @@ module.exports = {
|
|||||||
block: (data) => { return { type: MessageType.Block, data: data }; },
|
block: (data) => { return { type: MessageType.Block, data: data }; },
|
||||||
preprepare: (data) => { return { type: MessageType.PrePrepare, data: data }; },
|
preprepare: (data) => { return { type: MessageType.PrePrepare, data: data }; },
|
||||||
prepare: (data) => { return { type: MessageType.Prepare, data: data }; },
|
prepare: (data) => { return { type: MessageType.Prepare, data: data }; },
|
||||||
commit: (data) => { return { type: MessageType.Commit, data: data }; }
|
commit: (data) => { return { type: MessageType.Commit, data: data }; },
|
||||||
|
transaction: (data)=>{return { type: MessageType.Transaction, data: data }; }
|
||||||
};
|
};
|
@ -9,7 +9,8 @@
|
|||||||
"ed25519": "0.0.4",
|
"ed25519": "0.0.4",
|
||||||
"eslint": "^5.13.0",
|
"eslint": "^5.13.0",
|
||||||
"eslint-plugin-html": "^5.0.0",
|
"eslint-plugin-html": "^5.0.0",
|
||||||
"js-sha256": "^0.9.0"
|
"js-sha256": "^0.9.0",
|
||||||
|
"level": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -18,12 +18,12 @@ let genesis = {
|
|||||||
"hash": null,
|
"hash": null,
|
||||||
"block_signature": null,
|
"block_signature": null,
|
||||||
"consensus_data": {},
|
"consensus_data": {},
|
||||||
"transcations": []
|
"transactions": []
|
||||||
};
|
};
|
||||||
|
|
||||||
function prepare_data() {
|
function prepare_data() {
|
||||||
let tx = "";
|
let tx = "";
|
||||||
genesis.transcations.forEach(val => {
|
genesis.transactions.forEach(val => {
|
||||||
tx += val.toString('utf8');
|
tx += val.toString('utf8');
|
||||||
});
|
});
|
||||||
let data = genesis.version.toString()
|
let data = genesis.version.toString()
|
||||||
|
34
src/js/test/db.js
Normal file
34
src/js/test/db.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var ed = require('ed25519');
|
||||||
|
var BlockChain = require("../blockchain");
|
||||||
|
var Consensus = require("../consensus/dpos");
|
||||||
|
|
||||||
|
var password = 'I am tester!';
|
||||||
|
|
||||||
|
var hash = crypto.createHash('sha256').update(password).digest();
|
||||||
|
var keypair = ed.MakeKeypair(hash);
|
||||||
|
|
||||||
|
let blockchains = [];
|
||||||
|
for (var i = 0; i < 20; ++i) {
|
||||||
|
let blockchain = new BlockChain(Consensus, keypair, i);
|
||||||
|
blockchain.start();
|
||||||
|
blockchains.push(blockchain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// setTimeout(() => {
|
||||||
|
// for (var i = 0; i < 20; ++i) {
|
||||||
|
// console.log(`${i} --> ${blockchains[i].list_peers()}`);
|
||||||
|
// }
|
||||||
|
// }, 3000);
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
console.log("=================");
|
||||||
|
await blockchains[0].iterator_forward((block) => {
|
||||||
|
console.log("-----------------");
|
||||||
|
console.log(block.height);
|
||||||
|
console.log(block.hash);
|
||||||
|
return true;
|
||||||
|
}, blockchains[0].get_last_block().hash);
|
||||||
|
}, 5000);
|
40
src/js/test/transaction.js
Normal file
40
src/js/test/transaction.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var ed = require('ed25519');
|
||||||
|
var BlockChain = require("../blockchain");
|
||||||
|
var Consensus = require("../consensus/dpos");
|
||||||
|
|
||||||
|
|
||||||
|
let blockchains = [];
|
||||||
|
for (var i = 0; i < 20; ++i) {
|
||||||
|
|
||||||
|
var password = `I am tester ${i}!`;
|
||||||
|
var hash = crypto.createHash('sha256').update(password).digest();
|
||||||
|
var keypair = ed.MakeKeypair(hash);
|
||||||
|
console.log(`node ${i} address: ${keypair.publicKey.toString('hex')}`);
|
||||||
|
|
||||||
|
let blockchain = new BlockChain(Consensus, keypair, i);
|
||||||
|
blockchain.start();
|
||||||
|
blockchains.push(blockchain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// setTimeout(() => {
|
||||||
|
// for (var i = 0; i < 20; ++i) {
|
||||||
|
// console.log(`${i} --> ${blockchains[i].list_peers()}`);
|
||||||
|
// }
|
||||||
|
// }, 3000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
let address = blockchains[6].get_public_key();
|
||||||
|
blockchains[0].create_transaction(address, 30);
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
async function get_balance() {
|
||||||
|
let amount = await blockchains[0].get_balance();
|
||||||
|
console.log(`node 0 balance: ${amount}`);
|
||||||
|
amount = await blockchains[6].get_balance();
|
||||||
|
console.log(`node 6 balance: ${amount}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(get_balance, 10000);
|
@ -1,13 +1,64 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
var Crypto = require("./crypto");
|
||||||
|
|
||||||
class Transaction {
|
class TxOutput {
|
||||||
constructor() {
|
constructor(amount, ScriptPubKey) {
|
||||||
|
this.amount_ = amount;
|
||||||
|
this.script_pubkey_ = ScriptPubKey;
|
||||||
}
|
}
|
||||||
|
toObject() {
|
||||||
static verify(tx) {
|
let output = {
|
||||||
return true;
|
"amount": this.amount_,
|
||||||
|
"ScriptPubKey": this.script_pubkey_
|
||||||
|
};
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Transaction;
|
class TxInput {
|
||||||
|
constructor(id, index, ScriptSig) {
|
||||||
|
this.id_ = id;
|
||||||
|
this.index_ = index;
|
||||||
|
this.script_sig_ = ScriptSig;
|
||||||
|
}
|
||||||
|
toObject() {
|
||||||
|
let input = {
|
||||||
|
"id": this.id_,
|
||||||
|
"index": this.index_,
|
||||||
|
"ScriptSig": this.script_sig_
|
||||||
|
};
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Transaction {
|
||||||
|
constructor(input, output) {
|
||||||
|
this.input_ = [];
|
||||||
|
for (i = 0; i < input.length; ++i) {
|
||||||
|
this.input_.push(input[i].toObject());
|
||||||
|
}
|
||||||
|
this.output_ = [];
|
||||||
|
for (var i = 0; i < output.length; ++i) {
|
||||||
|
this.output_.push(output[i].toObject());
|
||||||
|
}
|
||||||
|
this.id_ = Crypto.calc_hash(JSON.stringify(this.input_) + JSON.stringify(this.output_));
|
||||||
|
return this.toObject();
|
||||||
|
}
|
||||||
|
get_id() { return this.id_; }
|
||||||
|
get_input() { return this.input_; }
|
||||||
|
get_output() { return this.output_; }
|
||||||
|
toObject() {
|
||||||
|
let tx = {
|
||||||
|
"id": this.id_,
|
||||||
|
"input": this.input_,
|
||||||
|
"output": this.output_
|
||||||
|
};
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
TxOutput,
|
||||||
|
TxInput,
|
||||||
|
Transaction
|
||||||
|
};
|
Reference in New Issue
Block a user