前言
区块链当中,所有的交易都包含在区块中,如何验证每一次接收到的区块的合法性就至关重要。 区块链是分布式的、对外公开的,所有人都可以参与,有些恶意节点通过修改代码的形式加入到区块链中,把一些对自己有利的交易、区块广播到节点当中,使这些交易对自己有利。 所以判断合法交易、区块就尤为重要。这些操作都是交易区块链当中的各个节点进行验证。
区块
区块是用来包含tron网中的交易的数据结构,是由27个超级节点(SR),每隔3秒,依次轮流将交易打包后的数据结构。
处理区块
处理入口
区块处理主要有两个入口,也对应两种场景:
- 同步处理区块
- 追块处理区块
同步处理区块,是节点之前本节点和网络中的节点区块之前高度没有差异,同步接收最新的区块。 追块处理区块,本节点区块高度落后网络中的节点太远,需要追到最新高度后,转换为同步处理区块。
同步处理区块
入口类:TronNetHandler TronNetHandler 是一个 Netty 的 Handler,接收其他节点的消息,基本和其它节点交互的入口类都是这个类。 整个流程调用栈:
代码语言:javascript复制TronNetHandler.channelRead0
|---tronNetService.onMessage(peer, msg);
|---BlockMsgHandler.processMessage();
|---tronNetDelegate.processBlock(block, false);
|---dbManager.pushBlock(block);
网络同步入口
TronNetHandler 是网络入口,是一个Netty的Handler。
代码语言:javascript复制@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 处理入口,包括几种不同的处理消息类型:
- SYNC_BLOCK_CHAIN 请求同步区块链消息
- BLOCK_CHAIN_INVENTORY 区块链区块清单消息
- INVENTORY 请求获取数据消息
- FETCH_INV_DATA 请求获取数据消息
- BLOCK 区块数据消息
- TRXS 交易易消息
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
处理实时接收到的区块。主要是做几步:
- 区块校验
- 接收 或 同步区块判断
- 处理区块
@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
缓存或数据库中查询这一块是否存在来决定。
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
核心处理区块逻辑,包括:
- 区块验签
- 切链
- 执行区块交易
- 奖励发放
等一系列操作。
代码语言:javascript复制
/**
* 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
代码语言:javascript复制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
代码语言:javascript复制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种处理场景:
- 启动同步区块
- 产块后处理区块
- 运行接收区块
启动同步区块
如果是一个新的节点接入到Tron链当中,当前数据库当中的数据是空的,一个区块都没有,需要从网络当中进行同步区块。
产块后处理区块
SR节点自己生产的区块,自己也需要处理
运行接收区块
节点运行过程中,接收到的区块。
区块结构
一个区块由两部分组成:
- 区块头
- 区块体
// 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观察区块的结果
代码语言:javascript复制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
]
区块体包含交易:
代码语言:javascript复制transactions {
raw_data {
ref_block_bytes: "`373"
ref_block_hash: "