前言

区块链当中,所有的交易都包含在区块中,如何验证每一次接收到的区块的合法性就至关重要。
区块链是分布式的、对外公开的,所有人都可以参与,有些恶意节点通过修改代码的形式加入到区块链中,把一些对自己有利的交易、区块广播到节点当中,使这些交易对自己有利。
所以判断合法交易、区块就尤为重要。这些操作都是交易区块链当中的各个节点进行验证。

区块

区块是用来包含tron网中的交易的数据结构,是由27个超级节点(SR),每隔3秒,依次轮流将交易打包后的数据结构。

处理区块

处理入口

区块处理主要有两个入口,也对应两种场景:

  1. 同步处理区块
  2. 追块处理区块

同步处理区块,是节点之前本节点和网络中的节点区块之前高度没有差异,同步接收最新的区块。
追块处理区块,本节点区块高度落后网络中的节点太远,需要追到最新高度后,转换为同步处理区块

同步处理区块

入口类:TronNetHandler
TronNetHandler 是一个 Netty 的 Handler,接收其他节点的消息,基本和其它节点交互的入口类都是这个类。
整个流程调用栈:

1
2
3
4
5
TronNetHandler.channelRead0
|---tronNetService.onMessage(peer, msg);
|---BlockMsgHandler.processMessage();
|---tronNetDelegate.processBlock(block, false);
|---dbManager.pushBlock(block);

网络同步入口

TronNetHandler 是网络入口,是一个Netty的Handler。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
@Scope("prototype")
public class TronNetHandler extends SimpleChannelInboundHandler<TronMessage> {

protected PeerConnection peer;

private MessageQueue msgQueue;

@Autowired
private TronNetService tronNetService;

@Override
public void channelRead0(final ChannelHandlerContext ctx, TronMessage msg) throws Exception {
msgQueue.receivedMessage(msg);
// 消息处理逻辑
tronNetService.onMessage(peer, msg);
}
}

TronNetService.onMessage 处理入口,包括几种不同的处理消息类型:

  1. SYNC_BLOCK_CHAIN 请求同步区块链消息
  2. BLOCK_CHAIN_INVENTORY 区块链区块清单消息
  3. INVENTORY 请求获取数据消息
  4. FETCH_INV_DATA 请求获取数据消息
  5. BLOCK 区块数据消息
  6. TRXS 交易易消息
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
protected void onMessage(PeerConnection peer, TronMessage msg) {
try {
switch (msg.getType()) {
case SYNC_BLOCK_CHAIN:
// 同步区块处理
syncBlockChainMsgHandler.processMessage(peer, msg);
break;
case BLOCK_CHAIN_INVENTORY:
chainInventoryMsgHandler.processMessage(peer, msg);
break;
case INVENTORY:
inventoryMsgHandler.processMessage(peer, msg);
break;
case FETCH_INV_DATA:
fetchInvDataMsgHandler.processMessage(peer, msg);
break;
// 区块同步处理,主要的处理方法
case BLOCK:
blockMsgHandler.processMessage(peer, msg);
break;
case TRXS:
// 接收交易处理
transactionsMsgHandler.processMessage(peer, msg);
break;
case PBFT_COMMIT_MSG:
pbftDataSyncHandler.processMessage(peer, msg);
break;
default:
throw new P2pException(TypeEnum.NO_SUCH_MESSAGE, msg.getType().toString());
}
} catch (Exception e) {
processException(peer, msg, e);
}
}

同步接收 BlockMsgHandler.processMessage

处理实时接收到的区块。主要是做几步:

  1. 区块校验
  2. 接收 或 同步区块判断
  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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

