前言 分布式区块链环境下,所有的钱包要发起交易,都可以通过网络中的FullNode
节点发起交易。 TRON 网络中,交易是从客户端发起,再通过 FullNode 进行广播,并将交易广播到网络的SR
节点,并由SR
节点进行打包。
主要角色 TRON网络中,站在发起交易的角度去看,需要了解的三个角色:
钱包客户端,代表用户
FullNode全节点,用来转广播交易
SR超级节点,用来使交易上链
使用TRON网络,主要就是各种钱包客户端。 构建交易,需要通过钱包应用发起,可以是手机钱包或者浏览器钱包插件,都可以发起一笔交易,也可以使用HTTP接口或者RPC接口都可以发起交易。
比如用用浏览器插件发起:
当然如果需要深入了解,可以使用官方的wallet-cli
工具,通过代码的方式,了解其实现原理。 官方钱包项目: https://github.com/tronprotocol/wallet-cli
交易 图形界面操作,就不需要多说了,这里来了解一下使用wallet-cli
工具发起的转账流程,wallet-cli
就是一个客户端,图形界面当中使用客户端也是相同原理。
TRON 中有三种代币,是三种不同类型的交易逻辑:
原生代币:TRX
TRC10代币:可自行发行的代币,不能执行智能合约
TRC20代币:可自行发行,可执行智能合约的合约代币
这三种代币可以理解成就是三套机构,内部业务完全不同。一个比一个复杂。
构建原生代币:TRX交易 构建一笔TRX
交易,需要和FullNode交互两次:
构建交易
广播交易
大至的处理流程
钱包发起交易-->FullNode 接收交易广播交易-->SR节点接收交易放入队列中
这个图描述了交易的构建、广播的一个大致流程,可以阅读代码来描述更多细节。
sendCoin 构建TRX交易,代码入口,整个流程比较长,这里看关键部份,有兴趣的小伙伴推荐拉下整个项目看一下。
构建交易
在这个方法中,有两次和FullNode
的交互,分别是:
rpcCli.createTransaction2: 调用gRPC访问FullNode构建交易基础信息
processTransactionExtention: 广播交易到FullNode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public boolean sendCoin (byte [] owner, byte [] to, long amount) throws CipherException, IOException, CancelException { if (owner == null ) { owner = getAddress(); } TransferContract contract = createTransferContract(to, owner, amount); if (rpcVersion == 2 ) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); } else { Transaction transaction = rpcCli.createTransaction(contract); return processTransaction(transaction); } }
构建本地交易对象 本地的构建很简单就几个关键要素 owner: 自己的地址 to: 目标地址 amount: 要转入的金额
1 2 3 4 5 6 7 8 9 10 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
调用栈:
WalletGrpc.invoke(Req request, io.grpc.stub.StreamObserver responseObserver)
RpcApiService.createTransaction2(TransferContract request, StreamObserver responseObserver)
RpcApiService.createTransactionExtention(Message request, ContractType contractType, StreamObserver responseObserver)
RpcApiService.createTransactionCapsule(com.google.protobuf.Message message, ContractType contractType)
Wallet.createTransactionCapsule(message, contractType);
Wallet.setTransaction(trx);
核心方法在: Wallet.createTransactionCapsule() 和 setTransaction(trx);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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设置交易引用区块和过期时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void setTransaction (TransactionCapsule trx) { try { BlockId blockId = chainBaseManager.getHeadBlockId(); if ("solid" .equals(Args.getInstance().getTrxReferenceBlock())) { blockId = chainBaseManager.getSolidBlockId(); } trx.setReference(blockId.getNum(), blockId.getBytes()); 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);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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); }
交易签名 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 private Transaction signTransaction (Transaction transaction) throws CipherException, IOException, CancelException { if (transaction.getRawData().getTimestamp() == 0 ) { 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 [] password = Utils.inputPassword(false ); 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()))); transaction = TransactionUtils.sign(transaction, this .getEcKey(passwd)); org.tron.keystore.StringUtils.clear(passwd); return transaction; }
sign方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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); } transaction = transactionBuilderSigned.build(); return transaction; }
this.getEcKey 获取取地秘钥信息
1 2 3 4 5 6 7 8 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 ,这个方法被很多方法调用,但是主要的两个调用方法:RPC 和HTTP 是以下两个方法。
RPC调用 走的 GRPC 调用,RpcApiService.broadcastTransaction
HTTP调用 java-tron 的HTTP接口都是以 Servlet 结属,HTTP服务器使用的是 Jetty 。 交易广播的入口在 BroadcastHexServlet.doPost ,调用 wallet.broadcastTransaction(transaction);
HTTP方法处口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 ,会对接收到的交易进行处理。
处理流程:
交易进入接收队列pushTransactionQueue
验签
构建快照
处理交易processTransaction
处理成功的交易进pendingTransactions ,这个真正的交易缓存池!!
从交易接收队pushTransactionQueue 列中移除这笔交易
篇幅有限,只看核心部分代码,细节看代码注释。
Wallet.broadcastTransaction 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 public GrpcAPI.Return broadcastTransaction (Transaction signedTransaction) { GrpcAPI.Return.Builder builder = GrpcAPI.Return.newBuilder(); TransactionCapsule trx = new TransactionCapsule(signedTransaction); trx.setTime(System.currentTimeMillis()); Sha256Hash txID = trx.getTransactionId(); try { TransactionMessage message = new TransactionMessage(signedTransaction.toByteArray()); 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(); } 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(); } } 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 交易处理 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 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。
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 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); } validateTapos(trxCap); 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); 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(); trace.setResult(); logger.info("Retry result for tx id: {}, tx resultCode in receipt: {}" , txId, trace.getReceipt().getResult()); } 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 (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(); } trxCap.setOrder(transactionInfo.getFee()); if (!eventPluginLoaded) { trxCap.setTrxTrace(null ); } return transactionInfo.getInstance(); }
交易执行 RuntimeImpl.execute() Manager.process是执行交易的入口的话,RuntimeImpl.execute就是选择实际执行交易的方法。
VMActuator : TVM 类型交易执行器,也就是智能合约Actuator : 非智能合约类型交易,包括TRX转账、提案(提案也是一笔交易)、TRC10交易等
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 @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(); act.execute(context.getProgramResult().getRet()); } } setResultCode(context.getProgramResult()); }
所有交易的Actuator
TRX转账交易执行的是TransferActuator
TransferActuator TRX转账交易 每个 Actuator 有两个主要的方法:
validate
execute
为什么是执行 TransferActutor 在每个 Actuator 注册时,都明确的Actuator的处理类型
TransferActutor 实现 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 @Slf4j(topic = "actuator") public class TransferActuator extends AbstractActuator { 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(); 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 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() + "]" ); } long fee = calcFee(); final TransferContract transferContract; try { 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 ) { fee = fee + dynamicStore.getCreateNewAccountFeeInSystemContract(); } if (dynamicStore.getForbidTransferToContract() == 1 && toAccount != null && toAccount.getType() == AccountType.Contract) { throw new ContractValidateException("Cannot transfer TRX to a smartContract." ); } if (dynamicStore.getAllowTvmCompatibleEvm() == 1 && toAccount != null && toAccount.getType() == AccountType.Contract) { ContractCapsule contractCapsule = chainBaseManager.getContractStore().get(toAddress); if (contractCapsule == null ) { 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 () { 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中的产易无法做到并行处理。原因是还是和区块链的非中心化特性有关。