tron 交易处理--交易执行逻辑

2023-10-23 14:34:08 浏览数 (2)

前言

分布式区块链环境下,所有的钱包要发起交易,都可以通过网络中的FullNode节点发起交易。 TRON 网络中,交易是从客户端发起,再通过 FullNode 进行广播,并将交易广播到网络的SR节点,并由SR节点进行打包。

主要角色

TRON网络中,站在发起交易的角度去看,需要了解的三个角色:

  1. 钱包客户端,代表用户
  2. FullNode全节点,用来转广播交易
  3. SR超级节点,用来使交易上链

使用TRON网络,主要就是各种钱包客户端。 构建交易,需要通过钱包应用发起,可以是手机钱包或者浏览器钱包插件,都可以发起一笔交易,也可以使用HTTP接口或者RPC接口都可以发起交易。

比如用用浏览器插件发起:

当然如果需要深入了解,可以使用官方的wallet-cli工具,通过代码的方式,了解其实现原理。 官方钱包项目: https://github.com/tronprotocol/wallet-cli

交易

图形界面操作,就不需要多说了,这里来了解一下使用wallet-cli工具发起的转账流程,wallet-cli就是一个客户端,图形界面当中使用客户端也是相同原理。

TRON 中有三种代币,是三种不同类型的交易逻辑:

  1. 原生代币:TRX
  2. TRC10代币:可自行发行的代币,不能执行智能合约
  3. TRC20代币:可自行发行,可执行智能合约的合约代币

这三种代币可以理解成就是三套机构,内部业务完全不同。一个比一个复杂。

构建原生代币:TRX交易

构建一笔TRX交易,需要和FullNode交互两次:

  1. 构建交易
  2. 广播交易

大至的处理流程

钱包发起交易-->FullNode 接收交易广播交易-->SR节点接收交易放入队列中

这个图描述了交易的构建、广播的一个大致流程,可以阅读代码来描述更多细节。

sendCoin 构建TRX交易,代码入口,整个流程比较长,这里看关键部份,有兴趣的小伙伴推荐拉下整个项目看一下。

构建交易

在这个方法中,有两次和FullNode的交互,分别是:

  1. rpcCli.createTransaction2: 调用gRPC访问FullNode构建交易基础信息
  2. processTransactionExtention: 广播交易到FullNode
代码语言:javascript复制
public boolean sendCoin(byte[] owner, byte[] to, long amount)
    throws CipherException, IOException, CancelException {
  if (owner == null) {
    owner = getAddress();
  }

  // 在本地构建交易对象
  TransferContract contract = createTransferContract(to, owner, amount);
  // recVersion=2,上面实始化时,从配置文件中初化为2
  if (rpcVersion == 2) {
    // 1.调用FullNode 的createTransaction 构建交易
    TransactionExtention transactionExtention = rpcCli.createTransaction2(contract);
    // 2.处理交易,并广播交易,最终结果由网络中27个SR中的某个SR节点打包上链
    return processTransactionExtention(transactionExtention);
  } else {
    Transaction transaction = rpcCli.createTransaction(contract);
    return processTransaction(transaction);
  }
}
构建本地交易对象

本地的构建很简单就几个关键要素 owner: 自己的地址 to: 目标地址 amount: 要转入的金额

代码语言:javascript复制
public static TransferContract createTransferContract(byte[] to, byte[] owner, long amount) {
  TransferContract.Builder builder = TransferContract.newBuilder();
  ByteString bsTo = ByteString.copyFrom(to);
  ByteString bsOwner = ByteString.copyFrom(owner);
  builder.setToAddress(bsTo);
  builder.setOwnerAddress(bsOwner);
  builder.setAmount(amount);

  return builder.build();
}
FullNode交易构建入口

再看下rpcCli.createTransaction2 对应的FullNode端的处理,如果感觉比较乱,参照一下上面时序图的步骤。 FullNode的gRPC入口:WalletGrpc