@Override
public void processMessage(PeerConnection peer, TronMessage msg) throws P2pException {

BlockMessage blockMessage = (BlockMessage) msg;
BlockId blockId = blockMessage.getBlockId();

// 非快速转发节点,即普能节点需要走一下 check 方法
if (!fastForward && !peer.isFastForwardPeer()) {
// 做三个校验,也是很重要的三个校验:
// 1. 校验消息是息在接收 或 发送缓存中
// 2. 校验消息是否长过长度
// 3. 校验消息是时间戳是否大于 BLOCK_PRODUCED_INTERVAL,也就是 3000 ms
// 建议自行看下
check(peer, blockMessage);
}

// 如是在同步请求缓存中,说明这个块是同步的块
// 如果进这个逻辑,那就是另一种同步了
// SyncBlockRequested 中的 blockId 哪来的?是在启动过程中预先判断自己的区块高度与邻近节点的差异
// 是一块比较复杂的逻辑,后面单独讲启动同步这块逻辑,所以如果是正常实时同步是走 else
if (peer.getSyncBlockRequested().containsKey(blockId)) {
peer.getSyncBlockRequested().remove(blockId);
syncService.processBlock(peer, blockMessage);
} else {
Long time = peer.getAdvInvRequest().remove(new Item(blockId, InventoryType.BLOCK));
long now = System.currentTimeMillis();
if (null != time) {
MetricsUtil.histogramUpdate(MetricsKey.NET_LATENCY_FETCH_BLOCK + peer.getNode().getHost(),
now - time);
}
fetchBlockService.blockFetchSuccess(blockId);
// num 指的是高度也就是编号
// HeadBlock 是指头块,TRON 里的 HeadBlock 和 SolidityBlock 是两个概念,这两个概念跟区块同步有关
// HeadBlock 是指最新接收到的块还未固化,大白话就是还没写库,在内存中,这个块可以被回退
// SolidityBlock 就是字面意思,固化块,大白话就是已经写库了,不会被回滚
// 为什么会有这种区别,这个有点长遍大论了,主要是因为TRON是快照机制而产生的这两种块的区别
long interval = blockId.getNum() - tronNetDelegate.getHeadBlockId().getNum();
// 进入下一阶段逻辑,整个流程下来,代码都非常重要,没有一段感觉是可以跳过去的
// 因为每个步骤环环相扣,但是这种代码风格,实在不像是java的那种啥都抽象一下的方式,
// 更像是一个精致流水线,每一步都写的很好,很精辟,但是不抽像。
// 这个项目的代码看多的时候,就会发现这个特点,所以会有一种环环相扣的感觉
processBlock(peer, blockMessage.getBlockCapsule());
logger.info(
"Receive block/interval {}/{} from {} fetch/delay {}/{}ms, "
+ "txs/process {}/{}ms, witness: {}",
blockId.getNum(),
interval,
peer.getInetAddress(),
time == null ? 0 : now - time,
now - blockMessage.getBlockCapsule().getTimeStamp(),
((BlockMessage) msg).getBlockCapsule().getTransactions().size(),
System.currentTimeMillis() - now,
Hex.toHexString(blockMessage.getBlockCapsule().getWitnessAddress().toByteArray()));
}
}

初步处理 BlockMsgHandler.processBlock

进行初步处理,做一些对区块的校验。
这个方法还会进一步判断是否需要同步,在tronNetDelegate.containBlock(block.getParentBlockId())这个判断内部,会判断区块是否存在,通过khaosDB缓存或数据库中查询这一块是否存在来决定。

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

private void processBlock(PeerConnection peer, BlockCapsule block) throws P2pException {
BlockId blockId = block.getBlockId();
// 判断Block是否存在
if (!tronNetDelegate.containBlock(block.getParentBlockId())) {
logger.warn("Get unlink block {} from {}, head is {}.", blockId.getString(),
peer.getInetAddress(), tronNetDelegate.getHeadBlockId().getString());
// 不存在说明,没有接收到这一块,重新进行同步
syncService.startSync(peer);
return;
}

long headNum = tronNetDelegate.getHeadBlockId().getNum();
// 如果这一块,比之前接收到的高度要低,也没必要继续了,我的块本身就比你的要高
if (block.getNum() < headNum) {
logger.warn("Receive a low block {}, head {}", blockId.getString(), headNum);
return;
}

// validBlock 主要验两个逻辑:
// 1. 验签,这步和互联网项目的验签类似
// 2. 判断是否为witness产的块
boolean flag = tronNetDelegate.validBlock(block);
if (flag) {
// 先广播broadcast,再处理tronNetDelegate.processBlock
broadcast(new BlockMessage(block));
}

try {
// 内部会调用 Manager.pushBlock 核心区块处理方法,走了一圈,终于到核心逻辑
tronNetDelegate.processBlock(block, false);
if (!flag) {
broadcast(new BlockMessage(block));
}

witnessProductBlockService.validWitnessProductTwoBlock(block);

tronNetDelegate.getActivePeer().forEach(p -> {
if (p.getAdvInvReceive().getIfPresent(blockId) != null) {
p.setBlockBothHave(blockId);
}
});
} catch (Exception e) {
logger.warn("Process adv block {} from peer {} failed. reason: {}",
blockId, peer.getInetAddress(), e.getMessage());
}
}

处理区块 Manager.pushBlock

