0%

区块链实训3

共识机制是一套算法、规则或协议,用于确保分布式系统中的多个节点能够就特定事务或状态达成一致意见。在分布式珠境中,书志阌的通信可能受到网络延迟、故障或恶意行为的影响,因此需要共识机制来解决教据不一教性的问题。

第一部分:区块链共识机制学习

1. 共识机制的定义和作用

共识机制是一套算法、规则或协议,用于确保分布式系统中的多个节点能够就特定事务或状态达成一致意见。在分布式珠境中,书志阌的通信可能受到网络延迟、故障或恶意行为的影响,因此需要共识机制来解决教据不一教性的问题。共识机制的主要作用包括:

1.1. 数据一致性和完整性

共识机制确保所有节点在特定事件或操作上达成一致,从而维护数据的一致性和完整性。无论节点之间的通信是否受到干扰,系统都能保持统一的数据副本,避免数据冲突和不一致。

1.2. 分布式事务处理

在分布式数据库或账本中,共识机制确保多个节点对事务的执行达成一致,从而保证事务的合法性和正确性。这对于金融交易、供应链管理等领域至关重要。

1.3. 防止双重支付和欺诈

在加密货币领域,共识机制防止同一笔资产被多次消费,确保交易的有效性和安全性从而防止欺诈行为和双重支付。

1.4. 网络安全和抵抗攻击

共识机制可以增强系统的安全性,防止恶意节点篡改数据或发动网络攻击。它通过验证节点的行为和数据,提高系统的抵抗能力。

1.5. 去中心化控制

共识机制使分布式系统能够在无需中心控制机构的情况下运行。这意味着系统可以实现去中心化的控制和管理,减少了单点故障的风险。总之,共识机制是分布式系统中的关键组成部分,它确保了节点之间的协作和一致性,为系统的可靠性和稳定性提供了坚实的基础。不同的共识机制在不同的情境下具有优势和特点,我们将在接下来的小节中深入探讨这些机制的分类和特点。

2. 共识机制的分类和特点

在分布式系统和区块链技术的领域中,共识机制是确保多个节点就特定事件或状态达成一致的核心方法。不同的共识机制基于不同的原则和机制,各自具有独特的特点和优势。

2.1. 工作量证明(Proof of Work,PoW)

工作量证明是最早被广泛应用的共识机制之一,其核心思想是通过解决复杂的数学难题,以证明节点对网络的贡献,从而获得权利参与共识过程。PoW机制具有以下特点:

安全性高:PoW的安全性源自于解决难题的复杂性,攻击者需要大量的计算能力来破解,从而防止恶意攻击的行为。

抵抗攻击:攻击者需要掌握绝大部分计算能力才能成功攻击,这在实际上是不切实际的,保护了网络的安全性。

去中心化:PoW机制鼓励广泛的节点参与,没有中心化的控制权,因此具有良好的分散性。

然而,PoW机制也存在能源消耗大、性能瓶颈等问题,尤其在当今环保意识高涨的情况下,其高能耗特性备受争议。

2.2. 权益证明(Proof of Stake, PoS)

权益证明是另一种常见的共识机制,其核心理念是参与共识的权重与持有的加密货币数量相关。PoS机制具有以下特点:

能源效率:相较于PoW,PoS消耗的能源要少得多,这在一定程度上解决了PoW的能源浪费问题。

抵抗攻击:PoS同样需要攻击者掌握大量代币,才有可能进行恶意攻击,这一特点保护了网络的安全性。

分散与权力:PoS可能导致持有大量代币的人在网络中拥有更多的权力,从而在一定程度上实现了权力的分散。

然而,PoS机制也存在权力集中和节点不一定积极参与的问题,可能影响网络的安全性。

2.3. 权益质押(Delegated Proof of Stake, DPoS)

权益质押是在PoS机制基础上的一种改进,其核心思想是通过代理节点来参与共识过程。DPoS机制具有以下特点:

快速和高吞吐:通过限制参与共识的节点数量,DPoS可以实现更快的交易确认和更高的吞吐量。

分散授权:持币者可以将代币委托给代理节点,从而实现更广泛的参与和分散的授权。

治理和决策:DPoS机制通常允许代币持有者就网络的治理和决策进行投票。

然而,DPoS机制也存在中心化风险和安全性相对较低的问题,代理节点数量较少可能导致权力集中。

2.4. 共识机制的组合(Hybrid)

在实际应用中,也可以采用不同共识机制的组合,以取得各自机制的优势,实现更好的性能、安全性和可扩展性。通过综合不同机制,可以应对特定问题,如提高网络的能源效率或减少中心化风险。然而,混合共识机制可能引入更多的复杂性,需要权衡不同机制的权衡和劣势。

2.5. 区块链中的共识机制

共识机制在区块链技术中至关重要,确保所有节点对账本的修改达成一致。不同类型的区块链(公有链、私有链、联盟链)可以根据其需求选择适合的共识机制。以下是一些示例:

比特币(公有链)使用PoW共识机制,保障了分布式数字货币的安全性。

以太坊(公有链)已经过渡到PoS,以提高能源效率和可扩展性。

超级账本(联盟链)理论上支持所有共识机制,但需要自己实现。

深入理解共识机制的分类、特点和应用,有助于在不同场景中做出明智的选择,以满足区块链应用的需求。在接下来的章节中,我们将进一步深入探讨每种共识机制的工作原理、实际应用案例和未来发展趋势。

3. 常见共识机制详解

在本章节中我们将介绍一些共识机制的原理、流程、优势以及他们的局限性

3.1. 工作量证明(Proof of Work, PoW)

工足量证明(Proof of Work, PoW)是一种十分常见的区块链共识机制,最早由比特币引入,以太坊1.0以及许多货币都使用工作量证明来当做他们的底层共识机制。它通过解决数学难题来证明参与者的工作量,从而获得创建新区块的权利。由于PoW的概念相对简单,因此十分适合新手们对共识机制进行学习。

3.1.1. PoW的原理和流程

PoW的核心原理是基于哈希函数的不可预测性和随机性。在PoW中,矿工需要寻找一个特定的哈希值,使得区块头的哈希满足一定的条件,通常要求哈希值以一定数量的前导零开头。这个条件在数学上是难以预测的,唯一的方法是通过不断尝试不同的随机数来进行哈希计算,直到找到满足条件的哈希值,这需要大量的计算能力。

具体来说,PoW的原理可以概括为以下步骤:

(1)选择交易和构建区块头:矿工从交易池中选择一组待确认的交易,并构建区块头,包括前一个区块的哈希、交易列表的哈希等信息。

(2)挑战难题:网络根据当前的难度目标,提出一个数学难题,要求找到一个特定的哈希值,使得区块头的哈希满足条件。

(3)工作量竞赛:矿工开始尝试不同的随机数,将随机数插入区块头中,然后进行哈希计算。由于哈希函数的随机性,不同的随机数会产生不同的哈希值。

(4)寻找满足条件的哈希值:矿工不断尝试不同的随机数,计算区块头的哈希,直到找到一个哈希值,满足难题条件(即以一定数量的前导零开头)。

PoW的整体流程可以分为以下几个关键步骤:

(1)交易池和构建区块:首先,网络中的节点将待确认的交易放入交易池。矿工从交易池中选择一定数量的交易,并将它们组合成一个区块。

(2)难题设置:网络设定一个难度目标,通常是一个表示难度的数字。这个数字决定了满足难题条件的哈希值需要以多少前导零开头。

(3)工作量竞赛:矿工开始尝试不同的Nonce值,将Nonce插入区块头中,然后计算区块头的哈希值。

(4)验证和广播:一旦矿工找到满足条件的哈希值,就将该区块广播给网络中的其他节点。其他节点会验证这个区块的工作量,以确保矿工的计算是有效的。

(5)奖励和新区块:如果其他节点验证通过,该区块将被添加到区块链中作为新的区块。矿工获得一定数量的奖励,通常包括新发行的代币和交易手续费。

image-20241018210050974

3.1.2. PoW的优势和局限性

PoW作为一种经典的共识算法,具有以下优势:

(1)安全性高: PoW机制的安全性源于解决数学难题的复杂性。攻击者需要大量计算能力来篡改数据,阻碍了恶意行为。

(2)抵抗攻击: 攻击者需要掌握网络中大多数计算能力,才能控制网络。这在实际上是难以实现的,保障了网络的去中心化和安全性。

(3)去中心化: PoW鼓励广泛的节点参与共识,没有中心化的控制权。网络中的参与者通过竞争解决难题来获得权利,没有单一实体能够垄断共识过程。

然而,PoW也存在以下局限性:

(1)能源消耗: PoW机制耗费大量电力,特别是随着竞争的加剧,电力消耗逐渐增大,引发环保和可持续性问题。

(2)性能瓶颈: 由于难题的复杂性,计算量逐渐增加,可能导致网络吞吐量下降,交易确认时间延长。通常需要数个区块确认后,交易才能被视为有效。这可能导致用户体验不佳,尤其是在需要快速完成交易的场景中。

(3)中心化趋势: 为了获得更高的算力,矿工可能加入矿池,导致算力集中在少数矿池,增加了中心化风险。

PoW在理论上是去中心化的,允许任何人参与,但在实践中,由于经济因素和技术要求,它可能会导致算力集中在少数矿池和资源丰富的参与者手中。这种现象并不是说PoW完全去中心化或完全中心化,而是存在一种动态的权衡。

最后工作量证明机制是区块链技术的重要组成部分,尤其在比特币等公有链中发挥着关键作用。然而,其高能耗和潜在的中心化趋势也促使人们寻求更环保、更分散化的共识机制。在实际应用中,需要权衡PoW的优势与局限性,结合具体需求做选择。

