{"content":{"title":"使用JavaScript实现一个简单的区块链","body":"> 本文内容参考自：[Learn Blockchains by Building One](https://medium.com/@vanflymen/learn-blockchains-by-building-one-117428612f46#id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjkzYjQ5NTE2MmFmMGM4N2NjN2E1MTY4NjI5NDA5NzA0MGRhZjNiNDMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiIyMTYyOTYwMzU4MzQtazFrNnFlMDYwczJ0cDJhMmphbTRsamRjbXMwMHN0dGcuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiIyMTYyOTYwMzU4MzQtazFrNnFlMDYwczJ0cDJhMmphbTRsamRjbXMwMHN0dGcuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDc4NTQwOTIwNzQ5MzIyNDY5NjkiLCJlbWFpbCI6ImRlc2lnbi5jYXJyeXdhbmdAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5iZiI6MTcxMzE4ODMyNSwibmFtZSI6IkNhcnJ5IFdhbmciLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jSk9RX2x1ZjVFblBFRUY4X0ZhT1lOTlJjRTkzSkJqeGpJT2JDbDJ4dHYxNmpYamFlbz1zOTYtYyIsImdpdmVuX25hbWUiOiJDYXJyeSIsImZhbWlseV9uYW1lIjoiV2FuZyIsImlhdCI6MTcxMzE4ODYyNSwiZXhwIjoxNzEzMTkyMjI1LCJqdGkiOiI4YzAzN2ZjZmFhMjU1OTU4YzI0ZjYzNTIyNWU4NzRiMDJiNTRkNTc1In0.iJOXUw2dNTfOB_9Z_murlx-31M9hk-hnqDGkRhPbCBHLUQIC87estR1Kw4_zm8MYggsoyndnQ0pq6PBmpyP31ssSzxpqT_Ob-xTfiqulkQrTGA1HSe3SVwJ4IGsEClMJny46s5i5LhcrsVrZRHUSJFGGoCXeYiorkcegt-i8pB0VG_40z4pMKOrLVAsG8trTX4plL3lQt17ng9HdTu8CU6KLQ8JldY7H63dogT5VXQjwyLY5zQZvVNXAi1-4nUO7CPLDElKLyqYJjdskPqt2t_7OxmHczi2uKAKG6qujdn_HNdwyUqLYD5pGD1VJ8VVq4eE_LwpWBBqNUPLQzvAekQ)\r\n\r\n\r\n本文使用JavaScript实现一个简单的区块链，主要在node.js环境中运行。使用crypto-js来为区块链中的块创建哈希加密，使用express.js来创建用于与区块链交互的API。\r\n本文源代码可查看：[GitHub - CarryWang/mini-blockchain: use node.js implement a mini blockchain](https://github.com/CarryWang/mini-blockchain)\r\n\r\n以下是实现区块链的步骤：\r\n\r\n1. 创建一个新的Node.js项目并安装必要的库：crypto-js、express。\r\n2. 定义区块链中的区块结构。每个块应该包含唯一的索引、时间戳和想要存储在块中的数据。\r\n3. 创建一个区块链类，包含一些操作区块的方法，例如向链中添加新块。\r\n4. 使用 express.js 创建api，可以使用postman查询区块链，添加交易，以及执行挖矿操作。\r\n5. 使用 postman 测试区块链。\r\n## 1. 创建一个新的Node.js项目并安装必要的库：crypto-js、express。\r\n首先初始化一个node项目：\r\n```javascript\r\nnpm init\r\n```\r\n在package.json文件中，添加type字段：\r\n```javascript\r\n\"type\": \"module\"\r\n```\r\n安装crypto-js和express：\r\n```javascript\r\nnpm install crypto-js\r\nnpm install express\r\nnpm install body-parser\r\n```\r\n在index.js中写入：\r\n```javascript\r\nimport express from \"express\";\r\nimport bodyParser from \"body-parser\";\r\n\r\nconst app = express();\r\nconst port = 3000;\r\n\r\napp.listen(port, () => {\r\n    console.log(`Example app listening on port ${port}`);\r\n});\r\n```\r\n在terminal中输入：\r\n```javascript\r\nnode index.js\r\n```\r\n你将看到：\r\n```javascript\r\nServer running on port 3000.\r\n```\r\n到这里，基本的node.js设置搞定。\r\n\r\n## 2. 定义区块链中的区块结构。每个块应该包含唯一的索引、时间戳和想要存储在块中的数据。\r\n新建`blockchain.js`文件，定义我们的区块结构：\r\n```javascript\r\nimport crypto from \"crypto-js\";\r\n\r\nclass Block {\r\n    constructor(index, timestamp, transactions = [], previousHash = \"\") {\r\n        this.index = index;\r\n        this.timestamp = timestamp;\r\n        this.transactions = transactions;\r\n        this.previousHash = previousHash;\r\n        this.hash = \"\";\r\n        this.nonce = 0;\r\n    }\r\n\r\n    calcHash() {\r\n        return crypto\r\n            .SHA256(\r\n                this.index +\r\n                    this.timestamp +\r\n                    JSON.stringify(this.transactions).toString() +\r\n                    this.previousHash +\r\n                    this.nonce,\r\n            )\r\n            .toString();\r\n    }\r\n\r\n    mineBlock(difficulty) {\r\n        while (!this.hash.startsWith(\"0\".repeat(difficulty))) {\r\n            this.nonce++;\r\n            this.hash = crypto\r\n                .SHA256(\r\n                    this.index +\r\n                        this.timestamp +\r\n                        JSON.stringify(this.transactions).toString() +\r\n                        this.previousHash +\r\n                        this.nonce,\r\n                )\r\n                .toString();\r\n        }\r\n    }\r\n}\r\n\r\n```\r\n`Block`类包含6个属性，2个方法。\r\n\r\n- `index`：数字，区块的索引。\r\n- `timestamp`：数字，时间戳。\r\n- `transactions`：数组，储存每一笔交易信息。\r\n- `previousHash`：哈希值，记录上一个区块的哈希值。\r\n- `hash`：哈希值，记录当前区块的哈希值。\r\n- `nonce`：数字，用于找到满足特定条件的哈希值的随机数，pow相关。\r\n- `calcHash`：函数，计算哈希值，将`index`, `timestamp`, `transactions`, `previousHash`, `nonce`拼接在一起输出哈希值。\r\n- `mineBlock`：函数，用于工作量证明的方法，接受一个代表复杂度的数字。本文对于工作量证明的计算条件为：寻找前4位为0的哈希值。\r\n## 3. 创建一个区块链类，包含一些操作区块的方法，例如向链中添加新块。\r\n同样在`blockchain.js`文件中，定义一个区块链类：\r\n```javascript\r\nexport class Blockchain {\r\n    constructor() {\r\n        this.difficulty = 4;\r\n        this.pendingTrasactions = [];\r\n        this.chain = [this.createGenesisBlock()];\r\n    }\r\n\r\n    createGenesisBlock() {\r\n        const genesisPreviousHash = \"0\".repeat(64);\r\n        const genesisBlock = new Block(0, Date.now(), [], genesisPreviousHash);\r\n        genesisBlock.mineBlock(this.difficulty);\r\n        genesisBlock.transactions.push({\r\n            sender: \"\",\r\n            recipient: genesisBlock.hash,\r\n            amount: 50,\r\n        });\r\n        return genesisBlock;\r\n    }\r\n\r\n    createBlock() {\r\n        const minerReward = {\r\n            sender: \"\",\r\n            recipient: \"miner_adress\",\r\n            amount: 50,\r\n        };\r\n        this.pendingTrasactions.push(minerReward);\r\n\r\n        const block = new Block(\r\n            this.getLatestBlock().index + 1,\r\n            Date.now(),\r\n            this.pendingTrasactions,\r\n            this.getLatestBlock().hash,\r\n        );\r\n        block.mineBlock(this.difficulty);\r\n\r\n        this.chain.push(block);\r\n\r\n        this.pendingTrasactions = [];\r\n    }\r\n\r\n    getLatestBlock() {\r\n        return this.chain[this.chain.length - 1];\r\n    }\r\n\r\n    createTransaction(transaction) {\r\n        this.pendingTrasactions.push(transaction);\r\n        return this.chain[this.chain.length - 1].index + 1;\r\n    }\r\n}\r\n\r\n```\r\n`Blockchain`类包含3个属性，4个方法。\r\n\r\n- `difficulty`：数字，代表复杂度的数字。\r\n- `pendingTrasactions`：数组，资源池，用于存储每一笔交易。当新的区块产生时，会将资源池中的交易信息存入新区块中。\r\n- `chain`：数组，储存每一个区块。\r\n- `createGenesisBlock`：函数，创建第一个区块（创世块）。\r\n- `createBlock`：函数，创建新区块。\r\n- `getLatestBlock`：函数，返回最后一个区块。\r\n- `createTransaction`：函数，创建一笔交易，返回将被写入区块的索引值。\r\n## 4. 使用 express.js 创建api，可以使用postman查询区块链，添加交易，以及执行挖矿操作。\r\n接下来需要实现三个api，分别是：\r\n```javascript\r\nGET /chain                //查看区块链\r\nGET /mine                 //执行挖矿，成功后增加一个区块\r\nPOST /transactions/new    //添加一笔交易\r\n```\r\n回到`index.js`中，导入`blockchain.js`，并添加`route`：\r\n```javascript\r\nimport express from \"express\";\r\nimport bodyParser from \"body-parser\";\r\n\r\nimport { Blockchain } from \"./blockchain.js\";\r\n\r\nconst app = express();\r\nconst port = 3000;\r\n\r\napp.listen(port, () => {\r\n    console.log(`Example app listening on port ${port}`);\r\n});\r\n\r\napp.use(express.json());\r\napp.use(bodyParser.urlencoded({ extended: false }));\r\napp.use(bodyParser.json());\r\n\r\nconst blockchain = new Blockchain();\r\n\r\napp.get(\"/mine\", (req, res) => {\r\n    blockchain.createBlock();\r\n    res.status(200).send(blockchain.chain[blockchain.chain.length - 1]);\r\n});\r\n\r\napp.post(\"/transactions/new\", (req, res) => {\r\n    // const transaction = {\r\n    //     sender: \"sender_adress\",\r\n    //     recipient: \"recipient_adress\",\r\n    //     amount: 50,\r\n    // };\r\n    const transaction = req.body;\r\n    const blockNumber = blockchain.createTransaction(transaction);\r\n    res.status(200).send(`will be added to block ${blockNumber}`);\r\n});\r\n\r\napp.get(\"/chain\", (req, res) => {\r\n    res.status(200).send(blockchain.chain);\r\n});\r\n\r\n```\r\n## 5. 使用 postman 测试区块链。\r\n测试 GET [http://localhost:3000/chain](http://localhost:3000/chain)：\r\n![chain.png](https://img.learnblockchain.cn/attachments/2024/04/2cWCQN5E6621493ed2562.png)\r\n测试 GET [http://localhost:3000/mine](http://localhost:3000/mine)：\r\n![mine.png](https://img.learnblockchain.cn/attachments/2024/04/kFheICeL6621495ca42aa.png)\r\n测试 POST [http://localhost:3000/transactions/new](http://localhost:3000/transactions/new)：\r\n![:transactions:new.png](https://img.learnblockchain.cn/attachments/2024/04/pOSGFBaB6621496ef242c.png)"},"author":{"user":"https://learnblockchain.cn/people/19477","address":"0xa0466a82b961e85077d4a8debc35fbf6cf18d464"},"history":"bafkreifcmekm77eaub5i53yehz7tcab7rqky7eeiknu6as4hb7gkdie2te","timestamp":1713458281,"version":1}