核心处理区块逻辑,包括:

  1. 区块验签
  2. 切链
  3. 执行区块交易
  4. 奖励发放

等一系列操作。

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183

/**
* save a block.
*/
public synchronized void pushBlock(final BlockCapsule block)
throws ValidateSignatureException, ContractValidateException, ContractExeException,
UnLinkedBlockException, ValidateScheduleException, AccountResourceInsufficientException,
TaposException, TooBigTransactionException, TooBigTransactionResultException,
DupTransactionException, TransactionExpirationException,
BadNumberBlockException, BadBlockException, NonCommonBlockException,
ReceiptCheckErrException, VMIllegalException, ZksnarkException, EventBloomException {
long start = System.currentTimeMillis();
List<TransactionCapsule> txs = getVerifyTxs(block);
logger.info("Block num: {}, re-push-size: {}, pending-size: {}, "
+ "block-tx-size: {}, verify-tx-size: {}",
block.getNum(), rePushTransactions.size(), pendingTransactions.size(),
block.getTransactions().size(), txs.size());

// 在指定高度停止,调试时用的功能
if (CommonParameter.getInstance().getShutdownBlockTime() != null
&& CommonParameter.getInstance().getShutdownBlockTime()
.isSatisfiedBy(new Date(block.getTimeStamp()))) {
latestSolidityNumShutDown = block.getNum();
}

// 注意,这里用的是 java7 的try 写法,不仔细看会踩坑,所以PendingManager里自行实现的资源关闭
try (PendingManager pm = new PendingManager(this)) {

if (!block.generatedByMyself) {
// 验证 默克尔根
if (!block.calcMerkleRoot().equals(block.getMerkleRoot())) {
logger.warn(
"The merkle root doesn't match, Calc result is "
+ block.calcMerkleRoot()
+ " , the headers is "
+ block.getMerkleRoot());
throw new BadBlockException("The merkle hash is not validated");
}
// 节点状态相关验证
consensus.receiveBlock(block);
}

// 只允许 1笔匿名交易存在
if (block.getTransactions().stream().filter(tran -> isShieldedTransaction(tran.getInstance()))
.count() > SHIELDED_TRANS_IN_BLOCK_COUNTS) {
throw new BadBlockException(
"shielded transaction count > " + SHIELDED_TRANS_IN_BLOCK_COUNTS);
}

BlockCapsule newBlock;
try {
// 把区存存入 KhaosDatabase,这个DB是一个纯内存的存储
newBlock = this.khaosDb.push(block);
} catch (UnLinkedBlockException e) {
logger.error(
"latestBlockHeaderHash:{}, latestBlockHeaderNumber:{}, latestSolidifiedBlockNum:{}",
getDynamicPropertiesStore().getLatestBlockHeaderHash(),
getDynamicPropertiesStore().getLatestBlockHeaderNumber(),
getDynamicPropertiesStore().getLatestSolidifiedBlockNum());
throw e;
}

// DB don't need lower block
if (getDynamicPropertiesStore().getLatestBlockHeaderHash() == null) {
if (newBlock.getNum() != 0) {
return;
}
} else {
if (newBlock.getNum() <= getDynamicPropertiesStore().getLatestBlockHeaderNumber()) {
return;
}

// switch fork
// 切链
// 什么是切链?
// 大白话就是选择一条正确的链
// 什么是选择一条正确的链?
// 大白话就是,27 个节点产块,它有可能出现两个节点同时产一个高度的区块,必须选出谁产的是正确的那个区块。
// 说人话:
// 假设当前TRON区块边节点最新高度是:10000,现在轮到 SR节点1 产块,但是,
// SR节点1可能由于网络原因,SR节点1生产的区块没有被下一个节点SR1收到,那么SR2认为SR1可能出现故障,就会接着 10000 后面生产自己的区块。
// 那么问题来了,SR节点1 网络恢复后,所有27个SR会同时收到两个 10001(SR1)和 10001(SR2)所产的两个高度相关的区块。
// 那到底谁才是正确的?
// 解决方案:谁的链更长,谁就是正确的。也就是10001(SR1)和 10001(SR2)这两个块,后面跟的块谁的长,谁就是正确的。
// SR1 : 10000-->10001-->10002
// SR2 : 10000-->10001-->10002-->10003-->10004-->10005 很明显这个是正确的。
if (!newBlock
.getParentHash()
.equals(getDynamicPropertiesStore().getLatestBlockHeaderHash())) {
logger.warn(
"switch fork! new head num = {}, block id = {}",
newBlock.getNum(),
newBlock.getBlockId());

logger.warn(
"******** before switchFork ******* push block: "
+ block.toString()
+ ", new block:"
+ newBlock.toString()
+ ", dynamic head num: "
+ chainBaseManager.getDynamicPropertiesStore().getLatestBlockHeaderNumber()
+ ", dynamic head hash: "
+ chainBaseManager.getDynamicPropertiesStore().getLatestBlockHeaderHash()
+ ", dynamic head timestamp: "
+ chainBaseManager.getDynamicPropertiesStore().getLatestBlockHeaderTimestamp()
+ ", khaosDb head: "
+ khaosDb.getHead()
+ ", khaosDb miniStore size: "
+ khaosDb.getMiniStore().size()
+ ", khaosDb unlinkMiniStore size: "
+ khaosDb.getMiniUnlinkedStore().size());

// 切链,还是一个比较核心主逻辑,因为在区块链中相同高度区块问题非常常见,包括比特币,比特币基于POW协议,同一时刻产块的相同
// 块高的区块更是多的多,所以理解这一块,对理解链的原理很有帮助
switchFork(newBlock);
logger.info(SAVE_BLOCK + newBlock);

logger.warn(
"******** after switchFork ******* push block: "
+ block.toString()
+ ", new block:"
+ newBlock.toString()
+ ", dynamic head num: "
+ chainBaseManager.getDynamicPropertiesStore().getLatestBlockHeaderNumber()
+ ", dynamic head hash: "
+ chainBaseManager.getDynamicPropertiesStore().getLatestBlockHeaderHash()
+ ", dynamic head timestamp: "
+ chainBaseManager.getDynamicPropertiesStore().getLatestBlockHeaderTimestamp()
+ ", khaosDb head: "
+ khaosDb.getHead()
+ ", khaosDb miniStore size: "
+ khaosDb.getMiniStore().size()
+ ", khaosDb unlinkMiniStore size: "
+ khaosDb.getMiniUnlinkedStore().size());

return;
}
// 构建一个可回退的 session
// 处理区块,如果失败,可以被这一区块处理过的数据回滚到上一个区块的状态
// 每一个块中的交易处理和泛汲到的数据变更,实际上是存在内存当中的,也就是存在 session 中
// 直到经过18个块的间隔后,状态才会被写入到磁盘中
try (ISession tmpSession = revokingStore.buildSession()) {

long oldSolidNum =
chainBaseManager.getDynamicPropertiesStore().getLatestSolidifiedBlockNum();
// 处理区块
applyBlock(newBlock, txs);
tmpSession.commit();
// if event subscribe is enabled, post block trigger to queue
postBlockTrigger(newBlock);
// if event subscribe is enabled, post solidity trigger to queue
postSolidityTrigger(oldSolidNum,
getDynamicPropertiesStore().getLatestSolidifiedBlockNum());
} catch (Throwable throwable) {
logger.error(throwable.getMessage(), throwable);
khaosDb.removeBlk(block.getBlockId());
throw throwable;
}
}
logger.info(SAVE_BLOCK + newBlock);
}
//clear ownerAddressSet
if (CollectionUtils.isNotEmpty(ownerAddressSet)) {
Set<String> result = new HashSet<>();
for (TransactionCapsule transactionCapsule : rePushTransactions) {
filterOwnerAddress(transactionCapsule, result);
}
for (TransactionCapsule transactionCapsule : pushTransactionQueue) {
filterOwnerAddress(transactionCapsule, result);
}
ownerAddressSet.clear();
ownerAddressSet.addAll(result);
}

MetricsUtil.meterMark(MetricsKey.BLOCKCHAIN_BLOCK_PROCESS_TIME,
System.currentTimeMillis() - start);

logger.info("pushBlock block number:{}, cost/txs:{}/{}",
block.getNum(),
System.currentTimeMillis() - start,
block.getTransactions().size());
}