3.2. 权益证明(Proof of Stake, Pos)

权益证明(Proof of Stake,PoS)是一种与工作量证明(PoW)不同的区块链共识机制,它通过参与者持有的代币数量来决定其创建新区块的权利。PoS机制的核心思想是通过代币的权益来分配网络中的权力和责任,以实现共识过程。

3.2.1. PoS的原理和流程

PoS的核心思想是,持有更多代币的参与者在网络中具有更大的权利和责任。与PoW中通过解决数学难题来获得权利不同,PoS中的权利分配是根据持有的代币数量来确定的。参与者需要将一定数量的代币锁定(抵押)在网络中,作为参与共识的凭证。他们的抵押数量决定了他们被选中的概率。PoS的整体流程可以分为以下几个关键步骤:

(1)抵押代币:参与者将一定数量的代币抵押到网络中。这些代币将被锁定,作为参与共识的凭证。抵押的数量通常与被选中的概率成正比。

(2)选择出块节点:网络根据参与者抵押的代币数量来选择出块节点,即有权力创建新区块的节点。持有更多代币的参与者被选中的概率更高。

(3)验证和创建区块:被选中的出块节点负责验证交易和创建新区块。他们收集待确认的交易,构建区块,然后进行签名和广播。

(4)奖励和惩罚:如果出块节点成功创建新区块,他们将获得交易手续费和一定数量的奖励。然而,如果出块节点的行为被发现有问题,比如双花攻击,他们的抵押代币可能会被惩罚。

3.2.2. PoS的优势和局限性

PoS相较于其他证明方式有以下几点优势:

(1)能源效率:PoS相对于PoW来说,能源消耗大幅降低。PoW机制需要大量计算能力解决复杂难题,而PoS则通过代币抵押来选取节点,无需大量能源消耗。这使得PoS成为一种更环保和可持续的共识机制。

(2)去中心化:PoS鼓励代币持有者积极参与共识过程,避免了PoW中可能出现的算力集中问题。持有更多代币的节点在共识中具有更大的权利,但这不同于PoW中算力竞争,不会导致资源垄断。

(3)安全性:PoS机制中,攻击者需要掌握大量代币才能攻击网络,这增加了攻击的成本和难度。这种设计保障了网络的安全性,因为攻击者需要破坏自身的利益来进行攻击。

(4)能够扩展性:PoS机制相对于PoW更容易实现扩展性,因为无需进行复杂的计算,节点只需要对交易进行验证和区块创建。这使得网络可以更轻松地处理更多的交易和数据。

(5)减少硬件竞争:PoW机制中,矿工为了获得更高的算力,不断更新硬件,造成资源浪费。在PoS中,算力不是关键因素,减少了硬件竞争,节约了资源。

同时PoS也有局限性和不足之处,主要有以下几点:

(1)权力集中:虽然PoS鼓励代币持有者参与共识,但拥有更多代币的参与者在网络中拥有更大的权力。这可能导致权力集中问题,让少数人掌控网络决策。

(2)节点激励问题:拥有代币并不一定意味着节点会积极参与共识过程。一些节点可能缺乏积极性,影响网络的安全性和稳定性。

(3)链长问题:PoS机制中,参与者倾向于选择拥有更多代币的链,导致链长问题。这可能让拥有更多代币的人在网络决策中拥有更大话语权,不利于去中心化。

(4)初始分配不公平:PoS的初始分配可能存在不公平性,最初拥有代币的人在网络中拥有更大的权利。这可能导致财富不平等问题。

(5)算法设计复杂性:PoS的算法设计相对复杂,需要考虑权益抵押、随机性等因素。一个不合理的设计可能导致系统不稳定或易受攻击。

综合来看,PoS作为一种区块链共识机制,在解决了能源消耗和性能问题的同时,也面临着权力集中、节点激励和初始分配不公平等一系列挑战。在选择PoS作为共识机制时,需要充分考虑其优势和局限性,结合具体应用场景做出合适的决策。

3.3. 其他常见的共识机制

