diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
new file mode 100644
index 0000000..d23208f
--- /dev/null
+++ b/.idea/jsLibraryMappings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index a5d8324..2a4ad10 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,12 +4,28 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -27,6 +43,8 @@
"RunOnceActivity.ShowReadmeOnStart": "true",
"WebServerToolWindowFactoryState": "false",
"git-widget-placeholder": "fme-lesson1",
+ "javascript.nodejs.core.library.configured.version": "18.16.0",
+ "javascript.nodejs.core.library.typings.version": "18.16.0",
"last_opened_file_path": "D:/GitHub/blockchain-in-js-workshop-2021",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
@@ -41,8 +59,8 @@
-
-
+
+
@@ -54,24 +72,16 @@
-
-
-
-
-
-
-
-
-
+
+
+
-
-
@@ -89,6 +99,10 @@
+
+
+
+
diff --git a/src/cryptoCurrency/MerkleTree.js b/src/cryptoCurrency/MerkleTree.js
new file mode 100644
index 0000000..ed998c1
--- /dev/null
+++ b/src/cryptoCurrency/MerkleTree.js
@@ -0,0 +1,80 @@
+import { createHash } from 'crypto';
+class MerkleTree {
+ constructor(data) {
+ this.leaves = data.map(item => this.hash(item));
+ this.tree = this.buildTree(this.leaves.slice());
+ this.root = this.tree[0];
+ }
+
+ hash(data) {
+ return createHash('sha256').update(data).digest('hex');
+ }
+
+ buildTree(leaves) {
+ let tree = leaves;
+ while (tree.length > 1) {
+ let parentLevel = [];
+ for (let i = 0; i < tree.length; i += 2) {
+ let left = tree[i];
+ let right = tree[i + 1] || left;
+ parentLevel.push(this.hash(left + right));
+ }
+ tree = parentLevel;
+ }
+ return tree;
+ }
+
+ // 添加节点
+ addNode(data) {
+ this.leaves.push(this.hash(data));
+ this.tree = this.buildTree(this.leaves.slice());
+ this.root = this.tree[0];
+ }
+
+ // 删除节点
+ removeNode(data) {
+ const hashedData = this.hash(data);
+ const index = this.leaves.findIndex(leaf => leaf === hashedData);
+ if (index !== -1) {
+ this.leaves.splice(index, 1);
+ this.tree = this.buildTree(this.leaves.slice());
+ this.root = this.tree[0];
+ }
+ }
+
+ // 验证函数
+ verify(data) {
+ const hashedData = this.hash(data);
+ let tree = this.leaves.slice();
+ while (tree.length > 1) {
+ let parentLevel = [];
+ for (let i = 0; i < tree.length; i += 2) {
+ let left = tree[i];
+ let right = tree[i + 1] || left;
+ if (hashedData === left || hashedData === right) {
+ return true;
+ }
+ parentLevel.push(this.hash(left + right));
+ }
+ tree = parentLevel;
+ }
+ return false;
+ }
+}
+
+const data = ['A', 'B', 'C', 'D'];
+const merkleTree = new MerkleTree(data);
+console.log('Merkle Root:', merkleTree.root);
+
+// 验证数据块 'A' 是否在 Merkle Tree 中
+console.log('Verify A:', merkleTree.verify('A')); // 应该输出 true
+
+// 添加一个新的数据块 'E'
+merkleTree.addNode('E');
+console.log('Merkle Root after adding E:', merkleTree.root); // Merkle Root 应该发生变化
+
+// 删除数据块 'E'
+merkleTree.removeNode('E');
+console.log('Merkle Root after removing E:', merkleTree.root); // Merkle Root 应该恢复到最初的值
+
+export default MerkleTree
diff --git a/src/models/Transaction.js b/src/models/Transaction.js
new file mode 100644
index 0000000..677769f
--- /dev/null
+++ b/src/models/Transaction.js
@@ -0,0 +1,24 @@
+import sha256 from 'crypto-js/sha256.js'
+import t from "ramda/src/T.js";
+
+
+class Transaction {
+ constructor(miner, receiverPubKey, value, hash) {
+ this.miner = miner
+ this.receiverPubKey = receiverPubKey
+ this.value = value
+ this.hash = hash || this._calculateHash()
+ }
+
+ // 更新交易 hash
+ _setHash() {
+ this.hash = this._calculateHash()
+ }
+
+ // 计算交易 hash 的摘要函数
+ _calculateHash() {
+ return sha256(this.miner + this.receiverPubKey + this.value).toString()
+ }
+}
+
+export default Transaction
\ No newline at end of file
diff --git a/src/models/UTXOPool.js b/src/models/UTXOPool.js
index 388f7c3..ffcfa6d 100644
--- a/src/models/UTXOPool.js
+++ b/src/models/UTXOPool.js
@@ -23,6 +23,34 @@ class UTXOPool {
clone() {
return new UTXOPool({...this.utxos})
}
+
+ // 处理交易函数
+ handleTransaction(transaction) {
+ if (!this.isValidTransaction(transaction.miner, transaction.value)) {
+ return
+ }
+
+ // miner地址的amount减去交易的value
+ this.utxos[transaction.miner].amount -= transaction.value
+
+ // !!!注意!!!
+ // 这里把公钥的地址加入到utxos里面,就不用在addUTXO函数里面添加判断这个地址到底是miner还是receiverPubKey了
+ // 因为addUTXO里面也判断不了,没有传transaction判断个屁
+ this.utxos[transaction.receiverPubKey] = new UTXO(transaction.receiverPubKey, 0)
+
+ // 把receiverPubKey加入到UTXOPool里面
+ this.addUTXO(transaction.receiverPubKey, transaction.value);
+ }
+
+ /**
+ * 验证交易合法性
+ * 验证余额
+ * 返回 bool
+ */
+ isValidTransaction(address, amount) {
+ return this.utxos[address].amount && this.utxos[address].amount >= amount
+ }
+
}
export default UTXOPool
\ No newline at end of file
diff --git a/src/tests/lesson1.js b/src/tests/lesson1.js
index 491da93..b40ee22 100644
--- a/src/tests/lesson1.js
+++ b/src/tests/lesson1.js
@@ -72,7 +72,7 @@ const main = () => {
// console.log(blockchain)
// 区块检查
- console.assert(longestChain.length === 3, 'Block height should be 2')
+ console.assert(longestChain.length === 3, 'Block height should be 3')
console.assert(
longestChain[2].hash === thirdBlock.hash,
`Height block hash should be ${thirdBlock.hash}`,
diff --git a/src/tests/lesson2.js b/src/tests/lesson2.js
index d0a91ee..2916b4f 100644
--- a/src/tests/lesson2.js
+++ b/src/tests/lesson2.js
@@ -73,7 +73,7 @@ const main = () => {
longestChain = blockchain.longestChain()
// 区块检查
- console.assert(longestChain.length === 3, 'Block height should be 2')
+ console.assert(longestChain.length === 3, 'Block height should be 3')
console.assert(
longestChain[2].hash === thirdBlock.hash,
`Height block hash should be ${thirdBlock.hash}`,
diff --git a/src/tests/lesson3.js b/src/tests/lesson3.js
index 83798b4..c621ace 100644
--- a/src/tests/lesson3.js
+++ b/src/tests/lesson3.js
@@ -73,7 +73,7 @@ const main = () => {
// console.log(blockchain)
// 区块检查
- console.assert(longestChain.length === 3, 'Block height should be 2')
+ console.assert(longestChain.length === 3, 'Block height should be 3')
console.assert(
longestChain[2].hash === thirdBlock.hash,
`Height block hash should be ${thirdBlock.hash}`,
diff --git a/src/tests/lesson4.js b/src/tests/lesson4.js
new file mode 100644
index 0000000..53050f8
--- /dev/null
+++ b/src/tests/lesson4.js
@@ -0,0 +1,161 @@
+import Block, { DIFFICULTY } from '../models/Block.js'
+import Blockchain from '../models/Blockchain.js'
+import Transaction from '../models/Transaction.js'
+
+import sha256 from 'crypto-js/sha256.js'
+import { calcNonce, validateHash } from '../validators/utils.js'
+
+const { log, assert } = console
+
+const main = () => {
+ const miner =
+ '04fc5783257a53bcfcc6e1ea3c5059393df15ef4a286f7ac4c771ab8caa67dd1391822f9f8c3ce74d7f7d2cb2055232c6382ccef5c324c957ef5c052fd57679e86'
+ // 初始化区块链
+ let blockchain = new Blockchain('BitCoin')
+
+ // 创建创世区块
+ let genesisBlock = new Block(blockchain, 'root', 0, 'root')
+
+ // 设置创世区块
+ blockchain.genesis = genesisBlock
+
+ // 验证区块难度
+ assert(DIFFICULTY > 0, 'Error: Need config DIFFICULTY on Block file')
+
+ // 构建区块
+ let newBlock = new Block(
+ blockchain,
+ genesisBlock.hash,
+ 1,
+ sha256(new Date().getTime().toString()).toString(),
+ miner,
+ )
+
+ assert(
+ newBlock.coinbaseBeneficiary === miner,
+ 'Error: Block miner public key error',
+ )
+
+ // 验证区块难度合法性
+ assert(newBlock.isValid() === false, 'Error: Very low probability')
+
+ newBlock = calcNonce(newBlock)
+
+ assert(newBlock.isValid() === true, 'Error: Very low probability')
+
+ blockchain._addBlock(newBlock)
+
+ let nextBlock = new Block(
+ blockchain,
+ newBlock.hash,
+ 2,
+ sha256(new Date().getTime().toString()).toString(),
+ miner,
+ )
+
+ nextBlock = calcNonce(nextBlock)
+
+ blockchain._addBlock(nextBlock)
+
+ let longestChain = blockchain.longestChain()
+
+ assert(longestChain.length === 2, 'Error: Block height should be 2')
+
+ let thirdBlock = new Block(
+ blockchain,
+ nextBlock.hash,
+ 3,
+ sha256(new Date().getTime().toString()).toString(),
+ )
+
+ thirdBlock = calcNonce(thirdBlock)
+
+ blockchain._addBlock(thirdBlock)
+
+ longestChain = blockchain.longestChain()
+
+ // 区块检查
+ assert(longestChain.length === 3, 'Block height should be 3')
+ assert(
+ longestChain[2].hash === thirdBlock.hash,
+ `Height block hash should be ${thirdBlock.hash}`,
+ )
+
+ // UTXO check
+
+ assert(
+ blockchain.containsBlock(thirdBlock) === true,
+ 'Error: blockchain should contain third block',
+ )
+
+ const latestUTXOPool = thirdBlock.utxoPool
+
+ log(latestUTXOPool)
+
+ assert(
+ latestUTXOPool.utxos[miner] && latestUTXOPool.utxos[miner].amount > 0,
+ 'Error: miner should got BTC',
+ )
+
+ assert(
+ latestUTXOPool.utxos[miner] && latestUTXOPool.utxos[miner].amount === 37.5,
+ 'Error: miner should got BTC',
+ )
+
+ // check transactions
+
+ let receiverPubKey =
+ '0416fb87fec6248fb55d3f73e5210b51514ebd44e9ff2a5c0af87110e8a39da47bf063ef3cccec58b8b823791a6b62feb24fbd8427ff6782609dd3bda9ea138487'
+
+ // 创建了一个名为 trx 的新交易对象(Transaction)。该交易对象使用了三个参数:miner、receiverPubKey 和 1。
+ // 其中,miner 是表示矿工(或发起交易的人)的标识,receiverPubKey 是接收方的公钥,1 是交易的数量(这里表示一单位的某种货币或资产)
+ let trx = new Transaction(miner, receiverPubKey, 1)
+
+ assert(validateHash(trx.hash), 'Error: Transaction hash invalid...')
+
+ assert(trx._calculateHash() === trx.hash, 'Error: Trx hash invalid')
+
+ assert(
+ latestUTXOPool.isValidTransaction(miner, 1) === true,
+ 'Error: trx need to be validate',
+ )
+
+ latestUTXOPool.handleTransaction(trx)
+
+ assert(
+ latestUTXOPool.utxos[miner] && latestUTXOPool.utxos[miner].amount === 36.5,
+ 'Error: miner should got right balance',
+ )
+
+ assert(
+ latestUTXOPool.utxos[receiverPubKey] &&
+ latestUTXOPool.utxos[receiverPubKey].amount === 1,
+ 'Error: receiver should got right balance',
+ )
+
+ // 打印最新的 UTXO pool
+ log(latestUTXOPool)
+
+ let badTrx = new Transaction(miner, receiverPubKey, 100)
+ latestUTXOPool.handleTransaction(badTrx)
+
+ assert(
+ latestUTXOPool.utxos[miner] && latestUTXOPool.utxos[miner].amount === 36.5,
+ 'Error: miner should got right balance',
+ )
+
+ // 这里一定要返回false
+ // 因为miner地址的amount只有36.5,不能完成value为100的交易
+ assert(
+ latestUTXOPool.isValidTransaction(receiverPubKey, 100) === false,
+ 'Error: trx need to be validate',
+ )
+
+ assert(
+ latestUTXOPool.utxos[receiverPubKey] &&
+ latestUTXOPool.utxos[receiverPubKey].amount === 1,
+ 'Error: receiver should got right balance',
+ )
+}
+
+main()
\ No newline at end of file
diff --git a/src/validators/utils.js b/src/validators/utils.js
index d493d44..440c2a4 100644
--- a/src/validators/utils.js
+++ b/src/validators/utils.js
@@ -1,6 +1,10 @@
import sha256 from 'crypto-js/sha256.js'
-export const validateHash = () => {}
+export const validateHash = (hash) => {
+ if (hash.length === 0 || !hash) return false
+
+ return hash.length === 64
+}
export const calcNonce = (block) => { // 定义一个名为 calcNonce 的常量,其值为一个接受一个 Block 对象作为参数的函数
console.log(`calc nonce of block ${block.height} `)