Skip to content

Commit

Permalink
support for 1559 transaction type
Browse files Browse the repository at this point in the history
  • Loading branch information
Pana committed Jun 28, 2024
1 parent e5ce734 commit 30ada30
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 13 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# CHANGELOG

### 1.3.0

This version add support for [Conflux hardfork v2.4.0](https://doc.confluxnetwork.org/docs/general/hardforks/v2.4), including:

1. New added RPC methods: cfx_maxPriorityFeePerGas, cfx_feeHistory, cfx_getFeeBurnt
2. Support for CIP-1559 transaction

### 1.2.10

1. Fix SendTransactionError parse method to handle data is null
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ plugins {
}

group = 'io.github.conflux-chain'
version = '1.2.10' // SNAPSHOT
version = '1.3.0' // SNAPSHOT

repositories {
jcenter()
Expand Down
109 changes: 105 additions & 4 deletions src/main/java/conflux/web3j/types/RawTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.nio.ByteBuffer;
import java.util.Arrays;

import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.Sign;
Expand All @@ -18,8 +21,8 @@

public class RawTransaction {

private static AtomicReference<BigInteger> DefaultGasPrice = new AtomicReference<BigInteger>(BigInteger.ONE);
private static AtomicReference<BigInteger> DefaultChainId = new AtomicReference<BigInteger>(BigInteger.valueOf(1029));
private static final AtomicReference<BigInteger> DefaultGasPrice = new AtomicReference<BigInteger>(BigInteger.ONE);
private static final AtomicReference<BigInteger> DefaultChainId = new AtomicReference<BigInteger>(BigInteger.valueOf(1029));

public static BigInteger getDefaultGasPrice() {
return DefaultGasPrice.get();
Expand All @@ -37,15 +40,26 @@ public static void setDefaultChainId(BigInteger defaultChainId) {
DefaultChainId.set(defaultChainId);
}

public static BigInteger TYPE_LEGACY = new BigInteger("0");
public static BigInteger TYPE_2930 = new BigInteger("1");
public static BigInteger TYPE_1559 = new BigInteger("2");

private static final byte[] TYPE_2930_PREFIX = {99, 102, 120, 1}; // cfx + 1
private static final byte[] TYPE_1559_PREFIX = {99, 102, 120, 2}; // cfx + 2

private BigInteger type;
private BigInteger nonce;
private BigInteger gasPrice;
private BigInteger maxPriorityFeePerGas;
private BigInteger maxFeePerGas;
private BigInteger gas;
private Address to;
private BigInteger value;
private BigInteger storageLimit;
private BigInteger epochHeight;
private BigInteger chainId;
private String data;
private List<AccessListEntry> accessList;

// Note default will use Mainnet chainId.
// To create a testnet, user need invoke RawTransaction.setDefaultChainId(BigInteger.ONE) before invoke the create method.
Expand All @@ -61,6 +75,7 @@ public static RawTransaction create(BigInteger nonce, BigInteger gas, Address to
tx.epochHeight = epochHeight;
tx.data = data;
tx.chainId = DefaultChainId.get();
tx.type = BigInteger.ZERO;

return tx;
}
Expand Down Expand Up @@ -96,6 +111,16 @@ public String sign(ECKeyPair ecKeyPair) {
RlpString.create(v),
RlpString.create(r),
RlpString.create(s)));

System.out.println(Numeric.toHexString(signedTx));

// add prefix for typed transaction
if (Objects.equals(this.type, RawTransaction.TYPE_1559)) {
signedTx = concat(TYPE_1559_PREFIX, signedTx);
}
if (Objects.equals(this.type, RawTransaction.TYPE_2930)) {
signedTx = concat(TYPE_2930_PREFIX, signedTx);
}

return Numeric.toHexString(signedTx);
}
Expand All @@ -104,7 +129,14 @@ public RlpType toRlp() {
List<RlpType> values = new ArrayList<RlpType>();

values.add(RlpString.create(this.nonce));
values.add(RlpString.create(this.gasPrice));

if (Objects.equals(this.type, RawTransaction.TYPE_1559)) {
values.add(RlpString.create(this.maxPriorityFeePerGas));
values.add(RlpString.create(this.maxFeePerGas));
} else {
values.add(RlpString.create(this.gasPrice));
}

values.add(RlpString.create(this.gas));

if (this.to != null) {
Expand All @@ -119,9 +151,30 @@ public RlpType toRlp() {
values.add(RlpString.create(this.chainId));
values.add(RlpString.create(Numeric.hexStringToByteArray(this.data == null ? "" : this.data)));

if (Objects.equals(this.type, RawTransaction.TYPE_1559) || Objects.equals(this.type, RawTransaction.TYPE_2930)) {
List<RlpType> rlpAccessList = new ArrayList<>();

accessList.forEach(entry -> {
List<RlpType> rlpAccessListObject = new ArrayList<>();
// add address
rlpAccessListObject.add(RlpString.create(Numeric.hexStringToByteArray(entry.getAddress().getHexAddress())));

// add storage keys
List<RlpType> keyList = new ArrayList<>();
entry.getStorageKeys().forEach(key -> {
keyList.add(RlpString.create(Numeric.hexStringToByteArray(key)));
});
rlpAccessListObject.add(new RlpList(keyList));

rlpAccessList.add(new RlpList(rlpAccessListObject));
});

values.add(new RlpList(rlpAccessList));
}

return new RlpList(values);
}

public BigInteger getNonce() {
return nonce;
}
Expand Down Expand Up @@ -194,4 +247,52 @@ public void setData(String data) {
this.data = data;
}

public BigInteger getType() {
return type;
}

public void setType(BigInteger type) {
this.type = type;
}

public BigInteger getMaxPriorityFeePerGas() {
return maxPriorityFeePerGas;
}

public void setMaxPriorityFeePerGas(BigInteger maxPriorityFeePerGas) {
this.maxPriorityFeePerGas = maxPriorityFeePerGas;
}

public BigInteger getMaxFeePerGas() {
return maxFeePerGas;
}

public void setMaxFeePerGas(BigInteger maxFeePerGas) {
this.maxFeePerGas = maxFeePerGas;
}

public List<AccessListEntry> getAccessList() {
return accessList;
}

public void setAccessList(List<AccessListEntry> accessList) {
this.accessList = accessList;
}

public static byte[] concat(byte[]... arrays) {
// Calculate the total length of the resulting array
int totalLength = Arrays.stream(arrays).mapToInt(arr -> arr.length).sum();

// Create a ByteBuffer with the total length
ByteBuffer buffer = ByteBuffer.allocate(totalLength);

// Put each byte array into the ByteBuffer
for (byte[] array : arrays) {
buffer.put(array);
}

// Return the underlying byte array
return buffer.array();
}

}
55 changes: 47 additions & 8 deletions src/main/java/conflux/web3j/types/TransactionBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.Objects;

import conflux.web3j.request.Epoch;
import conflux.web3j.response.Block;
import org.web3j.utils.Strings;

import conflux.web3j.Cfx;
Expand All @@ -12,7 +16,7 @@
public class TransactionBuilder {

public static final BigDecimal DEFAULT_GAS_OVERFLOW_RATIO = BigDecimal.valueOf(1.3);
public static final BigDecimal DEFAULT_COLLATERAL_OVERFLOW_RATIO = BigDecimal.valueOf(2);
public static final BigDecimal DEFAULT_COLLATERAL_OVERFLOW_RATIO = BigDecimal.valueOf(1.3);

private Address from;
private BigDecimal gasOverflowRatio;
Expand All @@ -38,6 +42,28 @@ public TransactionBuilder withNonce(BigInteger nonce) {
this.tx.setNonce(nonce);
return this;
}

public TransactionBuilder withType(BigInteger val) {
this.tx.setType(val);
return this;
}

public TransactionBuilder withAccessList(List<AccessListEntry> val) {
this.tx.setAccessList(val);
return this;
}

public TransactionBuilder withMaxPriorityFeePerGas(BigInteger val) {
this.tx.setType(RawTransaction.TYPE_1559);
this.tx.setMaxPriorityFeePerGas(val);
return this;
}

public TransactionBuilder withMaxFeePerGas(BigInteger val) {
this.tx.setType(RawTransaction.TYPE_1559);
this.tx.setMaxFeePerGas(val);
return this;
}

public TransactionBuilder withGasPrice(BigInteger price) {
this.tx.setGasPrice(price);
Expand Down Expand Up @@ -105,12 +131,25 @@ public RawTransaction build(Cfx cfx) {
if (this.tx.getNonce() == null) {
this.tx.setNonce(cfx.getNonce(this.from).sendAndGet());
}

if (this.tx.getGasPrice() == null) {
BigInteger gasPrice = cfx.getGasPrice().sendAndGet();
this.tx.setGasPrice(gasPrice);

if (Objects.equals(this.tx.getType(), RawTransaction.TYPE_1559)) {
if (this.tx.getMaxPriorityFeePerGas() == null) {
BigInteger maxPriorityFeePerGas = cfx.getMaxPriorityFeePerGas().sendAndGet();
this.tx.setMaxPriorityFeePerGas(maxPriorityFeePerGas);
}
if (this.tx.getMaxFeePerGas() == null) {
Block b = cfx.getBlockByEpoch(Epoch.latestState()).sendAndGet().get();

BigInteger maxFeePerGas = b.getBaseFeePerGas().multiply(new BigInteger("2")).add(this.tx.getMaxPriorityFeePerGas());
this.tx.setMaxFeePerGas(maxFeePerGas);
}
} else {
if (this.tx.getGasPrice() == null) {
BigInteger gasPrice = cfx.getGasPrice().sendAndGet();
this.tx.setGasPrice(gasPrice);
}
}

// if (this.tx.getTo() == null) {
// this.tx.setTo(null);
// }
Expand Down Expand Up @@ -156,8 +195,8 @@ private void estimateLimit(Cfx cfx) {
UsedGasAndCollateral estimation = cfx.estimateGasAndCollateral(call).sendAndGet();

if (this.tx.getGas() == null) {
BigDecimal gasLimit = new BigDecimal(estimation.getGasUsed()).multiply(this.gasOverflowRatio);
this.tx.setGas(gasLimit.toBigInteger());
// BigDecimal gasLimit = new BigDecimal(estimation.getGasUsed()).multiply(this.gasOverflowRatio);
this.tx.setGas(estimation.getGasUsed());
}

if (this.tx.getStorageLimit() == null) {
Expand Down
66 changes: 66 additions & 0 deletions src/test/java/conflux/web3j/types/RawTransactionTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package conflux.web3j.types;

//import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.web3j.rlp.RlpEncoder;
import org.web3j.utils.Numeric;

import static org.junit.jupiter.api.Assertions.assertEquals;
import java.math.BigInteger;
import java.util.Arrays;

public class RawTransactionTests {
@Test
@DisplayName("Raw2930TransactionRlp encode")
void raw2930TransactionRlpEncodeTest() {
RawTransaction tx = new RawTransaction();
tx.setType(RawTransaction.TYPE_2930);
tx.setNonce(new BigInteger("100"));
tx.setGas(new BigInteger("100"));
tx.setGasPrice(new BigInteger("100"));
tx.setChainId(new BigInteger("100"));
tx.setEpochHeight(new BigInteger("100"));
tx.setStorageLimit(new BigInteger("100"));
tx.setValue(new BigInteger("100"));
tx.setTo(new Address("0x19578cf3c71eab48cf810c78b5175d5c9e6ef441", 1));
tx.setData(Numeric.toHexString("Hello, World".getBytes()));

AccessListEntry entry = new AccessListEntry();
entry.setAddress(new CfxAddress("0x19578cf3c71eab48cf810c78b5175d5c9e6ef441", 1));
entry.setStorageKeys(Arrays.asList(new String[]{"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"}));

tx.setAccessList(Arrays.asList(new AccessListEntry[]{entry}));

byte[] encoded = RlpEncoder.encode(tx.toRlp());

assertEquals(Numeric.toHexString(encoded), "0xf8636464649419578cf3c71eab48cf810c78b5175d5c9e6ef441646464648c48656c6c6f2c20576f726c64f838f79419578cf3c71eab48cf810c78b5175d5c9e6ef441e1a01234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "");
}

@Test
@DisplayName("Raw1559TransactionRlp encode")
void raw1559TransactionRlpEncodeTest() {
RawTransaction tx = new RawTransaction();
tx.setType(RawTransaction.TYPE_1559);
tx.setNonce(new BigInteger("100"));
tx.setGas(new BigInteger("100"));
tx.setMaxPriorityFeePerGas(new BigInteger("100"));
tx.setMaxFeePerGas(new BigInteger("100"));
tx.setChainId(new BigInteger("100"));
tx.setEpochHeight(new BigInteger("100"));
tx.setStorageLimit(new BigInteger("100"));
tx.setValue(new BigInteger("100"));
tx.setTo(new Address("0x19578cf3c71eab48cf810c78b5175d5c9e6ef441", 1));
tx.setData(Numeric.toHexString("Hello, World".getBytes()));

AccessListEntry entry = new AccessListEntry();
entry.setAddress(new CfxAddress("0x19578cf3c71eab48cf810c78b5175d5c9e6ef441", 1));
entry.setStorageKeys(Arrays.asList(new String[]{"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"}));

tx.setAccessList(Arrays.asList(new AccessListEntry[]{entry}));

byte[] encoded = RlpEncoder.encode(tx.toRlp());

assertEquals(Numeric.toHexString(encoded), "0xf864646464649419578cf3c71eab48cf810c78b5175d5c9e6ef441646464648c48656c6c6f2c20576f726c64f838f79419578cf3c71eab48cf810c78b5175d5c9e6ef441e1a01234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "");
}
}

0 comments on commit 30ada30

Please sign in to comment.