处理区块 applyBlock

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
private void applyBlock(BlockCapsule block, List<TransactionCapsule> txs)
throws ContractValidateException, ContractExeException, ValidateSignatureException,
AccountResourceInsufficientException, TransactionExpirationException,
TooBigTransactionException, DupTransactionException, TaposException,
ValidateScheduleException, ReceiptCheckErrException, VMIllegalException,
TooBigTransactionResultException, ZksnarkException, BadBlockException, EventBloomException {
// 执行区块交易
processBlock(block, txs);
// 保存储区块
chainBaseManager.getBlockStore().put(block.getBlockId().getBytes(), block);
// 更新最新区块
chainBaseManager.getBlockIndexStore().put(block.getBlockId());
if (block.getTransactions().size() != 0) {
// 保存交易,根据区块ID,用于接口查询
chainBaseManager.getTransactionRetStore()
.put(ByteArray.fromLong(block.getNum()), block.getResult());
}

// 用于链升级、降级逻辑
updateFork(block);
// 如果当前时间跟区最新的区块之间差了 60 秒的话,区块就需要500块一刷盘,差这么多块,说明是在追块
// 因为此时在同步其它节点的区块,一块一刷盘太消耗性能,500块一刷也合理,如果中间失败了,也可以回退到上一个状态
// SnapshotManager.DEFAULT_MAX_FLUSH_COUNT = 500
if (System.currentTimeMillis() - block.getTimeStamp() >= 60_000) {
revokingStore.setMaxFlushCount(SnapshotManager.DEFAULT_MAX_FLUSH_COUNT);
if (Args.getInstance().getShutdownBlockTime() != null
&& Args.getInstance().getShutdownBlockTime().getNextValidTimeAfter(
new Date(block.getTimeStamp() - SnapshotManager.DEFAULT_MAX_FLUSH_COUNT * 1000 * 3))
.compareTo(new Date(block.getTimeStamp())) <= 0) {
revokingStore.setMaxFlushCount(SnapshotManager.DEFAULT_MIN_FLUSH_COUNT);
}
if (latestSolidityNumShutDown > 0 && latestSolidityNumShutDown - block.getNum()
<= SnapshotManager.DEFAULT_MAX_FLUSH_COUNT) {
revokingStore.setMaxFlushCount(SnapshotManager.DEFAULT_MIN_FLUSH_COUNT);
}
} else {
// 否则就是一块一刷盘,也就是正在同步处理
// SnapshotManager.DEFAULT_MIN_FLUSH_COUNT = 1
revokingStore.setMaxFlushCount(SnapshotManager.DEFAULT_MIN_FLUSH_COUNT);
}
}

