Skip to content

Adding support for EIP1559 Private Transactions #1980

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion abi/src/main/java/org/web3j/abi/TypeDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,8 @@ private static <T extends Type> T decodeDynamicParameterFromStruct(
value = decodeDynamicStruct(dynamicElementData, 0, TypeReference.create(declaredField));
} else if (DynamicArray.class.isAssignableFrom(declaredField)) {
if (parameter == null) {
throw new RuntimeException("parameter can not be null, try to use annotation @Parameterized to specify the parameter type");
throw new RuntimeException(
"parameter can not be null, try to use annotation @Parameterized to specify the parameter type");
}
value =
(T)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import org.web3j.abi.datatypes.generated.Bytes32;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.eea.crypto.PrivateTransactionEncoder;
import org.web3j.protocol.eea.crypto.RawPrivateTransaction;
import org.web3j.protocol.eea.crypto.transaction.type.RawPrivateTransaction;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we talked about, I guess this package change will break backwards compatibility?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we can change the class path to original so that it doesn't break anything

import org.web3j.tx.gas.BesuPrivacyGasProvider;
import org.web3j.utils.Base64String;
import org.web3j.utils.Numeric;
Expand Down
33 changes: 17 additions & 16 deletions besu/src/main/java/org/web3j/tx/PrivateTransactionManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
import org.web3j.protocol.core.methods.response.EthGetCode;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.eea.crypto.PrivateTxSignServiceImpl;
import org.web3j.protocol.eea.crypto.RawPrivateTransaction;
import org.web3j.protocol.eea.crypto.transaction.type.PrivateTransaction1559;
import org.web3j.protocol.eea.crypto.transaction.type.RawPrivateTransaction;
import org.web3j.service.TxSignService;
import org.web3j.tx.response.TransactionReceiptProcessor;
import org.web3j.utils.Base64String;
Expand All @@ -37,7 +38,7 @@ public class PrivateTransactionManager extends TransactionManager {

private final Besu besu;

private final TxSignService txSignService;
private final TxSignService privateTxSignService;
private final long chainId;

private final Base64String privateFrom;
Expand Down Expand Up @@ -74,15 +75,15 @@ public PrivateTransactionManager(
final Base64String privateFrom,
final Base64String privacyGroupId,
final Restriction restriction,
final TxSignService txSignService) {
final TxSignService privateTxSignService) {
super(transactionReceiptProcessor, credentials.getAddress());
this.besu = besu;
this.chainId = chainId;
this.privateFrom = privateFrom;
this.privateFor = null;
this.privacyGroupId = privacyGroupId;
this.restriction = restriction;
this.txSignService = txSignService;
this.privateTxSignService = privateTxSignService;
}

public PrivateTransactionManager(
Expand Down Expand Up @@ -112,15 +113,15 @@ public PrivateTransactionManager(
final Base64String privateFrom,
final List<Base64String> privateFor,
final Restriction restriction,
final TxSignService txSignService) {
final TxSignService privateTxSignService) {
super(transactionReceiptProcessor, credentials.getAddress());
this.besu = besu;
this.chainId = chainId;
this.privateFrom = privateFrom;
this.privateFor = privateFor;
this.privacyGroupId = PrivacyGroupUtils.generateLegacyGroup(privateFrom, privateFor);
this.restriction = restriction;
this.txSignService = txSignService;
this.privateTxSignService = privateTxSignService;
}

@Override
Expand All @@ -134,7 +135,7 @@ public EthSendTransaction sendTransaction(
throws IOException {

final BigInteger nonce =
besu.privGetTransactionCount(txSignService.getAddress(), privacyGroupId)
besu.privGetTransactionCount(privateTxSignService.getAddress(), privacyGroupId)
.send()
.getTransactionCount();

Expand Down Expand Up @@ -179,34 +180,34 @@ public EthSendTransaction sendEIP1559Transaction(
boolean constructor)
throws IOException {
final BigInteger nonce =
besu.privGetTransactionCount(txSignService.getAddress(), privacyGroupId)
besu.privGetTransactionCount(privateTxSignService.getAddress(), privacyGroupId)
.send()
.getTransactionCount();

final RawPrivateTransaction transaction;
final PrivateTransaction1559 transaction;
if (privateFor != null) {
transaction =
RawPrivateTransaction.createTransaction(
PrivateTransaction1559.createTransaction(
chainId,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
gasLimit,
to,
data,
maxPriorityFeePerGas,
maxFeePerGas,
privateFrom,
privateFor,
restriction);
} else {
transaction =
RawPrivateTransaction.createTransaction(
PrivateTransaction1559.createTransaction(
chainId,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
gasLimit,
to,
data,
maxPriorityFeePerGas,
maxFeePerGas,
privateFrom,
privacyGroupId,
restriction);
Expand Down Expand Up @@ -241,7 +242,7 @@ public EthGetCode getCode(

public String sign(final RawPrivateTransaction rawTransaction) {

final byte[] signedMessage = txSignService.sign(rawTransaction, chainId);
final byte[] signedMessage = privateTxSignService.sign(rawTransaction, chainId);

return Numeric.toHexString(signedMessage);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.web3j.protocol.besu.Besu;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.methods.response.EthCall;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.tx.exceptions.ContractCallException;
import org.web3j.tx.response.PollingPrivateTransactionReceiptProcessor;
import org.web3j.tx.response.TransactionReceiptProcessor;
Expand Down Expand Up @@ -51,6 +52,8 @@ class PrivateTransactionManagerTest {
DefaultBlockParameter defaultBlockParameter = mock(DefaultBlockParameter.class);
EthCall response = mock(EthCall.class);

EthSendTransaction sendTransaction = mock(EthSendTransaction.class);

@Test
public void sendPrivCallTest() throws IOException {
when(response.getValue()).thenReturn("test");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@
*/
package org.web3j.protocol.eea.crypto;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.SignedRawTransaction;
import org.web3j.crypto.TransactionDecoder;
import org.web3j.protocol.eea.crypto.transaction.type.PrivateTransaction1559;
import org.web3j.protocol.eea.crypto.transaction.type.PrivateTransactionType;
import org.web3j.protocol.eea.crypto.transaction.type.RawPrivateTransaction;
import org.web3j.rlp.RlpDecoder;
import org.web3j.rlp.RlpList;
import org.web3j.rlp.RlpString;
Expand All @@ -32,23 +37,108 @@ public class PrivateTransactionDecoder {

public static RawPrivateTransaction decode(final String hexTransaction) {
final byte[] transaction = Numeric.hexStringToByteArray(hexTransaction);
final PrivateTransactionType transactionType = getPrivateTransactionType(transaction);

if (transactionType == PrivateTransactionType.PRIVATE_1559) {
return decodePrivateTransaction1559(transaction);
}
return decodeLegacyPrivateTransaction(transaction);
}

private static PrivateTransactionType getPrivateTransactionType(final byte[] transaction) {
// Determine the type of the private transaction, similar to TransactionDecoder.
byte firstByte = transaction[0];
if (firstByte == PrivateTransactionType.PRIVATE_1559.getRlpType())
return PrivateTransactionType.PRIVATE_1559;
else return PrivateTransactionType.PRIVATE_LEGACY;
}

private static RawPrivateTransaction decodePrivateTransaction1559(final byte[] transaction) {
final byte[] encodedTx = Arrays.copyOfRange(transaction, 1, transaction.length);
final RlpList rlpList = RlpDecoder.decode(encodedTx);
final RlpList temp = (RlpList) rlpList.getValues().get(0);
final List<RlpType> values = temp.getValues();

final long chainId =
((RlpString) temp.getValues().get(0)).asPositiveBigInteger().longValue();
final BigInteger nonce = ((RlpString) temp.getValues().get(1)).asPositiveBigInteger();

final BigInteger maxPriorityFeePerGas =
((RlpString) temp.getValues().get(2)).asPositiveBigInteger();
final BigInteger maxFeePerGas =
((RlpString) temp.getValues().get(3)).asPositiveBigInteger();

final BigInteger gasLimit = ((RlpString) temp.getValues().get(4)).asPositiveBigInteger();
final String to = ((RlpString) temp.getValues().get(5)).asString();
final String data = ((RlpString) temp.getValues().get(7)).asString();

final Base64String privateFrom = extractBase64(values.get(8));
final Restriction restriction = extractRestriction(values.get(10));

if (values.get(9) instanceof RlpList) {
List<Base64String> privateForList = extractBase64List(values.get(9));
return PrivateTransaction1559.createTransaction(
chainId,
nonce,
gasLimit,
to,
data,
maxPriorityFeePerGas,
maxFeePerGas,
privateFrom,
privateForList,
restriction);
} else {
Base64String privacyGroupId = extractBase64(values.get(9));
return PrivateTransaction1559.createTransaction(
chainId,
nonce,
gasLimit,
to,
data,
maxPriorityFeePerGas,
maxFeePerGas,
privateFrom,
privacyGroupId,
restriction);
}
}

private static RawPrivateTransaction decodeLegacyPrivateTransaction(final byte[] transaction) {
final RlpList rlpList = RlpDecoder.decode(transaction);
final RlpList temp = (RlpList) rlpList.getValues().get(0);
final List<RlpType> values = temp.getValues();

final RawTransaction rawTransaction = TransactionDecoder.decode(hexTransaction);
final RawTransaction rawTransaction =
TransactionDecoder.decode(Numeric.toHexString(transaction));

if (values.size() == 9) {
final Base64String privateFrom = extractBase64(values.get(6));
final Restriction restriction = extractRestriction(values.get(8));

if (values.get(7) instanceof RlpList) {
return new RawPrivateTransaction(
rawTransaction, privateFrom, extractBase64List(values.get(7)), restriction);
List<Base64String> privateForList = extractBase64List(values.get(7));
return RawPrivateTransaction.createTransaction(
rawTransaction.getNonce(),
rawTransaction.getGasPrice(),
rawTransaction.getGasLimit(),
rawTransaction.getTo(),
rawTransaction.getData(),
privateFrom,
privateForList,
restriction);
} else {
return new RawPrivateTransaction(
rawTransaction, privateFrom, extractBase64(values.get(7)), restriction);
Base64String privacyGroupId = extractBase64(values.get(7));
return RawPrivateTransaction.createTransaction(
rawTransaction.getNonce(),
rawTransaction.getGasPrice(),
rawTransaction.getGasLimit(),
rawTransaction.getTo(),
rawTransaction.getData(),
privateFrom,
privacyGroupId,
restriction);
}

} else {
final Base64String privateFrom = extractBase64(values.get(9));
final Restriction restriction = extractRestriction(values.get(11));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,26 @@
package org.web3j.protocol.eea.crypto;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import org.web3j.crypto.Credentials;
import org.web3j.crypto.Sign;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.protocol.eea.crypto.transaction.type.RawPrivateTransaction;
import org.web3j.rlp.RlpEncoder;
import org.web3j.rlp.RlpList;
import org.web3j.rlp.RlpString;
import org.web3j.rlp.RlpType;
import org.web3j.utils.Base64String;

/** Create signed RLP encoded private transaction. */
public class PrivateTransactionEncoder {

public static byte[] signMessage(
final RawPrivateTransaction rawTransaction, final Credentials credentials) {
final byte[] encodedTransaction = encode(rawTransaction);
final RawPrivateTransaction privateTransaction, final Credentials credentials) {
final byte[] encodedTransaction = encode(privateTransaction);
final Sign.SignatureData signatureData =
Sign.signMessage(encodedTransaction, credentials.getEcKeyPair());

return encode(rawTransaction, signatureData);
return encode(privateTransaction, signatureData);
}

public static byte[] signMessage(
Expand All @@ -61,35 +59,25 @@ public static byte[] encode(final RawPrivateTransaction rawTransaction, final lo
}

private static byte[] encode(
final RawPrivateTransaction rawTransaction, final Sign.SignatureData signatureData) {
final List<RlpType> values = asRlpValues(rawTransaction, signatureData);
final RawPrivateTransaction privateTransaction,
final Sign.SignatureData signatureData) {
// final List<RlpType> values = asRlpValues(rawTransaction, signatureData);
final List<RlpType> values = privateTransaction.asRlpValues(signatureData);
final RlpList rlpList = new RlpList(values);
return RlpEncoder.encode(rlpList);
byte[] encoded = RlpEncoder.encode(rlpList);

if (privateTransaction.getTransactionType().isPrivateEip1559()) {
return ByteBuffer.allocate(encoded.length + 1)
.put(privateTransaction.getTransactionType().getRlpType())
.put(encoded)
.array();
}
return encoded;
}

private static byte[] longToBytes(long x) {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.putLong(x);
return buffer.array();
}

public static List<RlpType> asRlpValues(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any issue with this being removed for backwards compatibility?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So every Tx type has its own different asRlpValues(), so it is being transferred from here.

final RawPrivateTransaction privateTransaction,
final Sign.SignatureData signatureData) {

final List<RlpType> result =
new ArrayList<>(TransactionEncoder.asRlpValues(privateTransaction, signatureData));

result.add(privateTransaction.getPrivateFrom().asRlp());

privateTransaction
.getPrivateFor()
.ifPresent(privateFor -> result.add(Base64String.unwrapListToRlp(privateFor)));

privateTransaction.getPrivacyGroupId().map(Base64String::asRlp).ifPresent(result::add);

result.add(RlpString.create(privateTransaction.getRestriction().getRestriction()));

return result;
}
}
Loading