三生区块链 三生区块链
首页
  • 🔗 浅说区块链

    • 什么是区块链
    • 区块链运行机制
    • 共识机制解析
    • 区块链应用类型
    • 区块链与数字货币
    • 体验数字货币
    • 区块链的误区
    • 主流区块链项目
  • 💰 数字货币与资产

    • 比特币专题(一)
    • 比特币专题(二)
    • 数字货币与资产
    • 数字货币交易平台(一)
    • 数字货币交易平台(二)
  • 🌐 区块链与互联网

    • 互联网与区块链身份
    • 区块链即服务BaaS
    • 数字货币钱包服务
    • 区块链与供应链(一)
    • 区块链与供应链(二)
  • 🚀 如何从业

    • 区块链从业指南
    • 搭建迷你区块链(设计篇)
应用
  • 🛠 基础概念

    • 技术基础
    • 哈希与加密算法
    • UTXO与普通账户模型
    • 去中心化与区块链交易性能
  • 🔗 区块链类型与项目

    • 区块链项目详解:比特股BTS
    • 引人瞩目的区块链项目:EOS、IOTA、Cardano
    • 国内区块链项目技术一览
  • 🔐 加密与安全

    • P2P网络
    • 共识算法与分布式一致性算法
    • PoW共识
    • PoS
    • DPos
  • 💻 开发与实践

    • 智能合约与以太坊
    • 搭建一条属于自己的智能合约
  • 🔄 联盟链

    • 联盟链和它的困境
行业动态
  • 摘抄收录

    • ☆ 励志鸡汤
    • ❀ 人间烟火
    • ☣ 万物沦丧
    • ✌ 关掉烦恼
    • ✲ 小酒馆
  • 读书笔记

    • 《小狗钱钱》
    • 《穷爸爸富爸爸》
    • 《聪明人使用方格笔记本》
  • 📚 学习
  • 💡 心情杂货
  • 🔗 友情链接
关于
  • 🌐 网站
  • 📦 资源
  • 📂 分类
  • 🏷 标签
  • 📅 归档

三生

区块链小菜鸟(✪ω✪)
首页
  • 🔗 浅说区块链

    • 什么是区块链
    • 区块链运行机制
    • 共识机制解析
    • 区块链应用类型
    • 区块链与数字货币
    • 体验数字货币
    • 区块链的误区
    • 主流区块链项目
  • 💰 数字货币与资产

    • 比特币专题(一)
    • 比特币专题(二)
    • 数字货币与资产
    • 数字货币交易平台(一)
    • 数字货币交易平台(二)
  • 🌐 区块链与互联网

    • 互联网与区块链身份
    • 区块链即服务BaaS
    • 数字货币钱包服务
    • 区块链与供应链(一)
    • 区块链与供应链(二)
  • 🚀 如何从业

    • 区块链从业指南
    • 搭建迷你区块链(设计篇)
应用
  • 🛠 基础概念

    • 技术基础
    • 哈希与加密算法
    • UTXO与普通账户模型
    • 去中心化与区块链交易性能
  • 🔗 区块链类型与项目

    • 区块链项目详解:比特股BTS
    • 引人瞩目的区块链项目:EOS、IOTA、Cardano
    • 国内区块链项目技术一览
  • 🔐 加密与安全

    • P2P网络
    • 共识算法与分布式一致性算法
    • PoW共识
    • PoS
    • DPos
  • 💻 开发与实践

    • 智能合约与以太坊
    • 搭建一条属于自己的智能合约
  • 🔄 联盟链

    • 联盟链和它的困境
行业动态
  • 摘抄收录

    • ☆ 励志鸡汤
    • ❀ 人间烟火
    • ☣ 万物沦丧
    • ✌ 关掉烦恼
    • ✲ 小酒馆
  • 读书笔记

    • 《小狗钱钱》
    • 《穷爸爸富爸爸》
    • 《聪明人使用方格笔记本》
  • 📚 学习
  • 💡 心情杂货
  • 🔗 友情链接