除了工作量证明(PoW)和权益证明(PoS),在区块链领域还存在许多其他有趣和创新的共识机制,它们各自针对不同的问题和需求,旨在提供更高效、更安全、更快速的共识算法。以下是一些其他常见的共识机制的简要介绍:

  1. 权威共识(Delegated Proof of Stake,DPoS)
    权威共识是一种基于代表性和选举的共识机制,代币持有者通过投票选举一组代表来确认交易和生成区块。这些代表通常由社区选出,负责验证交易和维护网络。DPoS具有较高的交易处理速度和可扩展性,但也引入了一定程度的中心化,因为少数代表可能掌握网络控制权。
  2. 拜占庭容错(Byzantine Fault Tolerance,BFT)
    拜占庭容错是一种强调在节点存在故障或恶意行为时仍然能够保持一致性的共识机制。它适用于分布式系统中存在不确定性和节点间通信存在延迟的情况。拜占庭容错机制确保只要节点中的大多数诚实,就能达成共识。Hyperledger Fabric等区块链项目采用了BFT共识机制。
  3. 权益授权(Proof of Authority,PoA)
    权益授权是一种基于授权节点的共识机制,网络中的节点由已授权的实体管理。授权实体通常是已知的组织或个人,他们负责验证交易并生成新区块。PoA具有高度的安全性和性能,但在去中心化方面存在局限,因为网络的控制权被集中在少数授权实体手中。
  4. 流动性共识(Proof of Liquidity,PoL)
    流动性共识是一种将流动性提供者纳入共识过程的机制。持有代币的用户可以将代币锁定在智能合约中,以提供流动性支持,同时获得交易手续费和奖励。这种共识机制鼓励流动性提供者参与,增加了网络的交易吞吐量。
  5. 时间证明(Proof of Elapsed Time,PoET)
    时间证明是一种通过随机等待时间来选择区块生成者的共识机制。每个节点在等待一段随机时间后,竞争获得出块的权利。这种机制在Hyperledger Sawtooth等项目中得到了应用。
    这仅仅是一小部分其他常见共识机制的简要介绍。随着区块链技术的发展,还可能会出现新的共识算法,以满足不同场景下的需求。选择适合的共识机制需要根据项目的目标、安全性要求、性能需求等多方面因素进行权衡。

第二部分:PoW共识机制实践

共识机制的主要目标是确保在分布式网络中的节点之间就特定的事务或状态达成一致。在这一特性的基础上,工作量证明(Proof of Work,PoW)机制可以被看作是一种简单而直接的方式来体现这一理念。其核心思想是定义一个共同的目标,然后根据不同机器的计算能力来先算出这个目标的结果。计算成功后,算出结果的机器将通知其他节点,其他节点在收到通知后会验证这个结果,一旦验证通过,当前区块的计算即被终止,这个结果会被广泛接受并被用于产生新的区块。

1. 区块表结构

我们会使用新组件SQLite来构建我们的区块链表,使用SQLite的原因如下:

  • 轻量级:SQLite 是一个嵌入式数据库,资源占用少,非常适合嵌入到区块链应用中,特别是在资源有限的设备上。

  • 无服务器架构:SQLite 不需要单独的服务器进程,所有数据存储在一个文件中,简化了部署和管理,提高了可用性。

  • 快速读写:对于小型数据集,SQLite 的读写性能非常高,能够满足快速交易处理的需求。

  • 跨平台支持:SQLite 可以在多种操作系统上运行,便于在不同环境中进行开发和部署。

  • 简单易用:SQLite 的API简单,易于集成,开发者可以快速上手并实现功能

  • SQLite的应用:在 Android 开发中,SQLite 是官方推荐的本地数据存储解决方案。浏览器如 Chrome 和 Firefox 就使用 SQLite 存储浏览历史、书签等数据。

  1. 首先是block表,block表拥有以下字段

image-20241018210142829

下面是建表语句

1
2
3
4
5
6
7
8
9
10
11
12
13
public static final String getBlockSql(String index) {
String sql = "CREATE TABLE IF NOT EXISTS [block" + index + "] (" +
"[ID] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
"[blockIndex] TEXT NOT NULL," +
"[preBlockHash] VARCHAR(300) NOT NULL," +
"[path] VARCHAR(300) UNIQUE NOT NULL," +
"[createTime] VARCHAR(50) NOT NULL," +
"[blockHash] VARCHAR(300) UNIQUE NOT NULL," +
"[randomNumber] TEXT NOT NULL," +
"[onMingChain] INTEGER DEFAULT '0' NOT NULL" +
")";
return sql;
}

blockIndex:区块号

preBlockHash:上一块区块的哈希

path:区块对应的区块文件

createTime:区块创建时间

blockHash:本区块哈希

randomNumber:随机幸运数

onMingChain:该区块是否在主链上

block表主要是用来存储区块对应的信息

image-20241018210209169

  1. dictionary表

dictionary表主要存储区块的当前配置

image-20241018210230700

建表语句

1
2
3
4
5
6
7
8
public static final String getDicSql() {
String sql = "CREATE TABLE IF NOT EXISTS [dictionary] (" +
"[module] VARCHAR(20) NOT NULL," +
"[key] VARCHAR(20) NOT NULL," +
"[value] VARCHAR(20) NOT NULL" +
")";
return sql;
}

数据展示

image-20241018210248992

module:什么模块

key:键名

value:键名对应的值

currentBlockIndex:节点当前更新到的区块

blockIndex:网络中当前最大区块

difficulty:挖掘难度

  1. pending表

用来存储等待打包的交易

image-20241018210307347

建表语句