执行区块 processBlock

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
private void processBlock(BlockCapsule block, List<TransactionCapsule> txs)
throws ValidateSignatureException, ContractValidateException, ContractExeException,
AccountResourceInsufficientException, TaposException, TooBigTransactionException,
DupTransactionException, TransactionExpirationException, ValidateScheduleException,
ReceiptCheckErrException, VMIllegalException, TooBigTransactionResultException,
ZksnarkException, BadBlockException, EventBloomException {
// todo set revoking db max size.

// checkWitness
if (!consensus.validBlock(block)) {
throw new ValidateScheduleException("validateWitnessSchedule error");
}

chainBaseManager.getBalanceTraceStore().initCurrentBlockBalanceTrace(block);

//reset BlockEnergyUsage
chainBaseManager.getDynamicPropertiesStore().saveBlockEnergyUsage(0);
//parallel check sign
if (!block.generatedByMyself) {
try {
// 对交易并行验签
preValidateTransactionSign(txs);
} catch (InterruptedException e) {
logger.error("parallel check sign interrupted exception! block info: {}", block, e);
Thread.currentThread().interrupt();
}
}

TransactionRetCapsule transactionRetCapsule =
new TransactionRetCapsule(block);
try {
// 重置 默克尔树,其实是set自己为最新的树
merkleContainer.resetCurrentMerkleTree();
accountStateCallBack.preExecute(block);
for (TransactionCapsule transactionCapsule : block.getTransactions()) {
transactionCapsule.setBlockNum(block.getNum());
if (block.generatedByMyself) {
transactionCapsule.setVerified(true);
}
accountStateCallBack.preExeTrans();
// 执行一次交易
TransactionInfo result = processTransaction(transactionCapsule, block);
accountStateCallBack.exeTransFinish();
if (Objects.nonNull(result)) {
transactionRetCapsule.addTransactionInfo(result);
}
}
accountStateCallBack.executePushFinish();
} finally {
accountStateCallBack.exceptionFinish();
}
merkleContainer.saveCurrentMerkleTreeAsBestMerkleTree(block.getNum());
block.setResult(transactionRetCapsule);
// 计算能量、带宽,复杂逻辑,可以单独解读一篇文章
if (getDynamicPropertiesStore().getAllowAdaptiveEnergy() == 1) {
EnergyProcessor energyProcessor = new EnergyProcessor(
chainBaseManager.getDynamicPropertiesStore(), chainBaseManager.getAccountStore());
energyProcessor.updateTotalEnergyAverageUsage();
energyProcessor.updateAdaptiveTotalEnergyLimit();
}

// 将厉发放,用于奖励对witness投票的账户
payReward(block);

if (chainBaseManager.getDynamicPropertiesStore().getNextMaintenanceTime()
<= block.getTimeStamp()) {
proposalController.processProposals();
chainBaseManager.getForkController().reset();
}

if (!consensus.applyBlock(block)) {
throw new BadBlockException("consensus apply block failed");
}

// 更新存储
updateTransHashCache(block);
updateRecentBlock(block);
updateRecentTransaction(block);
updateDynamicProperties(block);

chainBaseManager.getBalanceTraceStore().resetCurrentBlockTrace();

if (CommonParameter.getInstance().isJsonRpcFilterEnabled()) {
Bloom blockBloom = chainBaseManager.getSectionBloomStore()
.initBlockSection(transactionRetCapsule);
chainBaseManager.getSectionBloomStore().write(block.getNum());
block.setBloom(blockBloom);
}
}

