当前位置:首页 » 《关于电脑》 » 正文

区块链solidity-使用Java访问合约的标准方式和高级技巧

2 人参与  2024年12月31日 18:02  分类 : 《关于电脑》  评论

点击全文阅读


一、如何对1个交易加速

所谓加速,是指让矿工尽早打包该事务。

对事务加速的方式即是:使用更高的gasPrice,将事务发送出去,并且必须使用原事务的nonce值。

为什么必须要使用原事务的nonce值?如果不使用原事务的nonce值,则原事务在条件达到时,仍旧会被执行。如果合约没有设计重入防护,将引入bug;即使合约设计了重入防护,也会导致无畏的gas消耗。

如果将原事务的nonce值占用,则原事务在被矿工打包时,将因为nonce已被使用而被矿工丢弃。

已知txHash,取出原nonce值 

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

// 创建返回固定值的transactionManager,可以结合上面的代码一起使用;

RawTransactionManager rawTransactionManager = new RawTransactionManager(web3j, adminCredential, chainId, queryReceiptAttempts, queryReceiptSleepDuration){

            protected BigInteger getNonce() throws IOException {

                return BigInteger.valueOf(155);

            }

        };

//将transactionManager用于创建合约类中,用该临时类创建事务,

YoloFoxProxy proxy = YoloFoxProxy.load(proxyAddress, web3j, rawTransactionManager, defaultGasProvider);

TransactionReceipt receipt = proxy.setImplementAddress(nftAddress).send();

二、如何取消1个事务

取消1个事务的原理和上面加速事务的原理类似:将被取消事务的nonce值挪做它用。

最简单的做法:给自己发送1笔1wei的转账,给予较高的gasPrice即可。

三、只有事务的哈希,事务执行失败,如何得到revert信息?

后端代码执行事务时,通常只记录了事务的hash,并定时去查询事务的receipt,如果发现receipt中事务失败了,但是却没有失败原因(revert信息),该如何获取revert信息呢?

这是来自web3j SDK中的做法:

将原事务的函数调用,按ethCall的形式执行,且在事务失败时的区块上执行,即可即时得到revert信息

获取失败事务失败原因 

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();

}

四、abi.encode/abi.decode 和 abi.encodePacked 对应的链下代码

4.1 合约代码对数据进行abi.decode,链下该如何构造数据上传?

合约代码 折叠源码

1

2

3

4

5

function _becomeImplementation(bytes memory data) public {

    //...

    (address daiJoinAddress_, address potAddress_) = abi.decode(data, (address, address));

    //...

}

如果该函数在另外的合约调用,只需要用 abi.encode对数据编码即可:

合约abi.encode 折叠源码

1

2

3

function test(address a1, address a2) public pure returns(bytes memory) {

  return abi.encode(a1,a2);

}

链下java代码对应到abi.encode:

对应的java编码方式 折叠源码

1

2

3

4

5

6

7

8

9

10

11

12

//Type的完整类型:org.web3j.abi.datatypes.Type

List<Type> typeList = new ArrayList<>();

  

//Address的完整类型:org.web3j.abi.datatypes.Address

typeList.add(new Address("0xa2bc756f63521e4Fa1d432Aab74AD29431Cb0361"));

typeList.add(new Address("0xa2bc756f63521e4Fa1d432Aab74AD29431Cb0362"));

  

//DefaultFunctionEncoder的完整类型:org.web3j.abi.DefaultFunctionEncoder

DefaultFunctionEncoder encoder = new DefaultFunctionEncoder();

  

// reslut 就是要发送到链上的 参数编码结果

String reslut = encoder.encodeParameters(typeList);

4.2 java代码对应到abi.encodePacked

常用的链下签名,链上验签的技术中,一般都用 abi.encodePacked 对数据进行打包,再使用 keccak256 对数据进行hash,之后再对hash后的数据签名

例如:

//被签名的内容,包含了 代理者,链上nonce值, 超时时间

bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));

//从签名中恢复出地址

address signatory = ecrecover(digest, v, r, s);

关于abi.encodePacked的详细说明位于这里

abi.encodePacked编码的两个特点:

非数组参数,不需要pad,即不需要在后面补0补足为32字节;数组参数,不编码数组长度,但是每个元素编码时要pad,补足为32字节;

假设链上的编码数据为:

1

2

3

//address a1,

//address[] memory addrArray, addrArray中有2个地址值

 abi.encodePacked(a1, addrArray);

对应的链下编码代码为:

链下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

//链上编码:abi.encodePacked(a1, addrArray)

Address a1 = new Address("0xa2bc756f63521e4Fa1d432Aab74AD29431Cb0361");

List<Address> addressList = new ArrayList<>();

addressList.add(new Address("0xa2bc756f63521e4Fa1d432Aab74AD29431Cb0361"));

addressList.add(new Address("0xa2bc756f63521e4Fa1d432Aab74AD29431Cb0362"));

StringBuilder result = new StringBuilder();

//参考了 org.web3j.abi.TypeEncoder.encodeAddress 的实现

//添加第1个参数

result.append(Numeric.toHexStringNoPrefix(a1.toUint().getValue().toByteArray()));

//添加第2个参数

for (Address a : addressList) {

    byte[] paddedRawValue = new byte[MAX_BYTE_LENGTH];

    byte[] rawValue = a.toUint().getValue().toByteArray();

    System.arraycopy(rawValue, 0, paddedRawValue, MAX_BYTE_LENGTH - rawValue.length, rawValue.length);

    result.append(Numeric.toHexStringNoPrefix(paddedRawValue));

}

//最终结果

String likeChainResult = result.toString();

4.3 对应keccak256的java函数

1

2

//org.web3j.crypto.Hash#sha3(java.lang.String)

String sha3(String hexInput)

五、链下签名

链下签名的应用是非常广泛的。

主要流程就是:在链下对特定数据进行签名 → 将签名数据和被签名信息发送到链上 → 链上重新合成被签名数据 → 从签名中恢复出签名者的公钥 → 从公钥计算出签名者的地址 → 检查该地址是否是特定账户的地址。

注意:【为了防止重放攻击,签名数据中必须包含nonce。如果要再安全点,把chainId也包含进来,这样就不会跨链重放攻击了】

在SunFlowerLand这款链游的调研中,就发现【链游后台为了控制用户的行为,在关键路径上全部使用链下签名控制用户的行为】。

下面我们对SunFlowerLand中一处链上验签,完成对应的链下签名代码:

链上恢复签名的代码 

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

//参与计算的变量

Bytes32 sessionId = new Bytes32(new byte[32]);

Uint256 farmId = new Uint256(0);

Uint256 deadline = new Uint256(0);

Uint256 mintId = new Uint256(0);

Address msgSender = new Address("0x00");

Credentials msgSenderCred = Credentials.create(Keys.createEcKeyPair());

//计算abi.encode

List<Type> params = new ArrayList<Type>();

params.add(mintId);

params.add(deadline);

params.add(msgSender);

params.add(sessionId);

params.add(farmId);

DefaultFunctionEncoder encoder = new DefaultFunctionEncoder();

//对应到 mintSignature

String txHash = Hash.sha3(encoder.encodeParameters(params)); //

//对数据进行签名,得到 v, r, s

Sign.SignatureData signature = Sign.signPrefixedMessage(Numeric.hexStringToByteArray(txHash), msgSenderCred.getEcKeyPair());

//将v, r, s 拼接成 bytes[]

StringBuilder tmpBuilder = new StringBuilder();

tmpBuilder.append(Numeric.toHexStringNoPrefix(signature.getV()));

tmpBuilder.append(Numeric.toHexStringNoPrefix(signature.getR()));

tmpBuilder.append(Numeric.toHexStringNoPrefix(signature.getS()));

//bytes 总是对应到 String 类型

String realSignatureData = tmpBuilder.toString(); //就是最终的签名数据

六、监听链上事件

典型的链上事件监听是创建好1个filter后,持续查询该filter对应的变化,但是这种方式在后端有可能宕机的场景下不适用。

此时,我们需要用 org.web3j.protocol.core.Ethereum#ethGetLogs 主动定时查询某段区块内指定的事件,并把查询过的最高区块高度记录下来。


点击全文阅读


本文链接:http://zhangshiyu.com/post/209900.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1