调用栈:

  1. WalletGrpc.invoke(Req request, io.grpc.stub.StreamObserver responseObserver)
  1. RpcApiService.createTransaction2(TransferContract request, StreamObserver responseObserver)
  2. RpcApiService.createTransactionExtention(Message request, ContractType contractType, StreamObserver responseObserver)
  3. RpcApiService.createTransactionCapsule(com.google.protobuf.Message message, ContractType contractType)
  4. Wallet.createTransactionCapsule(message, contractType);
  5. Wallet.setTransaction(trx);

核心方法在: Wallet.createTransactionCapsule() 和 setTransaction(trx);

代码语言:javascript复制
public TransactionCapsule createTransactionCapsule(com.google.protobuf.Message message,
    ContractType contractType) throws ContractValidateException {
  TransactionCapsule trx = new TransactionCapsule(message, contractType);
  if (contractType != ContractType.CreateSmartContract
      && contractType != ContractType.TriggerSmartContract) {
    List<Actuator> actList = ActuatorFactory.createActuator(trx, chainBaseManager);
    for (Actuator act : actList) {
      act.validate();
    }
  }

  if (contractType == ContractType.CreateSmartContract) {

    CreateSmartContract contract = ContractCapsule
        .getSmartContractFromTransaction(trx.getInstance());
    long percent = contract.getNewContract().getConsumeUserResourcePercent();
    if (percent < 0 || percent > 100) {
      throw new ContractValidateException("percent must be >= 0 and <= 100");
    }
  }
  setTransaction(trx);
  return trx;
}

setTransaction设置交易引用区块和过期时间

代码语言:javascript复制
private void setTransaction(TransactionCapsule trx) {
  try {
    BlockId blockId = chainBaseManager.getHeadBlockId();
    // 判断是以 solidity 还上 head 为交易的引用区块
    // 这里面有区别,如果是以head为区块引用交易有可能用丢失
    // 以 solidity 也就是固化块,交易分叉也不会丢失,因为头块有可能会被回滚
    if ("solid".equals(Args.getInstance().getTrxReferenceBlock())) {
      blockId = chainBaseManager.getSolidBlockId();
    }
    trx.setReference(blockId.getNum(), blockId.getBytes());
    // 超时时间等于: 当前时间最区块高度时间戳   60秒
    // Args.getInstance().getTrxExpirationTimeInMilliseconds(); 是60秒
    // public static final long TRANSACTION_DEFAULT_EXPIRATION_TIME = 60 * 1_000L; //60 seconds
    long expiration = chainBaseManager.getHeadBlockTimeStamp()   Args.getInstance()
        .getTrxExpirationTimeInMilliseconds();
    trx.setExpiration(expiration);
    trx.setTimestamp();
  } catch (Exception e) {
    logger.error("Create transaction capsule failed.", e);
  }
}

到这里交易的构建就完成了,但是并不会在FullNode端记录任何数据,因为这是去中心化的服务,成功的交易才会被打包到区块当中。不成功的交易会被接直丢弃,执行不成功并不会对账户造成损失。 这笔交易有可能因为网络原因、余额不足等原理,最后执行不一定会成功。

广播交易

回到sendCoin方法中,交易构建完成后,第二步就是广播。

主要方法:processTransactionExtention(transactionExtention);