到这里大致的处理流程就是这样,当然有很多细可以细扣。

为什么要处理区块?
为了使这个被2/3的SR节点认可。27个SR节点的2/3就是19个节点。

因为一个区块里包含了这个节点打包的一批交易,包含的笔数由当时打包的能力决定,这里有一个问题就是你无法保证这个节点是否会作恶,偷偷修改数据。
因此这些包易当中可能存在不合法的交易,在DPoS共识当中,一个区块必须是由超过 2/3的节点验证合法后,这个区块才会被当成一个合法的区块。也就是需要被2/3的节点认可。
之后这个区块性质就是一个固化块即Solidity块
2/3的节点收到一个区块后,会对这个区块进行验证。

怎么验证?
验证方式就是把接收到的这个区块,重新执行一遍。如果执行成功,结果不会主动上送,而是更新在存储当中,
产块节点会主动调用接口查询这一块的处理状态。

接收到的区块验证失败怎么处理?
失败会抛异常,节点被断开p2p链接,回溯代码可知,如果需要验证,在pushBlock中主动写代码抛异常就可以复现这个场景。

接收到区块,不处理直接存储行不行?
这个很难实现,第一要保证节点中的所有数据状态在接收是交易时就已经100%保证正常,这个应该很难实现,但是像Tendermint就是只在接收时处理一次,区块只验证hash。

验证区块场景

有3种处理场景:

  1. 启动同步区块
  2. 产块后处理区块
  3. 运行接收区块

启动同步区块

如果是一个新的节点接入到Tron链当中,当前数据库当中的数据是空的,一个区块都没有,需要从网络当中进行同步区块。

产块后处理区块

SR节点自己生产的区块,自己也需要处理

运行接收区块

节点运行过程中,接收到的区块。

区块结构

一个区块由两部分组成:

  1. 区块头
  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
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
134
135
136
137
// block
message Block {
// 区块体,只包含交易
repeated Transaction transactions = 1;
// 区块头
BlockHeader block_header = 2;
}

message BlockHeader {
message raw {
int64 timestamp = 1;
bytes txTrieRoot = 2;
bytes parentHash = 3;
//bytes nonce = 5;
//bytes difficulty = 6;
int64 number = 7;
int64 witness_id = 8;
bytes witness_address = 9;
int32 version = 10;
bytes accountStateRoot = 11;
}
raw raw_data = 1;
bytes witness_signature = 2;
}

