前言
分布式区块链环境下,所有的钱包要发起交易,都可以通过网络中的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
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
调用栈:
- 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);
代码语言: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,这个方法被很多方法调用,但是主要的两个调用方法:RPC和HTTP是以下两个方法。
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,会对接收到的交易进行处理。
处理流程:
- 交易进入接收队列pushTransactionQueue
- 验签
- 构建快照
- 处理交易processTransaction
- 处理成功的交易进pendingTransactions,这个真正的交易缓存池!!
- 从交易接收队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 有两个主要的方法:
- validate
- 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中的产易无法做到并行处理。原因是还是和区块链的非中心化特性有关。