发布时间:2025-06-24 19:04:58 作者:北方职教升学中心 阅读量:432
如果合约没有设计重入防护,将引入bug;即使合约设计了重入防护,也会导致无畏的gas消耗。如何对1个交易加速
所谓加速,是指让矿工尽早打包该事务。监听链上事件
典型的链上事件监听是创建好1个filter后,持续查询该filter对应的变化,但是这种方式在后端有可能宕机的场景下不适用。
主要流程就是:在链下对特定数据进行签名 → 将签名数据和被签名信息发送到链上 → 链上重新合成被签名数据 → 从签名中恢复出签名者的公钥 → 从公钥计算出签名者的地址 → 检查该地址是否是特定账户的地址。监听链上事件">六、
1
2
3
4
5
6
7
String txHash = ethSendTransaction.getTransactionHash();
EthTransaction transaction = web3j.ethGetTransactionByHash(txHash).send();
if
(!transaction.getTransaction().isPresent()) {
//错误处理
}
else
{
BigInteger nonce = transaction.getTransaction().get().getNonce();
}
1
2
3
4
5
6
7
String txHash = ethSendTransaction.getTransactionHash();
EthTransaction transaction = web3j.ethGetTransactionByHash(txHash).send();
if
(!transaction.getTransaction().isPresent()) {
//错误处理
}
else
{
BigInteger nonce = transaction.getTransaction().get().getNonce();
}
如果继续使用合约生成的java类,而不把web3j代码剖开,自己合成transaction的话,可以使用下面的方法,简单得更改新事务的nonce值
返回固定nonce值的 transactionManager
1 2 3 4 5 6 7 8 9 |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// org.web3j.utils.RevertReasonExtractor#retrieveRevertReason
// data参数就是 函数及其调用参数编码后的数据
public
static
String retrieveRevertReason(TransactionReceipt transactionReceipt, String data, Web3j web3j)
throws
IOException {
if
(transactionReceipt.getBlockNumber() ==
null
) {
return
null
;
}
//这的原理是:将原事务的函数调用,按ethCall的形式执行,且在事务失败时的区块上执行,即可即时得到revert信息
return
web3j.ethCall(
Transaction.createEthCallTransaction(transactionReceipt.getFrom(), transactionReceipt.getTo(), data),
DefaultBlockParameter.valueOf(transactionReceipt.getBlockNumber()))
.send()
.getRevertReason();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// org.web3j.utils.RevertReasonExtractor#retrieveRevertReason
// data参数就是 函数及其调用参数编码后的数据
public
static
String retrieveRevertReason(TransactionReceipt transactionReceipt, String data, Web3j web3j)
throws
IOException {
if
(transactionReceipt.getBlockNumber() ==
null
) {
return
null
;
}
//这的原理是:将原事务的函数调用,按ethCall的形式执行,且在事务失败时的区块上执行,即可即时得到revert信息
return
web3j.ethCall(
Transaction.createEthCallTransaction(transactionReceipt.getFrom(), transactionReceipt.getTo(), data),
DefaultBlockParameter.valueOf(transactionReceipt.getBlockNumber()))
.send()
.getRevertReason();
}
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
function mint(
// Verification
bytes memory signature,
bytes32 sessionId,
uint256 deadline,
// Data
uint256 farmId,
uint256 mintId
) external payable isReady(farmId) returns(bool success) {
...
// sessionId 就是这里的nonce
bytes32 txHash = mintSignature(sessionId, farmId, deadline, mintId);
//就是封装了一下keccak256
require(!executed[txHash],
"SunflowerLand: Tx Executed"
);
require(verify(txHash, signature),
"SunflowerLand: Unauthorised"
);
...
}
//封装计算hash的函数
function mintSignature(
bytes32 sessionId,
uint256 farmId,
uint256 deadline,
uint256 mintId
)
private
view returns(bytes32 success) {
return
keccak256(abi.encode(mintId, deadline, _msgSender(), sessionId, farmId));
}
//从签名数据中恢复出地址
function verify(bytes32 hash, bytes memory signature)
private
view returns (bool) {
//toEthSignedMessageHash 定义在 ECDSA.sol中,为 hash 拼接了特定的头,再次计算hash
//这个功能在web3j SDK中也有对应的函数
bytes32 ethSignedHash = hash.toEthSignedMessageHash();
//recover 也定义在 ECDSA.sol中,这里使用EVM的汇编代码,完成了从签名中恢复地址的操作
return
ethSignedHash.recover(signature) == signer;
}
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
function mint(
// Verification
bytes memory signature,
bytes32 sessionId,
uint256 deadline,
// Data
uint256 farmId,
uint256 mintId
) external payable isReady(farmId) returns(bool success) {
...
// sessionId 就是这里的nonce
bytes32 txHash = mintSignature(sessionId, farmId, deadline, mintId);
//就是封装了一下keccak256
require(!executed[txHash],
"SunflowerLand: Tx Executed"
);
require(verify(txHash, signature),
"SunflowerLand: Unauthorised"
);
...
}
//封装计算hash的函数
function mintSignature(
bytes32 sessionId,
uint256 farmId,
uint256 deadline,
uint256 mintId
)
private
view returns(bytes32 success) {
return
keccak256(abi.encode(mintId, deadline, _msgSender(), sessionId, farmId));
}
//从签名数据中恢复出地址
function verify(bytes32 hash, bytes memory signature)
private
view returns (bool) {
//toEthSignedMessageHash 定义在 ECDSA.sol中,为 hash 拼接了特定的头,再次计算hash
//这个功能在web3j SDK中也有对应的函数
bytes32 ethSignedHash = hash.toEthSignedMessageHash();
//recover 也定义在 ECDSA.sol中,这里使用EVM的汇编代码,完成了从签名中恢复地址的操作
return
ethSignedHash.recover(signature) == signer;
}
结合上面说的知识点,我们完成链下签名的代码:
链下签名代码
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 |
|
三、abi.encode/abi.decode 和 abi.encodePacked 对应的链下代码
4.1 合约代码对数据进行abi.decode,链下该如何构造数据上传?
合约代码 折叠源码
1 2 3 4 5 |
|
如果该函数在另外的合约调用,只需要用 abi.encode对数据编码即可:
合约abi.encode 折叠源码
1 2 3 |
|
链下java代码对应到abi.encode:
对应的java编码方式 折叠源码
1 2 3 4 5 6 7 8 9 10 11 12 |
|
4.2 java代码对应到abi.encodePacked
常用的链下签名,链上验签的技术中,一般都用 abi.encodePacked 对数据进行打包,再使用 keccak256 对数据进行hash,之后再对hash后的数据签名
例如:
|
关于abi.encodePacked的详细说明位于这里
abi.encodePacked编码的两个特点:
- 非数组参数,不需要pad,即不需要在后面补0补足为32字节;
- 数组参数,不编码数组长度,但是每个元素编码时要pad,补足为32字节;
假设链上的编码数据为:
1 2 3 |
|
对应的链下编码代码为:
链下java编码行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
4.3 对应keccak256的java函数
1 2 |
|