message Transaction {
message Contract {
// 交易类型
// 不是说只有原生代币TRX、TRC10、TRC20,怎么有这么多交易类型?
// 因为区块链当中,链治理也需要发起交易,比如:对提案投票、质押代币、节点投票都是交易
enum ContractType {
AccountCreateContract = 0;
TransferContract = 1;
TransferAssetContract = 2;
VoteAssetContract = 3;
VoteWitnessContract = 4;
WitnessCreateContract = 5;
AssetIssueContract = 6;
WitnessUpdateContract = 8;
ParticipateAssetIssueContract = 9;
AccountUpdateContract = 10;
FreezeBalanceContract = 11;
UnfreezeBalanceContract = 12;
WithdrawBalanceContract = 13;
UnfreezeAssetContract = 14;
UpdateAssetContract = 15;
ProposalCreateContract = 16;
ProposalApproveContract = 17;
ProposalDeleteContract = 18;
SetAccountIdContract = 19;
CustomContract = 20;
CreateSmartContract = 30;
TriggerSmartContract = 31;
GetContract = 32;
UpdateSettingContract = 33;
ExchangeCreateContract = 41;
ExchangeInjectContract = 42;
ExchangeWithdrawContract = 43;
ExchangeTransactionContract = 44;
UpdateEnergyLimitContract = 45;
AccountPermissionUpdateContract = 46;
ClearABIContract = 48;
UpdateBrokerageContract = 49;
ShieldedTransferContract = 51;
MarketSellAssetContract = 52;
MarketCancelOrderContract = 53;
}
ContractType type = 1;
google.protobuf.Any parameter = 2;
bytes provider = 3;
bytes ContractName = 4;
int32 Permission_id = 5;
}

message Result {
enum code {
SUCESS = 0;
FAILED = 1;
}
enum contractResult {
DEFAULT = 0;
SUCCESS = 1;
REVERT = 2;
BAD_JUMP_DESTINATION = 3;
OUT_OF_MEMORY = 4;
PRECOMPILED_CONTRACT = 5;
STACK_TOO_SMALL = 6;
STACK_TOO_LARGE = 7;
ILLEGAL_OPERATION = 8;
STACK_OVERFLOW = 9;
OUT_OF_ENERGY = 10;
OUT_OF_TIME = 11;
JVM_STACK_OVER_FLOW = 12;
UNKNOWN = 13;
TRANSFER_FAILED = 14;
INVALID_CODE = 15;
}
int64 fee = 1;
code ret = 2;
contractResult contractRet = 3;

string assetIssueID = 14;
int64 withdraw_amount = 15;
int64 unfreeze_amount = 16;
int64 exchange_received_amount = 18;
int64 exchange_inject_another_amount = 19;
int64 exchange_withdraw_another_amount = 20;
int64 exchange_id = 21;
int64 shielded_transaction_fee = 22;


bytes orderId = 25;
repeated MarketOrderDetail orderDetails = 26;
}

message raw {
bytes ref_block_bytes = 1;
int64 ref_block_num = 3;
bytes ref_block_hash = 4;
int64 expiration = 8;
repeated authority auths = 9;
// data not used
bytes data = 10;
//only support size = 1, repeated list here for extension
repeated Contract contract = 11;
// scripts not used
bytes scripts = 12;
int64 timestamp = 14;
int64 fee_limit = 18;
}

raw raw_data = 1;
// only support size = 1, repeated list here for muti-sig extension
repeated bytes signature = 2;
repeated Result ret = 5;
}

运程运行时,长啥样?
就是下面这样,区块头体积很小,只包含:区块高度、上一个区块hash、交易笔数等关键信息。像这样
这个是通过运行时,debug观察区块的结果

1
2
3
4
5
6
7
8
9
10
11
BlockCapsule 
[ hash=000000000229610fd7a9a496f5b5a720cdd8fa188b2c9c44504a918fffca07ef
number=36266255
parentId=000000000229610e871e5ad5ea7779b7f09db5cea82fc330606e497b6bea49b0
witness address=415863f6091b8e71766da808b1dd3159790f61de7d
generated by myself=false
generate time=2021-12-13 01:44:12.0
account root=0000000000000000000000000000000000000000000000000000000000000000
merkle root=dfcaf2b5168ea9601d9c7aabb88a3c144c4784ca580c0d660ebb268b363b2e8f
txs size=80
]

