一、实验目的
- 学习PoW,PoS共识机制
- PoW共识机制的实践
- postman启用接口、navicat连接SQLite数据库
二、实验环境
- Windows IDEA
- 引导节点:p2p_bootstrap.git
- 对等节点:block_chain.git
三、实验步骤
1.本机启动了一个引导节点、一个对等节点,组员启动了一个对等节点
启动引导节点:
1 | server.port=8886 |
启动对等节点:
1 | server.port=8001 |
组员启动对等节点:
1 | server.port=8001 |
2.对等节点观察到信息如下:
3.使用postman进入接口,开始挖矿:
4.观察到文件目录下多出来两个文件夹db
&ws
,其中db即SQLite数据库文件,ws即挖到的区块文件。由于对等节点一旦形成之间互相连接,文件共享,挖到的区块数目会一致。
5.使用IDEA调试并明确断点,可以实时观察到各参数信息:
6.使用navicat连接SQLite数据库,BlockChain.db测试连接,连接成功之后确认,可以查看到SQLite的三张表:block0,dictionary,pending
7.查看结果如下:
8.可以对比两个对等节点之间的区块数据会发现确实一致。
四、实验结果与讨论
引导节点、两个对等节点启动:
使用postman进入接口,开始挖矿,成功出现ws区块文件夹
使用navicat连接SQLite数据库:BlockChain.db
五、总结
PoW的核心原理是基于哈希函数的不可预测性和随机性。在PoW中,矿工需要寻找一个特定的哈希值,使得区块头的哈希满足一定的条件,通常要求哈希值以一定数量的前导零开头。这个条件在数学上是难以预测的,唯一的方法是通过不断尝试不同的随机数来进行哈希计算,直到找到满足条件的哈希值,这需要大量的计算能力。步骤:①选择交易和构建区块头;②挑战难题;③工作量竞赛;④寻找满足条件的哈希值。
定义一个共同的目标,然后根据不同机器的计算能力来先算出这个目标的结果,计算成功后通知其他节点,来进行验证,一旦验证通过,当前区块的计算即被种植,这个结果会被广泛接受并被用于产生新的区块。
Pos权益证明机制,通过参与者持有的代币数量来决定其创建新区快的权利。步骤:①抵押代币:抵押到网络中,这些代币被锁定,与被选中的概率更高。②选择出块节点:即有权利创建新区块的节点。③负责验证交易和创建新区快。④奖励和惩罚:(抵押的代币)。优势:①能源效率大幅降低;②去中心化,代币持有者积极参与共识机制,避免了算例集中问题;③安全性,攻击的成本和难度,攻击者需要破坏自身的利益来攻击;④能够扩展性,无需进行负责计算,只要进行验证和区块创建,可以更轻松的处理更多的交易和数据;⑤减少硬件竞争,节约了资源。局限性:①权力集中,更多代币的参与者有更大的权力;②节点激励问题,可能缺乏积极性,影响网络的安全性和稳定性;③链长问题,更倾向于选择更多代笔的链,更大话语权;④算法设计复杂性,要考虑权益抵押、随机性,容易不稳定。
一些其他的共识机制:
- 权威共识DPoS(代币持有者通过选举一组代表来确认交易和生成区块)
- 拜占庭容错BFT(强调在节点存在故障或恶意行为时仍然能够保持一致性的共识机制,确保只要节点中的大多数诚实,就能达成共识)
- 权益授权PoA(基于授权节点)
- 流动性共识PoL(将流动性提供者纳入共识过程的机制,将代币锁定在智能合约中,以提供流动性支持,同时获得交易手续费和奖励)
- 实践证明PoET(通过随机等待时间来选择区块生产者的共识机制)
SQLite数据库:
- block表:存储区块对应的信息
- dictionary表:存储区块的当前配置
- pending表:存储等待打包的交易
附上代码
难题设置以及工作量竞赛
①block实体
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
106package com.example.blockchain.entity.block;
import lombok.Getter;
import org.springframework.stereotype.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 + '\'' +
", path='" + path + '\'' +
", randomNumber='" + randomNumber + '\'' +
", tradeIds='" + tradeIds + '\'' +
'}';
}
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;
}
}②开始运算
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
39System.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());③挖到区块
1
2
3
4
5String outHash = EncryptUtil.encryptSHA256(block.workString());
System.out.println(outHash);
if (outHash.startsWith(diffWorkload.getValue())) {
System.out.println("挖到---------------------");
}计算结果广播以及工作量验证
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
32block.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) {
//正确结果超过半数以上
//将新区块记录到区块链中
}②其他节点验证计算结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public ResponseEntity<JSONObject> checkBlock( { 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);
}定时任务每秒向其他节点请求最新区块
①通过已经保存的对等节点的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
40public 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
74public 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
6bos = 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
public void blockdownload(HttpServletResponse response, { 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();
}
}
}
}数据库的保存以及修改
①保存数据save
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
32public 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;
}②修改数据update
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
29public 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;
}③删除数据deletePending
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
37public 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;
}发送交易
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
public ResponseEntity<JSONObject> trade( { 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);
}