关于
  • 🌐 网站
  • 📦 资源
  • 📂 分类
  • 🏷 标签
  • 📅 归档
  • 浅说区块链

  • 数字货币与数字资产

  • 区块链与当下互联网

  • 如何从业区块链

    • 从业区块链需要了解什么?
    • 搭建你的迷你区块链(设计篇 )
    • 搭建你的迷你区块链(实践篇)
      • 代码编写
        • 代码编写 1 Server
        • 代码编写 2 node
        • 代码编写 3 blockchain
        • 代码编写 4 network
        • 代码编写 5 consensus
        • 代码编写 6 database
        • 代码编写 7 commands
        • 代码编写 8 基础类
      • 首次运行
      • 首次挖矿
      • 第一笔交易
      • 分叉与合并
      • 总结
  • 基础
  • 如何从业区块链
三生
2023-09-27

搭建你的迷你区块链(实践篇)

请帮我优化 并以幽默的方式调整后以md的格式输出,我是探长三生 读者为区块链探险家 上一篇文章中,我们介绍了实现一个迷你区块链的大致思路。今天,我们将通过代码编写,以及简单的功能测试,来完成我们的迷你区块链 Tinychain。

除了正常的测试案例之外,我们还可以构造一些极端测试案例,来观察 Tinychain 的分叉合并,挖矿难度调整等情况。

# 代码编写

通过前文的分析,我们已经了解到了实践一个迷你区块链的大致思路。接下来,我将从顶层到底层来搭建区块链。

# 代码编写 1 Server

从链的顶层设计来看,我们需要一个入口,那么我们就从入口开始:我需要先为整个服务做一些基础设置,最后再来 Server.run()。

所以,我们的代码大概是这样子的。

node my_node;

mgbubble::RestServ Server{"webroot", my_node};

auto& conn = Server.bind("0.0.0.0:8000");

mg_set_protocol_http_websocket(&conn);

log::info("main")<<"httpserver started";

Server.run();

我们首先生成一个 node 实例,然后被 Server装载进去,最后设置好 Server 启动。

这个 Server 主要有两个功用,第一是向本地用户服务,也就是接受命令行,接受本地 RPC 调用;第二是接受外部网络传送进来是的新交易,和新的区块。所以 Server 是整个节点的入口。

# 代码编写 2 node

那么这里的 node 其实就是区块链的 node,里面包含了区块链的基本设置,这些一般都是硬编码在代码中的,例如一般区块链都有个 “魔法数”,实际上就是区块链 ID,这个 ID 会被放在所有消息的开头,如果区块链 ID 不匹配,则抛弃接收到的消息。

这里的区块链 ID 我们设置在这里。

blockchain(uint16_t id = 3721):id_(id) {

id_ = id;

create_genesis_block();

}

代码中所展示的 id_ 就是区块链 ID,在 Tinychain 的案例中,我也是硬编码的。

在一个 node 当中,至少要包含 network、blockchain、miner 三个模块。

public:

void miner_run(address_t address);

blockchain& chain() { return blockchain_; }

network& p2p() { return network_; }

private:

network network_;

blockchain blockchain_;

miner miner_{blockchain_};

network 也就是 P2P 网络类,blockchain 是区块链的核心类,miner 是共识模块下的核心类,三者被聚合到 node 中。

同时,node 也会提供一些 blockchain 和 miner 的接口,方便 Server 层调用。

# 代码编写 3 blockchain

一个 blockchain 实例,应当包含下面的内容。

uint16_t id_;

block genesis_block_;

chain_database chain_;

key_pair_database key_pair_database_;

memory_pool_t pool_;

genesisblock 就是创世区块,这个是预先生成好的。genesis_block 的信息也是被硬编码在代码中,我在 Tinychain 的例子为了方便测试,每个 genesis_block 都是可以自行生成的。

chaindatabase chain 是相对于 memorypool 而言的,chain 就是已经经过确认,并且在本地持久化存储的区块数据(由于时间有限,Tinychain 的案例中还未实现持久化存储,可以后续升级替换)。

memory_pool 是指还未经过确认,暂时驻留在内存中的交易池,交易池中的交易会在挖矿时,被导入到新的区块中。

这里的 pool 就是交易池。

key_pair_database 是指专门存储用户的私钥的数据库,同时提供私钥管理。

