java-tron 区块处理

2023-10-23 14:24:48 浏览数 (2)

前言

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

区块

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

处理区块

处理入口

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

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

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

同步处理区块

入口类: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 处理入口,包括几种不同的处理消息类型:

  1. SYNC_BLOCK_CHAIN 请求同步区块链消息
  2. BLOCK_CHAIN_INVENTORY 区块链区块清单消息
  3. INVENTORY 请求获取数据消息
  4. FETCH_INV_DATA 请求获取数据消息
  5. BLOCK 区块数据消息
  6. TRXS 交易易消息
代码语言:javascript复制
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. 处理区块
代码语言:javascript复制

@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缓存或数据库中查询这一块是否存在来决定。

代码语言:javascript复制

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. 奖励发放

等一系列操作。

代码语言: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种处理场景:

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

启动同步区块

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

产块后处理区块

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

运行接收区块

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

区块结构

一个区块由两部分组成:

  1. 区块头
  2. 区块体
代码语言:javascript复制
// 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: "33376ab257213z'"
    expiration: 1639331106000
    data: "344270223346263250344272216345214272345235227351223276351241271347233256345214205350243205347255226345210222350277220350220245344270200347253231345274217346234215345212241357274214344273243345270201351203250347275262357274214DAPP345274200345217221357274214346211271351207217350275254350264246357274214346265267345206205345244226350264242347273217345252222344275223345256243345217221357274214345256241350256241346212245345221212357274214350203275351207217347247237350265201357274214346263242345234272345212251346211213https://tronhelp.io 345256242346234215357274232tronassistant/tronaide"
    contract {
      type: TransferContract
      parameter {
        type_url: "type.googleapis.com/protocol.TransferContract"
        value: "n25A3036m24'222226R'(4Bk314U314266360373002225A265pN363C343222#1@g1353712334275340kC3001"
      }
    }
    timestamp: 1639331047596
  }
  signature: "177272316M370-37333}2027325506372u5g 372311255341uN209,377332!21125:244303}o27117-364s`31262rn02E272225253^G325`260@Gpl17366\00"
  ret {
    contractRet: SUCCESS
  }
}
transactions {
  raw_data {
    ref_block_bytes: "ar"
    ref_block_hash: "257r311x24I321356"
    expiration: 1639331106000
    contract {
      type: TransferContract
      parameter {
        type_url: "type.googleapis.com/protocol.TransferContract"
        value: "n25A31130432727307tp350346(302256354256WUD02163042225A(213354?*22236333?2722323730360Z*327234247203033021421301"
      }
    }
    timestamp: 1639331049389
  }
  signature: "212305333<01l2562257310t 275*G02b316M331?361343232ud221370203 [`361325351Sf323*5377315340 317227353375334305204322k.25532021U316242E03040601"
  ret {
    contractRet: SUCCESS
  }
}
transactions {
  raw_data {
    ref_block_bytes: "ar"
    ref_block_hash: "257r311x24I321356"
    expiration: 1639331106000
    contract {
      type: TriggerSmartContract
      parameter {
        type_url: "type.googleapis.com/protocol.TriggerSmartContract"
        value: "n25A31130432727307tp350346(302256354256WUD02163042225A2462437003266375xt206244,x354234177w346336321<"D25105234273000000000000000000000000300G334z26420021617?177Z272240267300|2212713703370000000000000000000000000000000000000000000000000000000026225313330"
      }
    }
    timestamp: 1639331048431
    fee_limit: 100000000
  }
  signature: "311=j354342253M30335334216353227215261260733036216270242 27425226621203205Q,261l9f207r&357267307265204~310253-120130535316 370R343Xs%z307242}G01"
  ret {
    contractRet: SUCCESS
  }
}
transactions {
  raw_data {
    ref_block_bytes: "ar"
    ref_block_hash: "257r311x24I321356"
    expiration: 1639331106000
    contract {
      type: TriggerSmartContract
      parameter {
        type_url: "type.googleapis.com/protocol.TriggerSmartContract"
        value: "n25A31130432727307tp350346(302256354256WUD02163042225A2462437003266375xt206244,x354234177w346336321<"D25105234273000000000000000000000000314331f256254r206307253251K227z346e316344365O2720000000000000000000000000000000000000000000000000000000002372360200"
      }
    }
    timestamp: 1639331048583
    fee_limit: 100000000
  }
  signature: "200356;24123~22233320P203323332f<221Vv.25127BD274Q226LL25220237220062633616],X262I`332232373364*33122132424264tE372L:30336364/H243r00"
  ret {
    contractRet: SUCCESS
  }
  // 太长了,省略部分
  ...
  ...
  ...
  witness_signature: "225245240342313y270311204`243372241]~9i263732727232742100432725233,376251315v232$l263375346232A37531337.!y201|360[22177313]2222645Y301270Y}3400"
}

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

总结

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

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

0 人点赞