代码语言:javascript复制
private boolean processTransactionExtention(TransactionExtention transactionExtention)
    throws IOException, CipherException, CancelException {
  if (transactionExtention == null) {
    return false;
  }
  Return ret = transactionExtention.getResult();
  if (!ret.getResult()) {
    System.out.println("Code = "   ret.getCode());
    System.out.println("Message = "   ret.getMessage().toStringUtf8());
    return false;
  }
  Transaction transaction = transactionExtention.getTransaction();
  if (transaction == null || transaction.getRawData().getContractCount() == 0){
    System.out.println("Transaction is empty");
    return false;
  }
  System.out.println(
      "Receive txid = "   ByteArray.toHexString(transactionExtention.getTxid().toByteArray()));
  // 对交易签名
  transaction = signTransaction(transaction);
  return rpcCli.broadcastTransaction(transaction);
}
交易签名
代码语言:javascript复制
private Transaction signTransaction(Transaction transaction)
    throws CipherException, IOException, CancelException {
  if (transaction.getRawData().getTimestamp() == 0) {
    // long currentTime = System.currentTimeMillis();
    transaction = TransactionUtils.setTimestamp(transaction);
  }
  System.out.println("Your transaction details are as follows, please confirm.");
  System.out.println(Utils.printTransaction(transaction));

  Scanner in = new Scanner(System.in);
  System.out.println("Please confirm that you want to continue enter y or Y, else any other.");

  while (true) {
    String input = in.nextLine().trim();
    String str = input.split("\s ")[0];
    if ("y".equalsIgnoreCase(str)) {
      break;
    } else {
      throw new CancelException("User cancelled");
    }
  }
  System.out.println("Please input your password.");
  // 转成char数组
  char[] password = Utils.inputPassword(false);
  // 转成 byte[]
  byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password);
  // 清除掉密码
  org.tron.keystore.StringUtils.clear(password);
  System.out.println(
      "txid = "   ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray())));
  // ECDSA加密:将 transaction 配合 publicKey,对交易生成签名
  transaction = TransactionUtils.sign(transaction, this.getEcKey(passwd));
  org.tron.keystore.StringUtils.clear(passwd);
  return transaction;
}

sign方法

代码语言:javascript复制
public static Transaction sign(Transaction transaction, ECKey myKey) {
  Transaction.Builder transactionBuilderSigned = transaction.toBuilder();
  byte[] hash = Sha256Hash.hash(transaction.getRawData().toByteArray());
  List<Contract> listContract = transaction.getRawData().getContractList();
  for (int i = 0; i < listContract.size(); i  ) {
    ECDSASignature signature = myKey.sign(hash);
    ByteString bsSign = ByteString.copyFrom(signature.toByteArray());
    transactionBuilderSigned.addSignature(
        bsSign);//Each contract may be signed with a different private key in the future.
  }

  transaction = transactionBuilderSigned.build();
  return transaction;
}

this.getEcKey 获取取地秘钥信息

代码语言:javascript复制
public ECKey getEcKey(byte[] password) throws CipherException, IOException {
  if (walletFile == null) {
    this.walletFile = loadWalletFile();
    this.address = decodeFromBase58Check(this.walletFile.getAddress());
  }
  // 私钥加密部份,很复杂完全看晕了
  return Wallet.decrypt(password, walletFile);
}
广播交易到FullNode

通过rpcCli.broadcastTransaction(transaction);这个方法广播到FullNode节点上,FullNode对广播过来的交易的主要入口: 交易由钱包发起后,会最先广播到FullNode节点,由FullNode节点将交易转发到SR节点上。

方法处口

主要入口方法是:Wallet#broadcastTransaction,这个方法被很多方法调用,但是主要的两个调用方法:RPCHTTP是以下两个方法。

RPC调用

走的 GRPC 调用,RpcApiService.broadcastTransaction

HTTP调用

java-tron的HTTP接口都是以 Servlet 结属,HTTP服务器使用的是 Jetty。 交易广播的入口在 BroadcastHexServlet.doPost,调用 wallet.broadcastTransaction(transaction);

HTTP方法处口
代码语言:javascript复制
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
  try {
    String input = request.getReader().lines()
        .collect(Collectors.joining(System.lineSeparator()));
    String trx = JSONObject.parseObject(input).getString("transaction");
    Transaction transaction = Transaction.parseFrom(ByteArray.fromHexString(trx));
    TransactionCapsule transactionCapsule = new TransactionCapsule(transaction);
    String transactionID = ByteArray
        .toHexString(transactionCapsule.getTransactionId().getBytes());
    GrpcAPI.Return result = wallet.broadcastTransaction(transaction);
    JSONObject json = new JSONObject();
    json.put("result", result.getResult());
    json.put("code", result.getCode().toString());
    json.put("message", result.getMessage().toStringUtf8());
    json.put("transaction", JsonFormat.printToString(transaction, true));
    json.put("txid", transactionID);

    response.getWriter().println(json.toJSONString());
  } catch (Exception e) {
    Util.processError(e, response);
  }
}

业务处理

前面两个是介绍调用入口,这部分说明如何处理交易并广播。 主要的处理入口:Manager#pushTransaction,会对接收到的交易进行处理。