1
2
3
4
5
6
7
8
9
public static final String getPendingSql() {
String sql = "CREATE TABLE IF NOT EXISTS [pending] (" +
"[orderNo] VARCHAR(200) UNIQUE NOT NULL," +
"[tradeBody] TEXT NOT NULL," +
"[tradeType] VARCHAR(1) NULL," +
"[createTime] VARCHAR(50) NOT NULL" +
")";
return sql;
}

orderNo:交易号

tradeBody:交易体,包含了交易数据等等

tradeType:交易类型,普通交易或者合约交易

createTime:交易的创建时间

2. 代码实现

引导节点仓库地址:https://gitee.com/daitoulin/p2p_bootstrap.git

对等节点仓库地址:https://gitee.com/daitoulin/block_chain.git

2.1. 难题设置以及工作量竞赛

首先来看下我们区块的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package com.example.blockchain.entity.block;

import lombok.Getter;
import org.springframework.stereotype.Component;

@Component
public class Block
{


public String blockIndex;
public String blockHash;
public String preBlockHash;
public String workLoad;
public String createTime;
public String path;
public String dataJson;
public String tradeIds;
public int id;

public String workString() {
return "Block{" +
"blockIndex='" + blockIndex + '\'' +
", blockPreHash='" + preBlockHash + '\'' +
", workLoad='" + workLoad + '\'' +
", dataJson='" + dataJson + '\'' +
", createTime='" + createTime + '\'' +
", randomNumber='" + randomNumber + '\'' +
", tradeIds='" + tradeIds + '\'' +
'}';
}

@Override
public String toString() {
return "Block{" +
"blockIndex='" + blockIndex + '\'' +
", blockHash='" + blockHash + '\'' +
", blockPreHash='" + preBlockHash + '\'' +
", workLoad='" + workLoad + '\'' +
", dataJson='" + dataJson + '\'' +
", createTime='" + createTime + '\'' +
", path='" + path + '\'' +
", tradeIds='" + tradeIds + '\'' +
", randomNumber='" + randomNumber + '\'' +
'}';
}


public String getBlockIndex() {
return blockIndex;
}

public void setBlockIndex(String blockIndex) {
this.blockIndex = blockIndex;
}

public String getBlockHash() {
return blockHash;
}

public void setBlockHash(String blockHash) {
this.blockHash = blockHash;
}

public String getPreBlockHash() {
return preBlockHash;
}

public void setPreBlockHash(String preBlockHash) {
this.preBlockHash = preBlockHash;
}

public String getWorkLoad() {
return workLoad;
}

public String randomNumber;


public String getPath() {
return path;
}

public void setPath(String path) {
this.path = path;
}

public void setWorkLoad(String workLoad) {
this.workLoad = workLoad;
}


public String getCreateTime() {
return createTime;
}

public void setCreateTime(String createTime) {
this.createTime = createTime;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getRandomNumber() {
return randomNumber;
}

public void setRandomNumber(String randomNumber) {
this.randomNumber = randomNumber;
}

public String getDataJson() {
return dataJson;
}

public void setDataJson(String dataJson) {
this.dataJson = dataJson;
}

public String getTradeIds() {
return tradeIds;
}

public void setTradeIds(String tradeIds) {
this.tradeIds = tradeIds;
}
}

一些常用的其他实体:

  • TradeBodyPool 交易池对象
  • BlockDownLoad 区块下载对象

首先定义了两个数字变量,用来判断其他节点给我们返回的结果。然后从定时任务中取出当前的区块号,对区块号进行校验,如果是第一块区块则需要进行特殊处理,因为第一块区块是没有前一块区块的哈希值的。对Block对象进行赋值,需要取出交易池中的pending对象,将交易的哈希值取出进行接下来的计算工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
System.out.println("开始运算---------------------");
int trueCount = 0;
int falseCount = 0;
String preBlockIndex = String.valueOf(UpdateTimer.currentMaxBlockIndex);
BlockServiceImpl.checkBlockTable(preBlockIndex);
List<Block> blocks = BlockServiceImpl.queryBlockByBlockIndex(preBlockIndex);
Block currentBlock = null;
if (blocks.size() > 0) {
currentBlock = blocks.get(0);
}
if (currentBlock == null) {
currentBlock = new Block();
currentBlock.setBlockHash("First block hash");
}
Dictionary diffWorkload = InitUtils.intiDifficulty();// 字典表的工作量配置
String maxBlockIndex = currentBlock.getBlockIndex();
String nextBlockIndex = getNextBlockIndex(maxBlockIndex);
Block block = new Block();
Random r = new Random();
String rand = String.valueOf(r.nextInt(1000000));
block.setBlockIndex(nextBlockIndex);
block.setCreateTime(time);
block.setWorkLoad(diffWorkload.getValue());
block.setCreateTime(DateUtils.getTime());
block.setPreBlockHash(currentBlock.getBlockHash());
block.setRandomNumber(rand);
Date runDate = new Date();
String blockPath = DataUtils.getBlockPath(nextBlockIndex, runDate);// gen block file path
block.setPath(blockPath);

//取出交易
List<String> tradeNos = new ArrayList<>();
List<Pending> list = PendingServiceImpl.queryPendings();
if (list.size() != 0) {
for (int i = 0; i < list.size(); i++) {
tradeNos.add(list.get(i).getOrderNo());
}
}
block.setDataJson(list.toString());

定义一个验证规则,作为工作量证明的目标。例如,以哈希运算结果的前缀为4个连续的零为例,将区块体对象的 workString 方法的输出作为输入进行哈希运算,同时使用 workLoad 作为一个随机变量来不断改变运算的结果。

在这个过程中,矿工不断尝试不同的随机值(workLoad),将区块体对象的字符串表示与该随机值进行组合,然后进行哈希运算。他们的目标是找到一个特定的随机值,使得通过哈希运算得到的结果满足一定的条件,例如在结果的前面有4个连续的零。这个过程需要不断尝试不同的随机值,直到找到一个符合条件的结果为止。

这个过程模拟了工作量证明机制,其中矿工需要投入大量的计算资源来不断尝试寻找符合条件的随机值,以获得权利创建新区块。这是一种保障网络安全的机制,因为其他节点需要验证这个随机值是否满足条件,从而确认该区块的有效性。

简单来说就是其他的值都是固定的,只有这个随机数是变动的,所以就只能穷举一直算。

1
2
3
4
5
String outHash = EncryptUtil.encryptSHA256(block.workString());
System.out.println(outHash);
if (outHash.startsWith(diffWorkload.getValue())) {
System.out.println("挖到---------------------");
}

2.2. 计算结果广播以及工作量验证

计算出答案的人会将结果进行广播,在网络中的人都会接收到区块信息并对接收到的信息进行验证,验证通过就返回true,失败则返回false,当正确的结果超过半数以上时,我们就认为这个节点是最先计算出正确答案的节点,拥有记账权,记录这个新的区块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
block.setBlockHash(outHash);
for (String port : map.getFs().keySet()) {
Friends f = map.getFs().get(port);
String ip = f.getIp();
try {
String resp = HttpHelper.checkBlock(ip, block);
JSONObject response = new Gson().fromJson(resp, JSONObject.class);
if (response.getCode().equals("1")) {
Boolean isTrue = (Boolean) response.getO();
if (isTrue) {
trueCount = trueCount + 1;
} else {
falseCount = falseCount + 1;
}
}
} catch (Exception e) {
System.out.println(ip + "失败");
map.getFs().get(port).setFriendliness(0);
}
}

int count = trueCount + falseCount;
boolean isTrueCountMajority = false;
if (count > 0) {
isTrueCountMajority = trueCount > (count / 2);
}

if (isTrueCountMajority) {
//正确结果超过半数以上

//将新区块记录到区块链中
}

来看下其他节点是怎样验证计算结果的,

  • 收到广播的消息后,我们也会对传输的数据进行sha256哈希计算,这一步可以验证交易是否有被篡改过。
  • 然后用传输过来的区块号去本地数据库查询,查看是否已经存在该区块,
  • 查看传输过来的区块是否是当前正在计算的区块
  • 查看区块号是否符合给定的计算条件(例如以0000开头的区块号)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RequestMapping(value = "/mining/checkBlock", method = {RequestMethod.POST})
public ResponseEntity<JSONObject> checkBlock(@RequestBody Block b) {
JSONObject jo = new JSONObject();

Dictionary diffWorkload = InitUtils.intiDifficulty();// 字典表的工作量配置
String blockHash = EncryptUtil.encryptSHA256(b.workString());
List<Block> bs = BlockServiceImpl.queryBlockByBlockIndex(b.getBlockIndex());

if (bs.size() != 0 || Integer.valueOf(b.getBlockIndex()) - 1 < UpdateTimer.currentMaxBlockIndex.intValue() || !blockHash.equals(b.getBlockHash()) || !blockHash.startsWith(diffWorkload.getValue())) {
jo.setO(false);
} else {
mining.isWork = false;
jo.setO(true);
}

jo.setCode("1");
return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);

}

2.3. 定时任务每秒向其他节点请求最新区块

