前言

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

主要角色

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

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

tron网络主要主色

使用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交易,代码入口,整个流程比较长,这里看关键部份,有兴趣的小伙伴推荐拉下整个项目看一下。

构建交易

sendCoin代码入口

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

  1. rpcCli.createTransaction2: 调用gRPC访问FullNode构建交易基础信息
  2. 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);
// 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: 要转入的金额

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

调用栈:

  1. WalletGrpc.invoke(Req request, io.grpc.stub.StreamObserver responseObserver)
    gRPC创建交易入口
  2. RpcApiService.createTransaction2(TransferContract request, StreamObserver responseObserver)
  3. RpcApiService.createTransactionExtention(Message request, ContractType contractType, StreamObserver responseObserver)
  4. RpcApiService.createTransactionCapsule(com.google.protobuf.Message message, ContractType contractType)
  5. Wallet.createTransactionCapsule(message, contractType);
  6. 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();
// 判断是以 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);

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) {
// 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方法

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);//Each contract may be signed with a different private key in the future.
}

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,这个方法被很多方法调用,但是主要的两个调用方法:RPCHTTP是以下两个方法。

broadcast方法

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,会对接收到的交易进行处理。

处理流程:

  1. 交易进入接收队列pushTransactionQueue
  2. 验签
  3. 构建快照
  4. 处理交易processTransaction
  5. 处理成功的交易进pendingTransactions,这个真正的交易缓存池!!
  6. 从交易接收队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
/**
* 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 交易处理

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
/**
* 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。

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

/**
* 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交易等

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();
// 执行
// 在TRX转账的场景下执行的是 TransferActuator
act.execute(context.getProgramResult().getRet());
}
}
setResultCode(context.getProgramResult());
}

所有交易的Actuator

所有交易Actuator

TRX转账交易执行的是TransferActuator

TransferActuator TRX转账交易

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

  1. validate
  2. execute

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

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 {

// 构造器注册交易类型: 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中的产易无法做到并行处理。原因是还是和区块链的非中心化特性有关。