处理流程:

  1. 交易进入接收队列pushTransactionQueue
  2. 验签
  3. 构建快照
  4. 处理交易processTransaction
  5. 处理成功的交易进pendingTransactions,这个真正的交易缓存池!!
  6. 从交易接收队pushTransactionQueue列中移除这笔交易

篇幅有限,只看核心部分代码,细节看代码注释。

Wallet.broadcastTransaction

代码语言:javascript复制
/**
 * Broadcast a transaction.
 */
public GrpcAPI.Return broadcastTransaction(Transaction signedTransaction) {
  GrpcAPI.Return.Builder builder = GrpcAPI.Return.newBuilder();
  // 重新构建交易,以 Capsule 结构的类,都是一个对 protobfu 的包装结构,最终会转化成二进制
  TransactionCapsule trx = new TransactionCapsule(signedTransaction);
  //  
  trx.setTime(System.currentTimeMillis());
  Sha256Hash txID = trx.getTransactionId();
  try {
    TransactionMessage message = new TransactionMessage(signedTransaction.toByteArray());
    // 默认 minEffectiveConnection = 1
    if (minEffectiveConnection != 0) {
      if (tronNetDelegate.getActivePeer().isEmpty()) {
        logger.warn("Broadcast transaction {} has failed, no connection.", txID);
        return builder.setResult(false).setCode(response_code.NO_CONNECTION)
            .setMessage(ByteString.copyFromUtf8("No connection."))
            .build();
      }

      // needSyncFromUs 是在两个节点连接建立时,判断是从本节点同步给对方节点,还是反过来
      // 这里过滤掉需要同步的节点
      int count = (int) tronNetDelegate.getActivePeer().stream()
          .filter(p -> !p.isNeedSyncFromUs() && !p.isNeedSyncFromPeer())
          .count();

      if (count < minEffectiveConnection) {
        String info = "Effective connection:"   count   " lt minEffectiveConnection:"
              minEffectiveConnection;
        logger.warn("Broadcast transaction {} has failed. {}.", txID, info);
        return builder.setResult(false).setCode(response_code.NOT_ENOUGH_EFFECTIVE_CONNECTION)
            .setMessage(ByteString.copyFromUtf8(info))
            .build();
      }
    }

    // 判断 当前 pending 和 rePush 队列的长度是否超过最大值
    // 这个值同样可以通过配置文件配置,默认2000
    if (dbManager.isTooManyPending()) {
      logger.warn("Broadcast transaction {} has failed, too many pending.", txID);
      return builder.setResult(false).setCode(response_code.SERVER_BUSY)
          .setMessage(ByteString.copyFromUtf8("Server busy.")).build();
    }

    // 交易缓存
    if (trxCacheEnable) {
      // 拒绝重复交易
      if (dbManager.getTransactionIdCache().getIfPresent(txID) != null) {
        logger.warn("Broadcast transaction {} has failed, it already exists.", txID);
        return builder.setResult(false).setCode(response_code.DUP_TRANSACTION_ERROR)
            .setMessage(ByteString.copyFromUtf8("Transaction already exists.")).build();
      } else {
        dbManager.getTransactionIdCache().put(txID, true);
      }
    }

    if (chainBaseManager.getDynamicPropertiesStore().supportVM()) {
      trx.resetResult();
    }
    dbManager.pushTransaction(trx);
    int num = tronNetService.fastBroadcastTransaction(message);
    if (num == 0 && minEffectiveConnection != 0) {
      return builder.setResult(false).setCode(response_code.NOT_ENOUGH_EFFECTIVE_CONNECTION)
          .setMessage(ByteString.copyFromUtf8("P2P broadcast failed.")).build();
    } else {
      logger.info("Broadcast transaction {} to {} peers successfully.", txID, num);
      return builder.setResult(true).setCode(response_code.SUCCESS).build();
    }

Manager.pushTransaction 交易处理

代码语言:javascript复制
/**
 * push transaction into pending.
 */
public boolean pushTransaction(final TransactionCapsule trx)
    throws ValidateSignatureException, ContractValidateException, ContractExeException,
    AccountResourceInsufficientException, DupTransactionException, TaposException,
    TooBigTransactionException, TransactionExpirationException,
    ReceiptCheckErrException, VMIllegalException, TooBigTransactionResultException {

  if (isShieldedTransaction(trx.getInstance()) && !Args.getInstance()
      .isFullNodeAllowShieldedTransactionArgs()) {
    return true;
  }

  pushTransactionQueue.add(trx);

  try {
    if (!trx.validateSignature(chainBaseManager.getAccountStore(),
        chainBaseManager.getDynamicPropertiesStore())) {
      throw new ValidateSignatureException("trans sig validate failed");
    }

    synchronized (this) {
      if (isShieldedTransaction(trx.getInstance())
          && shieldedTransInPendingCounts.get() >= shieldedTransInPendingMaxCounts) {
        return false;
      }
      if (!session.valid()) {
        session.setValue(revokingStore.buildSession());
      }

      try (ISession tmpSession = revokingStore.buildSession()) {
        processTransaction(trx, null);
        trx.setTrxTrace(null);
        pendingTransactions.add(trx);
        tmpSession.merge();
      }
      if (isShieldedTransaction(trx.getInstance())) {
        shieldedTransInPendingCounts.incrementAndGet();
      }
    }
  } finally {
    pushTransactionQueue.remove(trx);
  }
  return true;
}

Manager.processTransaction 核心逻辑

流程就是这么个流程,那么来看几个的问题,细节在 processTransaction。

代码语言:javascript复制

/**
 * Process transaction.
 */
public TransactionInfo processTransaction(final TransactionCapsule trxCap, BlockCapsule blockCap)
    throws ValidateSignatureException, ContractValidateException, ContractExeException,
    AccountResourceInsufficientException, TransactionExpirationException,
    TooBigTransactionException, TooBigTransactionResultException,
    DupTransactionException, TaposException, ReceiptCheckErrException, VMIllegalException {
  if (trxCap == null) {
    return null;
  }

  if (Objects.nonNull(blockCap)) {
    chainBaseManager.getBalanceTraceStore().initCurrentTransactionBalanceTrace(trxCap);
  }
  // TaPos 校验,这个很精随,交易必须是引用自最近的 65535个区块,防止交易在分叉链上双花
  validateTapos(trxCap);
  // 校验:交易超时时间 和 交易最大字节数
  // TRANSACTION_MAX_BYTE_SIZE = 500 * 1_024L;
  validateCommon(trxCap);

  if (trxCap.getInstance().getRawData().getContractList().size() != 1) {
    throw new ContractSizeNotEqualToOneException(
        "act size should be exactly 1, this is extend feature");
  }

  // 交易验重
  validateDup(trxCap);

  if (!trxCap.validateSignature(chainBaseManager.getAccountStore(),
      chainBaseManager.getDynamicPropertiesStore())) {
    throw new ValidateSignatureException("transaction signature validate failed");
  }

  TransactionTrace trace = new TransactionTrace(trxCap, StoreFactory.getInstance(),
      new RuntimeImpl());
  trxCap.setTrxTrace(trace);

  // 计算代宽 TRX 转账消耗
  consumeBandwidth(trxCap, trace);
  // 计算多签手续费
  consumeMultiSignFee(trxCap, trace);

  trace.init(blockCap, eventPluginLoaded);
  // 验证是否是合约类型
  trace.checkIsConstant();
  trace.exec();

  if (Objects.nonNull(blockCap)) {
    trace.setResult();
    if (blockCap.hasWitnessSignature()) {
      if (trace.checkNeedRetry()) {
        String txId = Hex.toHexString(trxCap.getTransactionId().getBytes());
        logger.info("Retry for tx id: {}", txId);
        trace.init(blockCap, eventPluginLoaded);
        trace.checkIsConstant();
        // 执行交易,调用实际执行方法,根据不同类型执行
        // 普通交易 和 智能合约 交易是不同的两套逻辑
        // 这块逻辑直接写在文章下面
        trace.exec();
        // 设置 resultCode
        trace.setResult();
        logger.info("Retry result for tx id: {}, tx resultCode in receipt: {}",
            txId, trace.getReceipt().getResult());
      }
      // 校验最络结果,非预期结果会抛 ReceiptCheckErrException 异常
      trace.check();
    }
  }

  // 计算实际消耗能量
  trace.finalization();
  if (Objects.nonNull(blockCap) && getDynamicPropertiesStore().supportVM()) {
    trxCap.setResult(trace.getTransactionContext());
  }
  chainBaseManager.getTransactionStore().put(trxCap.getTransactionId().getBytes(), trxCap);

  Optional.ofNullable(transactionCache)
      .ifPresent(t -> t.put(trxCap.getTransactionId().getBytes(),
          new BytesCapsule(ByteArray.fromLong(trxCap.getBlockNum()))));

  TransactionInfoCapsule transactionInfo = TransactionUtil
      .buildTransactionInfoInstance(trxCap, blockCap, trace);

  // if event subscribe is enabled, post contract triggers to queue
  // only trigger when process block
  if (Objects.nonNull(blockCap) && !blockCap.isMerkleRootEmpty()) {
    String blockHash = blockCap.getBlockId().toString();
    postContractTrigger(trace, false, blockHash);
  }

  Contract contract = trxCap.getInstance().getRawData().getContract(0);
  if (isMultiSignTransaction(trxCap.getInstance())) {
    ownerAddressSet.add(ByteArray.toHexString(TransactionCapsule.getOwner(contract)));
  }

  if (Objects.nonNull(blockCap)) {
    chainBaseManager.getBalanceTraceStore()
        .updateCurrentTransactionStatus(
            trace.getRuntimeResult().getResultCode().name());
    chainBaseManager.getBalanceTraceStore().resetCurrentTransactionTrace();
  }
  //set the sort order
  trxCap.setOrder(transactionInfo.getFee());
  if (!eventPluginLoaded) {
    trxCap.setTrxTrace(null);
  }
  return transactionInfo.getInstance();
}

交易执行 RuntimeImpl.execute()

Manager.process是执行交易的入口的话,RuntimeImpl.execute就是选择实际执行交易的方法。

VMActuator: TVM 类型交易执行器,也就是智能合约 Actuator: 非智能合约类型交易,包括TRX转账、提案(提案也是一笔交易)、TRC10交易等

代码语言:javascript复制
@Override
public void execute(TransactionContext context)
    throws ContractValidateException, ContractExeException {
  this.context = context;

  ContractType contractType = context.getTrxCap().getInstance().getRawData().getContract(0)
      .getType();
  switch (contractType.getNumber()) {
    case ContractType.TriggerSmartContract_VALUE:
    case ContractType.CreateSmartContract_VALUE:
      Set<String> actuatorSet = CommonParameter.getInstance().getActuatorSet();
      if (!actuatorSet.isEmpty() && !actuatorSet.contains(VMActuator.class.getSimpleName())) {
        throw new ContractValidateException("not exist contract "   "SmartContract");
      }
      actuator2 = new VMActuator(context.isStatic());
      break;
    default:
      actuatorList = ActuatorCreator.getINSTANCE().createActuator(context.getTrxCap());
  }
  if (actuator2 != null) {
    // 智能合约逻辑
    actuator2.validate(context);
    actuator2.execute(context);
  } else {
    // 非合约交易
    for (Actuator act : actuatorList) {
      // 验证
      act.validate();
      // 执行
      // 在TRX转账的场景下执行的是 TransferActuator
      act.execute(context.getProgramResult().getRet());
    }
  }
  setResultCode(context.getProgramResult());
}

所有交易的Actuator

TRX转账交易执行的是TransferActuator

TransferActuator TRX转账交易

每个 Actuator 有两个主要的方法:

  1. validate
  2. execute

为什么是执行 TransferActutor 在每个 Actuator 注册时,都明确的Actuator的处理类型

TransferActutor 实现

代码语言:javascript复制
@Slf4j(topic = "actuator")
public class TransferActuator extends AbstractActuator {

  // 构造器注册交易类型: ContractType.TransferContract
  public TransferActuator() {
    super(ContractType.TransferContract, TransferContract.class);
  }

  @Override
  public boolean execute(Object object) throws ContractExeException {
    TransactionResultCapsule ret = (TransactionResultCapsule) object;
    if (Objects.isNull(ret)) {
      throw new RuntimeException(ActuatorConstant.TX_RESULT_NULL);
    }

    long fee = calcFee();
    AccountStore accountStore = chainBaseManager.getAccountStore();
    DynamicPropertiesStore dynamicStore = chainBaseManager.getDynamicPropertiesStore();
    try {
      TransferContract transferContract = any.unpack(TransferContract.class);
      long amount = transferContract.getAmount();
      byte[] toAddress = transferContract.getToAddress().toByteArray();
      byte[] ownerAddress = transferContract.getOwnerAddress().toByteArray();

      // if account with to_address does not exist, create it first.
      // 这里有点重点,如果 toAccount 不存在,就创建一个
      // 就像是, 我给你卡里转账,你的卡号不存在,我让银行立马生成一个卡号,是这个意思没错
      // 这么做的原因是,区块链是非中心化的节点,我在 A 节点创建账号,这个账号B节点未有存在,我也不能保证我跟B节点之前是存在通信的
      // 网络中的节点可能存在上百个,广播的话流量太大
      // 如果这个账号不存在,是随便写的怎么办?
      // 那这笔交易永远不可能到达
      AccountCapsule toAccount = accountStore.get(toAddress);
      if (toAccount == null) {
        boolean withDefaultPermission =
            dynamicStore.getAllowMultiSign() == 1;
        toAccount = new AccountCapsule(ByteString.copyFrom(toAddress), AccountType.Normal,
            dynamicStore.getLatestBlockHeaderTimestamp(), withDefaultPermission, dynamicStore);
        accountStore.put(toAddress, toAccount);

        fee = fee   dynamicStore.getCreateNewAccountFeeInSystemContract();
      }

      Commons.adjustBalance(accountStore, ownerAddress, -(Math.addExact(fee, amount)));
      if (dynamicStore.supportBlackHoleOptimization()) {
        dynamicStore.burnTrx(fee);
      } else {
        Commons.adjustBalance(accountStore, accountStore.getBlackhole(), fee);
      }
      Commons.adjustBalance(accountStore, toAddress, amount);
      ret.setStatus(fee, code.SUCESS);
    } catch (BalanceInsufficientException | ArithmeticException | InvalidProtocolBufferException e) {
      logger.debug(e.getMessage(), e);
      ret.setStatus(fee, code.FAILED);
      throw new ContractExeException(e.getMessage());
    }
    return true;
  }

  @Override
  public boolean validate() throws ContractValidateException {
    if (this.any == null) {
      throw new ContractValidateException(ActuatorConstant.CONTRACT_NOT_EXIST);
    }
    if (chainBaseManager == null) {
      throw new ContractValidateException(ActuatorConstant.STORE_NOT_EXIST);
    }

    // AccountStore 是账户数据库,使用的是 leveldb,比特币也是leveldb
    // 以太坊使用的是 rocksdb,是在 leveldb 的础上改进的db
    AccountStore accountStore = chainBaseManager.getAccountStore();
    DynamicPropertiesStore dynamicStore = chainBaseManager.getDynamicPropertiesStore();
    if (!this.any.is(TransferContract.class)) {
      throw new ContractValidateException(
          "contract type error, expected type [TransferContract], real type ["   this.any
              .getClass()   "]");
    }
    // fee = 0
    long fee = calcFee();
    final TransferContract transferContract;
    try {
      // any.unpack 是 protobuf 一种通配的写法,可以转成任意类型的对象
      transferContract = any.unpack(TransferContract.class);
    } catch (InvalidProtocolBufferException e) {
      logger.debug(e.getMessage(), e);
      throw new ContractValidateException(e.getMessage());
    }

    // 下面是常规校验
    byte[] toAddress = transferContract.getToAddress().toByteArray();
    byte[] ownerAddress = transferContract.getOwnerAddress().toByteArray();
    long amount = transferContract.getAmount();

    if (!DecodeUtil.addressValid(ownerAddress)) {
      throw new ContractValidateException("Invalid ownerAddress!");
    }
    if (!DecodeUtil.addressValid(toAddress)) {
      throw new ContractValidateException("Invalid toAddress!");
    }

    if (Arrays.equals(toAddress, ownerAddress)) {
      throw new ContractValidateException("Cannot transfer TRX to yourself.");
    }

    AccountCapsule ownerAccount = accountStore.get(ownerAddress);

    if (ownerAccount == null) {
      throw new ContractValidateException("Validate TransferContract error, no OwnerAccount.");
    }

    long balance = ownerAccount.getBalance();

    if (amount <= 0) {
      throw new ContractValidateException("Amount must be greater than 0.");
    }

    try {
      AccountCapsule toAccount = accountStore.get(toAddress);
      if (toAccount == null) {
        // 这里是有手续费的,默认值,也是0
        fee = fee   dynamicStore.getCreateNewAccountFeeInSystemContract();
      }
      //after ForbidTransferToContract proposal, send trx to smartContract by actuator is not allowed.
      if (dynamicStore.getForbidTransferToContract() == 1
          && toAccount != null
          && toAccount.getType() == AccountType.Contract) {

        throw new ContractValidateException("Cannot transfer TRX to a smartContract.");

      }

      // after AllowTvmCompatibleEvm proposal, send trx to smartContract which version is one
      // by actuator is not allowed.
      if (dynamicStore.getAllowTvmCompatibleEvm() == 1
          && toAccount != null
          && toAccount.getType() == AccountType.Contract) {

        ContractCapsule contractCapsule = chainBaseManager.getContractStore().get(toAddress);
        if (contractCapsule == null) { //  this can not happen
          throw new ContractValidateException(
              "Account type is Contract, but it is not exist in contract store.");
        } else if (contractCapsule.getContractVersion() == 1) {
          throw new ContractValidateException(
              "Cannot transfer TRX to a smartContract which version is one. "
                    "Instead please use TriggerSmartContract ");
        }
      }

      if (balance < Math.addExact(amount, fee)) {
        throw new ContractValidateException(
            "Validate TransferContract error, balance is not sufficient.");
      }

      if (toAccount != null) {
        Math.addExact(toAccount.getBalance(), amount);
      }
    } catch (ArithmeticException e) {
      logger.debug(e.getMessage(), e);
      throw new ContractValidateException(e.getMessage());
    }

    return true;
  }

  @Override
  public ByteString getOwnerAddress() throws InvalidProtocolBufferException {
    return any.unpack(TransferContract.class).getOwnerAddress();
  }

  @Override
  public long calcFee() {
    // 手续费0,但是会消耗带宽
    return TRANSFER_FEE;
  }

}

创建账号手续费

交易为什么要先进pushTransactionQueue?

pushTransactionQueue 就是一个交易缓存池,处理成功的交易会被放到pendingTransactions当中。 无论处理结果如何,最后都会从 pushTransactionQueue中移除。

直接进pendingTransactions处理不行吗?

可行,也不可行。 如果强行直接放 pendingTransactions 也不是不可以,但是更为负杂,pendingTransactions主要是在打包交易时提供有效的数据,假设所有交易都直接进pendingTransactions,里面的交易在打包时,还需要判断哪些有效哪些无效?就多了很多判断逻辑,还有processTransaction是一个Queue,是有顺序的,要删除已使用的交易时处理起来就劲了。 与其这么麻烦,不如分成两个处理。

交易竟然还有一个 rePush 对列,用这个的意义是什么?

这个开始理解不了,后来看到打包部分,打包时间只有750毫秒,这段时间内SR不可能把所有交易全打包,所以没打包完的交易移动到rePUsh队列中。

总结

注意在交易处理中pushBlock使用了synchronized,Manager中有4个地方使用了synchronized,分别是

  • pushBlock
  • generateBlock
  • eraseBlock
  • pushTransaction

说明这4个操作,同时只能有一个进行,这是因为Tron中的产易无法做到并行处理。原因是还是和区块链的非中心化特性有关。

0 人点赞