我们会通过已经保存的对等节点的ip进行最新区块的获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public void updateBlock(String blockIndex, Mining mining, MapFriends map) throws Exception {
BlockDownLoad bdl = null;
for(String port:map.getFs().keySet()){
Friends f= map.getFs().get(port);
String ip=f.getIp();
if (f.getFriendliness() == 0){
continue;
}
NoticeParams np = new NoticeParams(blockIndex.toString(), ip,"");
bdl = HttpHelper.downLoadBlock(ip, 8888, np);//获取区块和区块内容
if(bdl == null) {
continue;
}

//检测当前区块是否已经存在
TradeBodyPool tbp = BlockBaseUtils.genTbp(bdl);
List<Block> bs=BlockServiceImpl.queryBlockByBlockIndex(bdl.getBlock().getBlockIndex());
if(bs.size() > 0 ){
deletePending(tbp);//删除pending
return;
}
BlockServiceImpl.checkBlockTable(bdl.getBlock().getBlockIndex());//检查表是否存在

BlockServiceImpl.save(bdl.getBlock());//保存区块DB
BlockServiceImpl.saveBlockFile(bdl);//保存区块文件
DicServiceImpl.updateDicBlockIndex(blockIndex);//更新当前更新到的块号
DicServiceImpl.updateDicMainBockIndex(bdl.getMaxBlockIndex());//更新当前更新到的块号
UpdateTimer.currentBlockIndex= new BigInteger(blockIndex) ;
UpdateTimer.currentMaxBlockIndex= new BigInteger(bdl.getMaxBlockIndex()) ;
deletePending(tbp);//删除pending

break;
}
if(bdl==null){
mining.updateComplete=true;
mining.isWork=true;
//已经更新到最高区块
throw new Exception();
}
}

