Skip to content

Commit

Permalink
Merge pull request #1980 from web3j/nicks/issue-1966
Browse files Browse the repository at this point in the history
Adding support for EIP1559 Private Transactions
  • Loading branch information
NickSneo authored Nov 30, 2023
2 parents 9ee5b4f + c254a72 commit 4b583dd
Show file tree
Hide file tree
Showing 13 changed files with 1,098 additions and 162 deletions.
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
16 changes: 8 additions & 8 deletions besu/src/main/java/org/web3j/tx/PrivateTransactionManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,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 +74,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 +112,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 +134,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,7 +179,7 @@ public EthSendTransaction sendEIP1559Transaction(
boolean constructor)
throws IOException {
final BigInteger nonce =
besu.privGetTransactionCount(txSignService.getAddress(), privacyGroupId)
besu.privGetTransactionCount(privateTxSignService.getAddress(), privacyGroupId)
.send()
.getTransactionCount();

Expand Down Expand Up @@ -241,7 +241,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,16 @@
*/
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.Sign;
import org.web3j.crypto.SignedRawTransaction;
import org.web3j.crypto.TransactionDecoder;
import org.web3j.crypto.transaction.type.TransactionType;
import org.web3j.rlp.RlpDecoder;
import org.web3j.rlp.RlpList;
import org.web3j.rlp.RlpString;
Expand All @@ -32,23 +36,159 @@ public class PrivateTransactionDecoder {

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

if (transactionType == TransactionType.EIP1559) {
return decodePrivateTransaction1559(transaction);
}
return decodeLegacyPrivateTransaction(transaction);
}

private static TransactionType getPrivateTransactionType(final byte[] transaction) {
// Determine the type of the private transaction, similar to TransactionDecoder.
byte firstByte = transaction[0];
if (firstByte == TransactionType.EIP1559.getRlpType()) return TransactionType.EIP1559;
else return TransactionType.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();

if (values.size() == 11) {
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 RawPrivateTransaction.createTransaction(
chainId,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
gasLimit,
to,
data,
privateFrom,
privateForList,
null,
restriction);
} else {
Base64String privacyGroupId = extractBase64(values.get(9));
return RawPrivateTransaction.createTransaction(
chainId,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
gasLimit,
to,
data,
privateFrom,
null,
privacyGroupId,
restriction);
}
} else {
final Base64String privateFrom = extractBase64(values.get(11));
final Restriction restriction = extractRestriction(values.get(13));

final byte[] v =
Sign.getVFromRecId(
Numeric.toBigInt(((RlpString) values.get(8)).getBytes()).intValue());
final byte[] r =
Numeric.toBytesPadded(
Numeric.toBigInt(((RlpString) values.get(9)).getBytes()), 32);
final byte[] s =
Numeric.toBytesPadded(
Numeric.toBigInt(((RlpString) values.get(10)).getBytes()), 32);
final Sign.SignatureData signatureData = new Sign.SignatureData(v, r, s);

if (values.get(12) instanceof RlpList) {
List<Base64String> privateForList = extractBase64List(values.get(12));
return new SignedRawPrivateTransaction(
chainId,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
gasLimit,
to,
data,
signatureData,
privateFrom,
privateForList,
null,
restriction);
} else {
Base64String privacyGroupId = extractBase64(values.get(12));
return new SignedRawPrivateTransaction(
chainId,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
gasLimit,
to,
data,
signatureData,
privateFrom,
null,
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,
null,
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,
null,
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,25 @@
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.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 +58,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 =
privateTransaction.getPrivateTransaction().asRlpValues(signatureData);
final RlpList rlpList = new RlpList(values);
return RlpEncoder.encode(rlpList);
byte[] encoded = RlpEncoder.encode(rlpList);

if (privateTransaction.getType().isEip1559()) {
return ByteBuffer.allocate(encoded.length + 1)
.put(privateTransaction.getType().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(
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,25 @@ public PrivateTxSignServiceImpl(Credentials credentials) {
this.credentials = credentials;
}

@Override
public byte[] sign(RawTransaction rawTransaction, long chainId) {
if (!(rawTransaction instanceof RawPrivateTransaction)) {
throw new RuntimeException("Can only sign RawPrivateTransaction");
public byte[] sign(RawTransaction privateTransaction, long chainId) {
if (!(privateTransaction instanceof RawPrivateTransaction)) {
throw new RuntimeException("Can only sign LegacyPrivateTransaction");
}

final byte[] signedMessage;

if (chainId > ChainId.NONE) {
signedMessage =
PrivateTransactionEncoder.signMessage(
(RawPrivateTransaction) rawTransaction, chainId, credentials);
(RawPrivateTransaction) privateTransaction, chainId, credentials);
} else {
signedMessage =
PrivateTransactionEncoder.signMessage(
(RawPrivateTransaction) rawTransaction, credentials);
(RawPrivateTransaction) privateTransaction, credentials);
}
return signedMessage;
}

@Override
public String getAddress() {
return credentials.getAddress();
}
Expand Down
Loading

0 comments on commit 4b583dd

Please sign in to comment.