Skip to content

Commit 4b583dd

Browse files
authored
Merge pull request #1980 from web3j/nicks/issue-1966
Adding support for EIP1559 Private Transactions
2 parents 9ee5b4f + c254a72 commit 4b583dd

File tree

13 files changed

+1098
-162
lines changed

13 files changed

+1098
-162
lines changed

abi/src/main/java/org/web3j/abi/TypeDecoder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,8 @@ private static <T extends Type> T decodeDynamicParameterFromStruct(
565565
value = decodeDynamicStruct(dynamicElementData, 0, TypeReference.create(declaredField));
566566
} else if (DynamicArray.class.isAssignableFrom(declaredField)) {
567567
if (parameter == null) {
568-
throw new RuntimeException("parameter can not be null, try to use annotation @Parameterized to specify the parameter type");
568+
throw new RuntimeException(
569+
"parameter can not be null, try to use annotation @Parameterized to specify the parameter type");
569570
}
570571
value =
571572
(T)

besu/src/main/java/org/web3j/tx/PrivateTransactionManager.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public class PrivateTransactionManager extends TransactionManager {
3737

3838
private final Besu besu;
3939

40-
private final TxSignService txSignService;
40+
private final TxSignService privateTxSignService;
4141
private final long chainId;
4242

4343
private final Base64String privateFrom;
@@ -74,15 +74,15 @@ public PrivateTransactionManager(
7474
final Base64String privateFrom,
7575
final Base64String privacyGroupId,
7676
final Restriction restriction,
77-
final TxSignService txSignService) {
77+
final TxSignService privateTxSignService) {
7878
super(transactionReceiptProcessor, credentials.getAddress());
7979
this.besu = besu;
8080
this.chainId = chainId;
8181
this.privateFrom = privateFrom;
8282
this.privateFor = null;
8383
this.privacyGroupId = privacyGroupId;
8484
this.restriction = restriction;
85-
this.txSignService = txSignService;
85+
this.privateTxSignService = privateTxSignService;
8686
}
8787

8888
public PrivateTransactionManager(
@@ -112,15 +112,15 @@ public PrivateTransactionManager(
112112
final Base64String privateFrom,
113113
final List<Base64String> privateFor,
114114
final Restriction restriction,
115-
final TxSignService txSignService) {
115+
final TxSignService privateTxSignService) {
116116
super(transactionReceiptProcessor, credentials.getAddress());
117117
this.besu = besu;
118118
this.chainId = chainId;
119119
this.privateFrom = privateFrom;
120120
this.privateFor = privateFor;
121121
this.privacyGroupId = PrivacyGroupUtils.generateLegacyGroup(privateFrom, privateFor);
122122
this.restriction = restriction;
123-
this.txSignService = txSignService;
123+
this.privateTxSignService = privateTxSignService;
124124
}
125125

126126
@Override
@@ -134,7 +134,7 @@ public EthSendTransaction sendTransaction(
134134
throws IOException {
135135

136136
final BigInteger nonce =
137-
besu.privGetTransactionCount(txSignService.getAddress(), privacyGroupId)
137+
besu.privGetTransactionCount(privateTxSignService.getAddress(), privacyGroupId)
138138
.send()
139139
.getTransactionCount();
140140

@@ -179,7 +179,7 @@ public EthSendTransaction sendEIP1559Transaction(
179179
boolean constructor)
180180
throws IOException {
181181
final BigInteger nonce =
182-
besu.privGetTransactionCount(txSignService.getAddress(), privacyGroupId)
182+
besu.privGetTransactionCount(privateTxSignService.getAddress(), privacyGroupId)
183183
.send()
184184
.getTransactionCount();
185185

@@ -241,7 +241,7 @@ public EthGetCode getCode(
241241

242242
public String sign(final RawPrivateTransaction rawTransaction) {
243243

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

246246
return Numeric.toHexString(signedMessage);
247247
}

besu/src/test/java/org/web3j/tx/PrivateTransactionManagerTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.web3j.protocol.besu.Besu;
2222
import org.web3j.protocol.core.DefaultBlockParameter;
2323
import org.web3j.protocol.core.methods.response.EthCall;
24+
import org.web3j.protocol.core.methods.response.EthSendTransaction;
2425
import org.web3j.tx.exceptions.ContractCallException;
2526
import org.web3j.tx.response.PollingPrivateTransactionReceiptProcessor;
2627
import org.web3j.tx.response.TransactionReceiptProcessor;
@@ -51,6 +52,8 @@ class PrivateTransactionManagerTest {
5152
DefaultBlockParameter defaultBlockParameter = mock(DefaultBlockParameter.class);
5253
EthCall response = mock(EthCall.class);
5354

55+
EthSendTransaction sendTransaction = mock(EthSendTransaction.class);
56+
5457
@Test
5558
public void sendPrivCallTest() throws IOException {
5659
when(response.getValue()).thenReturn("test");

eea/src/main/java/org/web3j/protocol/eea/crypto/PrivateTransactionDecoder.java

Lines changed: 146 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@
1212
*/
1313
package org.web3j.protocol.eea.crypto;
1414

15+
import java.math.BigInteger;
16+
import java.util.Arrays;
1517
import java.util.List;
1618
import java.util.stream.Collectors;
1719

1820
import org.web3j.crypto.RawTransaction;
21+
import org.web3j.crypto.Sign;
1922
import org.web3j.crypto.SignedRawTransaction;
2023
import org.web3j.crypto.TransactionDecoder;
24+
import org.web3j.crypto.transaction.type.TransactionType;
2125
import org.web3j.rlp.RlpDecoder;
2226
import org.web3j.rlp.RlpList;
2327
import org.web3j.rlp.RlpString;
@@ -32,23 +36,159 @@ public class PrivateTransactionDecoder {
3236

3337
public static RawPrivateTransaction decode(final String hexTransaction) {
3438
final byte[] transaction = Numeric.hexStringToByteArray(hexTransaction);
39+
final TransactionType transactionType = getPrivateTransactionType(transaction);
40+
41+
if (transactionType == TransactionType.EIP1559) {
42+
return decodePrivateTransaction1559(transaction);
43+
}
44+
return decodeLegacyPrivateTransaction(transaction);
45+
}
46+
47+
private static TransactionType getPrivateTransactionType(final byte[] transaction) {
48+
// Determine the type of the private transaction, similar to TransactionDecoder.
49+
byte firstByte = transaction[0];
50+
if (firstByte == TransactionType.EIP1559.getRlpType()) return TransactionType.EIP1559;
51+
else return TransactionType.LEGACY;
52+
}
53+
54+
private static RawPrivateTransaction decodePrivateTransaction1559(final byte[] transaction) {
55+
final byte[] encodedTx = Arrays.copyOfRange(transaction, 1, transaction.length);
56+
final RlpList rlpList = RlpDecoder.decode(encodedTx);
57+
final RlpList temp = (RlpList) rlpList.getValues().get(0);
58+
final List<RlpType> values = temp.getValues();
59+
60+
final long chainId =
61+
((RlpString) temp.getValues().get(0)).asPositiveBigInteger().longValue();
62+
final BigInteger nonce = ((RlpString) temp.getValues().get(1)).asPositiveBigInteger();
63+
64+
final BigInteger maxPriorityFeePerGas =
65+
((RlpString) temp.getValues().get(2)).asPositiveBigInteger();
66+
final BigInteger maxFeePerGas =
67+
((RlpString) temp.getValues().get(3)).asPositiveBigInteger();
68+
69+
final BigInteger gasLimit = ((RlpString) temp.getValues().get(4)).asPositiveBigInteger();
70+
final String to = ((RlpString) temp.getValues().get(5)).asString();
71+
final String data = ((RlpString) temp.getValues().get(7)).asString();
72+
73+
if (values.size() == 11) {
74+
final Base64String privateFrom = extractBase64(values.get(8));
75+
final Restriction restriction = extractRestriction(values.get(10));
76+
77+
if (values.get(9) instanceof RlpList) {
78+
List<Base64String> privateForList = extractBase64List(values.get(9));
79+
return RawPrivateTransaction.createTransaction(
80+
chainId,
81+
nonce,
82+
maxPriorityFeePerGas,
83+
maxFeePerGas,
84+
gasLimit,
85+
to,
86+
data,
87+
privateFrom,
88+
privateForList,
89+
null,
90+
restriction);
91+
} else {
92+
Base64String privacyGroupId = extractBase64(values.get(9));
93+
return RawPrivateTransaction.createTransaction(
94+
chainId,
95+
nonce,
96+
maxPriorityFeePerGas,
97+
maxFeePerGas,
98+
gasLimit,
99+
to,
100+
data,
101+
privateFrom,
102+
null,
103+
privacyGroupId,
104+
restriction);
105+
}
106+
} else {
107+
final Base64String privateFrom = extractBase64(values.get(11));
108+
final Restriction restriction = extractRestriction(values.get(13));
109+
110+
final byte[] v =
111+
Sign.getVFromRecId(
112+
Numeric.toBigInt(((RlpString) values.get(8)).getBytes()).intValue());
113+
final byte[] r =
114+
Numeric.toBytesPadded(
115+
Numeric.toBigInt(((RlpString) values.get(9)).getBytes()), 32);
116+
final byte[] s =
117+
Numeric.toBytesPadded(
118+
Numeric.toBigInt(((RlpString) values.get(10)).getBytes()), 32);
119+
final Sign.SignatureData signatureData = new Sign.SignatureData(v, r, s);
120+
121+
if (values.get(12) instanceof RlpList) {
122+
List<Base64String> privateForList = extractBase64List(values.get(12));
123+
return new SignedRawPrivateTransaction(
124+
chainId,
125+
nonce,
126+
maxPriorityFeePerGas,
127+
maxFeePerGas,
128+
gasLimit,
129+
to,
130+
data,
131+
signatureData,
132+
privateFrom,
133+
privateForList,
134+
null,
135+
restriction);
136+
} else {
137+
Base64String privacyGroupId = extractBase64(values.get(12));
138+
return new SignedRawPrivateTransaction(
139+
chainId,
140+
nonce,
141+
maxPriorityFeePerGas,
142+
maxFeePerGas,
143+
gasLimit,
144+
to,
145+
data,
146+
signatureData,
147+
privateFrom,
148+
null,
149+
privacyGroupId,
150+
restriction);
151+
}
152+
}
153+
}
154+
155+
private static RawPrivateTransaction decodeLegacyPrivateTransaction(final byte[] transaction) {
35156
final RlpList rlpList = RlpDecoder.decode(transaction);
36157
final RlpList temp = (RlpList) rlpList.getValues().get(0);
37158
final List<RlpType> values = temp.getValues();
38159

39-
final RawTransaction rawTransaction = TransactionDecoder.decode(hexTransaction);
160+
final RawTransaction rawTransaction =
161+
TransactionDecoder.decode(Numeric.toHexString(transaction));
40162

41163
if (values.size() == 9) {
42164
final Base64String privateFrom = extractBase64(values.get(6));
43165
final Restriction restriction = extractRestriction(values.get(8));
166+
44167
if (values.get(7) instanceof RlpList) {
45-
return new RawPrivateTransaction(
46-
rawTransaction, privateFrom, extractBase64List(values.get(7)), restriction);
168+
List<Base64String> privateForList = extractBase64List(values.get(7));
169+
return RawPrivateTransaction.createTransaction(
170+
rawTransaction.getNonce(),
171+
rawTransaction.getGasPrice(),
172+
rawTransaction.getGasLimit(),
173+
rawTransaction.getTo(),
174+
rawTransaction.getData(),
175+
privateFrom,
176+
privateForList,
177+
null,
178+
restriction);
47179
} else {
48-
return new RawPrivateTransaction(
49-
rawTransaction, privateFrom, extractBase64(values.get(7)), restriction);
180+
Base64String privacyGroupId = extractBase64(values.get(7));
181+
return RawPrivateTransaction.createTransaction(
182+
rawTransaction.getNonce(),
183+
rawTransaction.getGasPrice(),
184+
rawTransaction.getGasLimit(),
185+
rawTransaction.getTo(),
186+
rawTransaction.getData(),
187+
privateFrom,
188+
null,
189+
privacyGroupId,
190+
restriction);
50191
}
51-
52192
} else {
53193
final Base64String privateFrom = extractBase64(values.get(9));
54194
final Restriction restriction = extractRestriction(values.get(11));

eea/src/main/java/org/web3j/protocol/eea/crypto/PrivateTransactionEncoder.java

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,25 @@
1313
package org.web3j.protocol.eea.crypto;
1414

1515
import java.nio.ByteBuffer;
16-
import java.util.ArrayList;
1716
import java.util.List;
1817

1918
import org.web3j.crypto.Credentials;
2019
import org.web3j.crypto.Sign;
2120
import org.web3j.crypto.TransactionEncoder;
2221
import org.web3j.rlp.RlpEncoder;
2322
import org.web3j.rlp.RlpList;
24-
import org.web3j.rlp.RlpString;
2523
import org.web3j.rlp.RlpType;
26-
import org.web3j.utils.Base64String;
2724

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

3128
public static byte[] signMessage(
32-
final RawPrivateTransaction rawTransaction, final Credentials credentials) {
33-
final byte[] encodedTransaction = encode(rawTransaction);
29+
final RawPrivateTransaction privateTransaction, final Credentials credentials) {
30+
final byte[] encodedTransaction = encode(privateTransaction);
3431
final Sign.SignatureData signatureData =
3532
Sign.signMessage(encodedTransaction, credentials.getEcKeyPair());
3633

37-
return encode(rawTransaction, signatureData);
34+
return encode(privateTransaction, signatureData);
3835
}
3936

4037
public static byte[] signMessage(
@@ -61,35 +58,25 @@ public static byte[] encode(final RawPrivateTransaction rawTransaction, final lo
6158
}
6259

6360
private static byte[] encode(
64-
final RawPrivateTransaction rawTransaction, final Sign.SignatureData signatureData) {
65-
final List<RlpType> values = asRlpValues(rawTransaction, signatureData);
61+
final RawPrivateTransaction privateTransaction,
62+
final Sign.SignatureData signatureData) {
63+
final List<RlpType> values =
64+
privateTransaction.getPrivateTransaction().asRlpValues(signatureData);
6665
final RlpList rlpList = new RlpList(values);
67-
return RlpEncoder.encode(rlpList);
66+
byte[] encoded = RlpEncoder.encode(rlpList);
67+
68+
if (privateTransaction.getType().isEip1559()) {
69+
return ByteBuffer.allocate(encoded.length + 1)
70+
.put(privateTransaction.getType().getRlpType())
71+
.put(encoded)
72+
.array();
73+
}
74+
return encoded;
6875
}
6976

7077
private static byte[] longToBytes(long x) {
7178
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
7279
buffer.putLong(x);
7380
return buffer.array();
7481
}
75-
76-
public static List<RlpType> asRlpValues(
77-
final RawPrivateTransaction privateTransaction,
78-
final Sign.SignatureData signatureData) {
79-
80-
final List<RlpType> result =
81-
new ArrayList<>(TransactionEncoder.asRlpValues(privateTransaction, signatureData));
82-
83-
result.add(privateTransaction.getPrivateFrom().asRlp());
84-
85-
privateTransaction
86-
.getPrivateFor()
87-
.ifPresent(privateFor -> result.add(Base64String.unwrapListToRlp(privateFor)));
88-
89-
privateTransaction.getPrivacyGroupId().map(Base64String::asRlp).ifPresent(result::add);
90-
91-
result.add(RlpString.create(privateTransaction.getRestriction().getRestriction()));
92-
93-
return result;
94-
}
9582
}

eea/src/main/java/org/web3j/protocol/eea/crypto/PrivateTxSignServiceImpl.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,25 @@ public PrivateTxSignServiceImpl(Credentials credentials) {
2626
this.credentials = credentials;
2727
}
2828

29-
@Override
30-
public byte[] sign(RawTransaction rawTransaction, long chainId) {
31-
if (!(rawTransaction instanceof RawPrivateTransaction)) {
32-
throw new RuntimeException("Can only sign RawPrivateTransaction");
29+
public byte[] sign(RawTransaction privateTransaction, long chainId) {
30+
if (!(privateTransaction instanceof RawPrivateTransaction)) {
31+
throw new RuntimeException("Can only sign LegacyPrivateTransaction");
3332
}
3433

3534
final byte[] signedMessage;
3635

3736
if (chainId > ChainId.NONE) {
3837
signedMessage =
3938
PrivateTransactionEncoder.signMessage(
40-
(RawPrivateTransaction) rawTransaction, chainId, credentials);
39+
(RawPrivateTransaction) privateTransaction, chainId, credentials);
4140
} else {
4241
signedMessage =
4342
PrivateTransactionEncoder.signMessage(
44-
(RawPrivateTransaction) rawTransaction, credentials);
43+
(RawPrivateTransaction) privateTransaction, credentials);
4544
}
4645
return signedMessage;
4746
}
4847

49-
@Override
5048
public String getAddress() {
5149
return credentials.getAddress();
5250
}

0 commit comments

Comments
 (0)