下载区块的具体方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public static BlockDownLoad downLoadBlock(String ip, int port, NoticeParams np) {
BlockDownLoad bdl = new BlockDownLoad();
String url = "";
if (StringUtils.isNotBlank(ip)) {
url = "http://" + ip + ":" + port + "/mining/server/block.zip";
} else {
url = "http://" + np.getIp() + ":" + "8001" + "/mining/server/block.zip";
}
// create Httpclient object
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
ZipInputStream zis = null;
HttpPost httpPost = null;
try {
System.out.println("Download Block ");
httpPost = new HttpPost(url);
httpPost.setHeader("Content-type", "application/json; charset=utf-8");
httpPost.setHeader("Connection", "Close");
httpPost.addHeader("Accept-Encoding", "GZIP");
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(5000).setConnectionRequestTimeout(15000).setSocketTimeout(15000).build();
httpPost.setConfig(requestConfig);
StringEntity entity = new StringEntity(np.toJSONString(), Charset.forName("UTF-8"));
entity.setContentEncoding("UTF-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
response = httpClient.execute(httpPost);
//get zip
zis = new ZipInputStream(response.getEntity().getContent(), Charset.forName("UTF-8"));
ZipEntry ze = null;
Block block = null;
String blockFileStr = "";
String maxIndex = "";
while ((ze = zis.getNextEntry()) != null) {
if ("blockObject".equals(ze.getName())) {
//区块对象
block = BlockServiceImpl.getBlockObeject(zis);
} else if ("maxblockindex".equals(ze.getName())) {
//获得最大块编号
maxIndex = BlockServiceImpl.getMaxBlockIndexStr(zis);
} else if ("tokblockfile".equals(ze.getName())) {
//获得区块文件
blockFileStr = BlockServiceImpl.getBlockFileStr(zis);
System.out.println(blockFileStr);
}
}
if (block == null || StringUtils.isBlank(blockFileStr)) {

//throw new Exception("down block is not complete.");
System.out.println("当前block为空,无需下载");
}
bdl.setBlock(block);
bdl.setBlockFileStr(blockFileStr);
bdl.setMaxBlockIndex(maxIndex);
return bdl;
} catch (Exception e) {
//e.printStackTrace();
System.out.println("连接不到对等节点:" + ip);

} finally {
try {
if (httpPost != null) {
httpPost.releaseConnection();
}
if (httpClient != null) {
httpClient.close();
}
if (response != null)
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}

读取流代码

1
2
3
4
5
6
bos = new ByteArrayOutputStream(); // 创建一个字节数组输出流
byte[] buffer = new byte[512]; // 创建一个大小为 512 字节的缓冲区
int len = 0; // 初始化读取长度为 0
while ((len = zis.read(buffer)) != -1) { // 当还有数据可读时继续循环
bos.write(buffer, 0, len); // 将已读取的数据写入到字节数组输出流中
}

下载区块的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@PostMapping(value = "/mining/server/block.zip")
@ResponseBody
public void blockdownload(HttpServletResponse response, @RequestBody NoticeParams noticeParams) {
OutputStream responseBody = null;
ZipOutputStream zos = null;
try {
List<Block> bs = BlockServiceImpl.queryBlockByBlockIndex(noticeParams.getBn());
Block b = null;
//
if (bs != null && bs.size() > 0) {
b = bs.get(0);
if (b != null) {
zos = new ZipOutputStream(response.getOutputStream());
//写入块对象
zos.putNextEntry(new ZipEntry("blockObject"));
String boStr = new Gson().toJson(b);
byte[] b_str = boStr.getBytes();
zos.write(b_str, 0, b_str.length);
//写入块文件
zos.putNextEntry(new ZipEntry("tokblockfile"));
String blockString = DataUtils.getBlockString(DataUtils.getRelativePath(b.getPath()));
byte[] bs_str = blockString.getBytes();
zos.write(bs_str, 0, bs_str.length);
//写入主链上最高的编号
zos.putNextEntry(new ZipEntry("maxblockindex"));
Dictionary dic = DicServiceImpl.queryDic(Dictionary.MODUAL_BLOCK, Dictionary.CURRENTBLOCKINDEX);
byte[] m_str = dic.getValue().getBytes();
zos.write(m_str, 0, m_str.length);

zos.closeEntry();
zos.close();
}
}
} catch (Exception e) {
e.getMessage();
} finally {
if (responseBody != null) {
try {
responseBody.close();
} catch (IOException e) {
e.getMessage();
}
}
if (zos != null) {
try {
zos.close();
} catch (IOException e) {
e.getMessage();
}
}
}
}

2.4. 数据库的保存以及修改

SQLite操作

(1)保存数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static boolean save(Block block) {
Connection connection = null;
try {
String tableIndex = DataUtils.getBlockSerial(block.getBlockIndex());
connection = SQLiteHelper.getConnection();
connection.setAutoCommit(false);
String sql = "insert into [block" + tableIndex + "]([blockIndex],[preBlockHash],[path],[createTime],[blockHash],[randomNumber],[onMingChain]) values(?,?,?,?,?,?,?);";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, block.getBlockIndex() + "");
statement.setString(2, block.getPreBlockHash());
statement.setString(3, block.getPath());
statement.setString(4, block.getCreateTime());
statement.setString(5, block.getBlockHash());
statement.setString(6, block.getRandomNumber());
statement.setInt(7, 1);
statement.execute();
connection.commit();
return true;
} catch (Exception e) {
if (connection != null) {
try {
connection.rollback();
} catch (SQLException e1) {
e.getMessage();
}
}
e.getMessage();
} finally {
SQLiteHelper.close(connection);
}
return false;
}

(2)修改数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static boolean update(Dictionary dic) {
Connection connection = null;
try {
connection = SQLiteHelper.getConnection();
connection.setAutoCommit(false);
String sql = "update [dictionary] set [value] = ? where [module]=? and [key]=?";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, dic.getValue());
statement.setString(2, dic.getModule());
statement.setString(3, dic.getKey());
statement.execute();
connection.commit();
return true;
} catch (Exception e) {
if(connection != null) {
try {
connection.rollback();
} catch (SQLException e1) {
e.getMessage();

}
}
e.getMessage();

}finally {
SQLiteHelper.close(connection);
}
return false;
}

(3)删除数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static boolean deletePendings(List<String> tradeNos) {
if(tradeNos == null || tradeNos.size() == 0) {
return true;
}
Connection connection = null;
try {
connection = SQLiteHelper.getConnection();
connection.setAutoCommit(false);
PreparedStatement statement = connection.prepareStatement("delete from [pending] where [orderNo]=?");
for(int i = 0; i< tradeNos.size(); i++) {
System.out.println(tradeNos.get(i));
statement.setString(1, tradeNos.get(i));
statement.addBatch();
if(i % 100 ==0) {
statement.executeBatch();
statement.clearBatch();
}
}
statement.executeBatch();
statement.clearBatch();
connection.commit();
return true;
} catch (Exception e) {
try {
if(connection != null) {
connection.rollback();
}
} catch (SQLException e1) {
e.getMessage();

}
e.printStackTrace();
}finally {
SQLiteHelper.close(connection);
}
return false;
}

2.5. 发送交易

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@RequestMapping(value = "/data/trade", method = {RequestMethod.POST})
public ResponseEntity<JSONObject> trade(@RequestBody TradeObject tradeObject) {
JSONObject jo = new JSONObject();
List<Pending> pes = PendingServiceImpl.queryPendings();


String no = PendingServiceImpl.genTradeNo(tradeObject);
tradeObject.setHashNo(no);


String genTradeNo = PendingServiceImpl.genTradeNo(tradeObject);
for (Pending p : pes) {
if (p.getOrderNo().equals(genTradeNo)) {
jo.setCode("-1");
jo.setMsg("交易已存在");
return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
}
}

//验证钱包地址


String body = new Gson().toJson(tradeObject);
try {
PendingServiceImpl.validateTradeNo(tradeObject);
Pending pending = new Pending();
pending.setTradeBody(body);
pending.setCreateTime(tradeObject.getJsoncreatetime());
pending.setOrderNo(tradeObject.getHashNo());
pending.setTradeType("1");
PendingServiceImpl.save(pending);
for (String port : map.getFs().keySet()) {
Friends f = map.getFs().get(port);
String ip = f.getIp();
String url = "http://" + ip + ":8001/data/trade";
restTemplate.postForEntity(url, tradeObject, TradeObject.class);
}
jo.setCode("1");
jo.setMsg("成功");

} catch (Exception e) {
System.out.println(e.getMessage());
jo.setCode("-1");
jo.setMsg("失败");
}
return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);

}