同时 blockchain 也负责统一对外提供上述功能的接口。

uint64_t height() { return chain_.height();}

block get_last_block();

bool get_block(sha256_t block_hash, block& out);

bool get_tx(sha256_t tx_hash, tx& out);

bool get_balance(address_t address, uint64_t balance);

auto id() {return id_;}

memory_pool_t pool() { return pool_; }

void pool_reset() { pool_.clear();}

void collect(tx& tx) {

pool_.push_back(tx);

}

void merge_replace(block_list_t& block_list);

除了上述接口之外,blockchain 还负责当发现自己处于较短的分叉链上时,自动合并到最长链。

# 代码编写 4 network

在 network 中,可用的地址簿代表了可用的其他对等节点,至少是连接过成功一次的。

public:

void broadcast(const block& block);

void broadcast(const tx& transaction);

void process(event_t ev, func_t f);

private:

endpoint_book_t book_;

channels_t channels_;

地址簿会随着网络的变化进行更新,实时状态的地址簿是驻留在内存中的,当节点关闭是,会被刷到持久化存储中。

channels 代表了已经激活的连接,这些连接可以被 broadcast 接口使用,当本地节点产生新的区块和交易时,会调起这些 channels。

当 P2P 网络产生了新的事件时,会通过 process 接口处理新到达的交易和区块,这一事件会传导给 blockchain 模块。

# 代码编写 5 consensus

consensus 的含义为共识,共识会在两种情况下产生,第一是对本地生产的交易进行验证,第二是外来的区块和交易进行验证。

无论是哪种情况,他们遵循的验证规则是一样的。validate_tx 和 validate_block 分别承担了这样的功能。

bool validate_tx(const tx& new_tx) ;

bool validate_block(const tx& new_block) ;

除了验证区块之外,还涉及到提供基础挖矿设施。我们知道挖矿分为两种,一种叫做 solo 挖矿,另外一种叫做联合挖矿。其实无论哪种挖矿类型,都必须用到 miner 类。

public:

void start(address_t& addr);

inline bool pow_once(block& new_block, address_t& addr);

tx create_coinbase_tx(address_t& addr);

private:

blockchain& chain_;

miner 类展示了在 solo 挖矿情况下,支持开始挖矿以及计算自己的 coinbase 的过程。

实际 pow_once 的挖矿代码如下,pow_once 被 start 调用,start 里面是一个死循环,死循环里面包了 pow_once 函数。