区块体包含交易:

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
transactions {
raw_data {
ref_block_bytes: "`\373"
ref_block_hash: "\033\376ab\257\213z\'"
expiration: 1639331106000
data: "\344\270\223\346\263\250\344\272\216\345\214\272\345\235\227\351\223\276\351\241\271\347\233\256\345\214\205\350\243\205\347\255\226\345\210\222\350\277\220\350\220\245\344\270\200\347\253\231\345\274\217\346\234\215\345\212\241\357\274\214\344\273\243\345\270\201\351\203\250\347\275\262\357\274\214DAPP\345\274\200\345\217\221\357\274\214\346\211\271\351\207\217\350\275\254\350\264\246\357\274\214\346\265\267\345\206\205\345\244\226\350\264\242\347\273\217\345\252\222\344\275\223\345\256\243\345\217\221\357\274\214\345\256\241\350\256\241\346\212\245\345\221\212\357\274\214\350\203\275\351\207\217\347\247\237\350\265\201\357\274\214\346\263\242\345\234\272\345\212\251\346\211\213https://tronhelp.io \345\256\242\346\234\215\357\274\232tronassistant/tronaide"
contract {
type: TransferContract
parameter {
type_url: "type.googleapis.com/protocol.TransferContract"
value: "\n\025A\030\036m\024\'\222\226R\'(4Bk\314U\314\266\360\037\300\022\025A\265pN\363C\343\222#1@g1\035\371\023\034\275\340kC\030\001"
}
}
timestamp: 1639331047596
}
signature: "\177\272\316M\370-\373\033}\020\273\255\006\372u5g \372\311\255\341uN\0209,\377\332!\211\025:\244\303}o\271\017-\364s`\031\262r\n\002E\272\225\253^G\325`\260@Gpl\017\366\\\000"
ret {
contractRet: SUCCESS
}
}
transactions {
raw_data {
ref_block_bytes: "a\r"
ref_block_hash: "\257\r\311x\024I\321\356"
expiration: 1639331106000
contract {
type: TransferContract
parameter {
type_url: "type.googleapis.com/protocol.TransferContract"
value: "\n\025A\311\304\327\027\307\tp\350\346(\302\256\354\256WUD0\216\304\022\025A(\213\354?*\222\036\333?\272\232\3730\360Z*\327\234\247\020\030\330\214\213\001"
}
}
timestamp: 1639331049389
}
signature: "\212\305\333<\001l\256\2257\310t+\275*G\002b\316M\331?\361\343\232ud\221\370\203 [`\361\325\351Sf\323*5\377\315\340+\317\227\353\375\334\305\204\322k.\255\320\021U\316\242E0\304\006\001"
ret {
contractRet: SUCCESS
}
}
transactions {
raw_data {
ref_block_bytes: "a\r"
ref_block_hash: "\257\r\311x\024I\321\356"
expiration: 1639331106000
contract {
type: TriggerSmartContract
parameter {
type_url: "type.googleapis.com/protocol.TriggerSmartContract"
value: "\n\025A\311\304\327\027\307\tp\350\346(\302\256\354\256WUD0\216\304\022\025A\246\024\370\003\266\375x\t\206\244,x\354\234\177w\346\336\321<\"D\251\005\234\273\000\000\000\000\000\000\000\000\000\000\000\000\300G\334z\264\200\216\017?\177Z\272\240\267\300|\221\271\370\337\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\026\225\313\330"
}
}
timestamp: 1639331048431
fee_limit: 100000000
}
signature: "\311=j\354\342\253M\303\353\034\216\353\227\215\261\2607\330\036\216\270\242 \274\252\266\212\003\205Q,\261l9\f\207\r&\357\267\307\265\204~\310\253-1\201\305\353\016+\370R\343Xs%z\307\242}G\001"
ret {
contractRet: SUCCESS
}
}
transactions {
raw_data {
ref_block_bytes: "a\r"
ref_block_hash: "\257\r\311x\024I\321\356"
expiration: 1639331106000
contract {
type: TriggerSmartContract
parameter {
type_url: "type.googleapis.com/protocol.TriggerSmartContract"
value: "\n\025A\311\304\327\027\307\tp\350\346(\302\256\354\256WUD0\216\304\022\025A\246\024\370\003\266\375x\t\206\244,x\354\234\177w\346\336\321<\"D\251\005\234\273\000\000\000\000\000\000\000\000\000\000\000\000\314\331f\256\254r\206\307\253\251K\227z\346e\316\344\365O\272\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\372\360\200"
}
}
timestamp: 1639331048583
fee_limit: 100000000
}
signature: "\200\356;\241\023~\222\033\320P\203\323\332f<\221V\v.\251\027BD\274Q\226LL\252\202\372\020\006\263\3616],X\262I`\332\232\373\364*\331\221\324\024\264tE\372L:\030\336\364/H\243\r\000"
ret {
contractRet: SUCCESS
}
// 太长了,省略部分
...
...
...
witness_signature: "\225\245\240\342\313y\270\311\204`\243\372\241]~9i\2637\327\272\3274\210\004\327\252\033,\376\251\315v\232$l\263\375\346\232A\375\031\337.!y\201|\360[\022\177\313]\222\2645Y\301\270Y}\034\000"
}

区块头 和 区块体,在存储时,也是分开存储,使用leveldb时,也表现在存储在两个不同的物理数据文件中。

总结

区块处理的流程大致是这样,泛区块网络传输、验证、处理、存储等,是一个比较核心的流程,区块链中最基本的两个流程:1.产块 和 2.区块上链(处理成功的区块)。在Tron中每3秒产一个块,空块(不包含交易,只有区块头的块)也是可以上链的。

理解这个流程,对链的开发和特点有一些帮助。