bool miner::pow_once(block& new_block, address_t& addr) {

auto&& pool = chain_.pool();

auto&& prev_block = chain_.get_last_block();

new_block.header_.height = prev_block.header_.height + 1;

new_block.header_.prev_hash = prev_block.header_.hash;

new_block.header_.timestamp = get_now_timestamp();

new_block.header_.tx_count = pool.size();

uint64_t time_peroid = new_block.header_.timestamp - prev_block.header_.timestamp;

if (time_peroid <= 10u) {

new_block.header_.difficulty = prev_block.header_.difficulty + 9000;

} else {

new_block.header_.difficulty = prev_block.header_.difficulty - 3000;

}

uint64_t target = 0xffffffffffffffff / prev_block.header_.difficulty;

auto&& tx = create_coinbase_tx(addr);

pool.push_back(tx);

new_block.setup(pool);

for ( uint64_t n = 0; ; ++n) {

new_block.header_.nonce = n;

auto&& jv_block = new_block.to_json();

auto&& can = to_sha256(jv_block);

uint64_t ncan = std::stoull(can.substr(0, 16), 0, 16);

if (ncan < target) {

new_block.header_.hash = can;

log::info("consensus") << "new block :" << jv_block.toStyledString();

log::info("consensus") << "new block :" << can;

return true;

}

}

上面的代码从一开始到 for 循环之前,都可以提取出来,做成叫做 getblocktemplate 的接口,getblocktemplate 是一种 JSON-RPC 调用。

通过这个调用,就可以把挖矿的状态信息分享给其他矿机,矿机拿到 blocktemplate 以后直接进行 nonce 部分暴力搜索即可。

# 代码编写 6 database

database 是偏底层的接口,主要的功能有两个,第一是提供区块和私钥的持久化存储,第二是提供交易和区块的查询接口。

上文 blockchain 中的 blockchain_database 和 keypair_database 都是从 database 派生过来的。

key_pair_database

class key_pair_database

{

public:

key_pair get_new_key_pair();

const key_pair_database_t& list_keys() const;

private:

key_pair_database_t key_pair_database_;

};

blockchain_database

public:

uint64_t height();

auto get_last_block();

bool get_block (const sha256_t block_hash, block& b);

bool get_tx (const sha256_t tx_hash, tx& t);

bool push_block (const block& b);

bool pop_block (cconst sha256_t block_hash);

private:

chain_database_t chain_database_;

# 代码编写 7 commands

commands 提供了开发者命令行交互接口。

bool exec(Json::Value& out);

static const vargv_t commands_list;

private:

vargv_t vargv_;

node& node_;

首先得有一个可识别的命令列表,接着是执行接口,例如命令行发起生成新 key_pair 的过程,执行 getnewkey 命令。

先被 command 解析,接着执行 exec,执行的时候需要用到 node 对象。

实际上 command 类比较繁琐,因为一个功能复杂的钱包,维护的命令和种类可能多达几十种。

同时命令又可以被 JSON-RPC 调用,所以一般命令行客户端本身就是一个轻量级的 http-client。

std::string url{"127.0.0.1:8000/rpc"};

HttpReq req(url, 3000, reply_handler(my_impl));

# 代码编写 8 基础类

基础类是实际生成公私钥对、构建交易 tx 的基本单元类,构建区块的基本单元类。

key_pair:

class key_pair

{

public:

key_pair() {

private_key_ = RSA::new_key();

public_key_ = private_key_.public_key();

}

address_t address();

sha256_t public_key() const;

uint64_t private_key() const;

private:

private_key_t private_key_;

public_key_t public_key_;

tx:

public:

input_t inputs() const { return inputs_; }

output_t outputs() const { return outputs_; }

sha256_t hash() const { return hash_; }

private:

input_t inputs_;

output_t outputs_;

sha256_t hash_;

block:

class block

{

public:

typedef std::vector tx_list_t;

struct blockheader {

uint64_t nonce{0};

uint64_t height{0};

uint64_t timestamp{0};

uint64_t tx_count{0};

uint64_t difficulty{0};

sha256_t hash;

sha256_t merkel_root_hash;

sha256_t prev_hash;

};

std::string to_string() {

auto&& j = to_json();

return j.toStyledString();

}

sha256_t hash() const { return header_.hash; }

void setup(tx_list_t& txs) {tx_list_.swap(txs);}

private:

blockheader header_;

tx_list_t tx_list_;

# 首次运行

我们编写完基础类和基本结构的代码之后,就可以运行试一试。

编译成功是这样子的。

我们可以看到有 Tinychain 和 Cli-tinychain。

Tnychain 就是我们的核心程序,cli-tinychain 就是我们的命令行客户端。

实际上我在 Server 里还嵌入了一个可视化的 Websocket 界面。

只需要在 Tinychain 可执行文件同目录底下创建 webroot 文件夹,将 etc 底下的 index 放入 webroot 下,接着打开浏览器 127.0.0.1:8000 就可以看到了。

实际上这个页面我想做成区块的监视页面,只是还没改造完成,目前支持发送命令。

我们开始首次运行 Tinychain。

运行后,等 node 和 server 全部 started,就可以开始操作命令行了。

也可以通过日志进行监视,但是需要在代码处详细打桩,这次我偷懒了,没有好好打,所以不多,直接查看同目录下 debug.log 和 error.log 即可。

# 首次挖矿

我们通过./tinychain 启动之后,开始第一次挖矿。

✘ chenhao@chenhaodeMacBook-Pro  ~/workspace/tinychain/build/bin   master  ./tinychain

20180610T232347 INFO [main] started

20180610T232347 INFO [node] node started

20180610T232347 INFO [main] httpserver started

20180610T232356 INFO [consensus] new block :{

"header" :

{

"difficulty" : 9001,

"hash" : "",

"height" : 1,

"merkel_header_hash" : "",

"nonce" : 0,

"prev_hash" : "00b586611d6f2580e1ea0773ec8b684dc4acf231710519e6272ed7d0c61ed43e",

"timestamp" : 1528644236,

"tx_count" : 0

},

"txs" :

[

{

"hash" : "cddf6e838eff470d81155cb4c26fd3a7615b94a00e82f99b1fd9f583d7bc0659",

"inputs" :

[

{

"hash" : "00000000000000000000000000000000",

"index" : 0

}

],

"outputs" :

[

{

"address" : "122b03d11a622ac3384904948c4d808",

"value" : 1000

}

]

}

]

}

20180610T232356 INFO [consensus] new block :0de5c36420aab2f7fc9413cfbd21bece697a349106771dc58b25a6a099d6aa86

20180610T232357 INFO [consensus] new block :{

"header" :

{

"difficulty" : 18001,

"hash" : "",

"height" : 2,

"merkel_header_hash" : "",

"nonce" : 6048,

"prev_hash" : "0de5c36420aab2f7fc9413cfbd21bece697a349106771dc58b25a6a099d6aa86",

"timestamp" : 1528644236,

"tx_count" : 0

},

"txs" :

[

{

"hash" : "cddf6e838eff470d81155cb4c26fd3a7615b94a00e82f99b1fd9f583d7bc0659",

"inputs" :

[

{

"hash" : "00000000000000000000000000000000",

"index" : 0

}

],

"outputs" :

[

{

"address" : "122b03d11a622ac3384904948c4d808",

"value" : 1000

}

]

}

]

}

刚开始挖矿会比较快,随着难度提升,会趋向于稳定到 10 秒种左右一个块,如果长时间不出块,难度会自动降下来。曾经元界的代码在难度调整上有缺陷,遭受了严重的 “难度坠落” 攻击。

我们可以通过这个位置观察难度调整的情况。

# 第一笔交易

我们保持挖矿,接下来发送一笔交易。我们先通过 getnewkey 命令获得一个新公私钥对以及对应的地址。

接着发送第一笔交易。

探测到接下来被打包到区块中。

# 分叉与合并

区块链分叉是数据全网不一致的表现,通常是矿工节点行为不一致导致的,常见的有网络分区和协议不兼容,如果同时产生,那么必然会出现两条比较长的分叉链。

在现实情况中,分叉 1 个是最常见的,2 个已经非常罕见了,3 个以上基本是网络分区造成的。

如果我们要在 Tinychain 中实践网络分区和分叉,我们需要构建局域网多节点私链环境,可以通过 docker 来试验。

通过本文,你可以看到即使是搭建一个迷你区块链,它的工作量也是巨大的,其中不仅仅只是组合几个基础组件那么简单,还要涉及各个模块的设计和交互等详细的工作。

由于在短时间内全部搭建以及实现 Tinychain 所有功能是不可行的,在这里,我只为你提供了一些实践的思路。

目前 Tinychain 缺失了 P2P 网络实现、RSA 公私钥对集成、共识模块的交易和区块的验证等内容,我会在后续逐渐完善,你也可以跟我一起补充。

# 总结

好了,通过今天的代码实践,我们实现了迷你区块链 Tinychain,并且,通过运行与测试 Tinychain,我们了解到了一个最简单区块链的运行原理,希望通过今天的文章,可以帮你加深对区块链技术的理解。

区块链技术只是作为基础设施,服务于广大的开发者和业务需求。目前区块链的发展远远不止 Tinychain 中所展现的样子,我们还需要去考虑区块链 2.0 智能合约,如何设计 Token 经济等一些问题。

随着区块链的发展和应用规模,区块链安全问题也日益突出,所以今天的问题是,如果要攻击 Tinychain,可以采取什么手段呢?你可以给我留言,我们一起讨论。

感谢你的收听,我们下次再见。

上次更新: 2023/10/09, 1:10:00
搭建你的迷你区块链(设计篇 )

← 搭建你的迷你区块链(设计篇 )

最近更新
01
到底什么才是区块链?
10-08
02
区块链的运行机制
10-08
03
费曼学习法
10-08
更多文章>
Theme by Vdoing | Copyright © 2023-2025 三生区块链 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式