Skip to content

Commit

Permalink
Encode/Decode 1559 transactions for public and private transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
siladu committed Jul 11, 2023
1 parent fdede82 commit 7258533
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
import tech.pegasys.web3signer.core.service.jsonrpc.response.JsonRpcErrorResponse;
import tech.pegasys.web3signer.dsl.signer.SignerResponse;

import java.io.IOException;
import java.util.Optional;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.awaitility.core.ConditionTimeoutException;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.protocol.exceptions.ClientConnectionException;

public class Transactions {
Expand Down Expand Up @@ -56,4 +60,13 @@ public void awaitBlockContaining(final String hash) {
throw new RuntimeException("No receipt found for hash: " + hash);
}
}

public Optional<TransactionReceipt> getTransactionReceipt(final String hash) {
try {
return eth.getTransactionReceipt(hash);
} catch (IOException e) {
LOG.error("IOException with message: " + e.getMessage());
throw new RuntimeException("No tx receipt found for hash: " + hash);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static org.assertj.core.api.Assertions.assertThat;
import static org.web3j.crypto.transaction.type.TransactionType.EIP1559;
import static tech.pegasys.web3signer.core.service.jsonrpc.response.JsonRpcError.SIGNING_FROM_IS_NOT_AN_UNLOCKED_ACCOUNT;
import static tech.pegasys.web3signer.core.service.jsonrpc.response.JsonRpcError.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE;

Expand All @@ -40,6 +41,8 @@ public class ValueTransferAcceptanceTest extends Eth1RpcAcceptanceTestBase {

private static final String RECIPIENT = "0x1b00ba00ca00bb00aa00bc00be00ac00ca00da00";
private static final long FIFTY_TRANSACTIONS = 50;
private static final String FRONTIER = "0x0";
private static final String EIP1559 = "0x2";

@BeforeEach
public void setup() {
Expand Down Expand Up @@ -74,6 +77,39 @@ public void valueTransfer() {
final BigInteger expectedEndBalance = startBalance.add(transferAmountWei);
final BigInteger actualEndBalance = besu.accounts().balance(RECIPIENT);
assertThat(actualEndBalance).isEqualTo(expectedEndBalance);

// assert tx is FRONTIER type
final var receipt = besu.transactions().getTransactionReceipt(hash).orElseThrow();
assertThat(receipt.getType()).isEqualTo(FRONTIER);
}

@Test
public void valueTransferEip1559() {
final BigInteger transferAmountWei = Convert.toWei("1.75", Unit.ETHER).toBigIntegerExact();
final BigInteger startBalance = besu.accounts().balance(RECIPIENT);
final Transaction eip1559Transaction =
new Transaction(
richBenefactor().address(),
null,
null,
INTRINSIC_GAS,
RECIPIENT,
transferAmountWei,
null,
2018L,
GAS_PRICE,
GAS_PRICE);

final String hash = signer.transactions().submit(eip1559Transaction);
besu.transactions().awaitBlockContaining(hash);

final BigInteger expectedEndBalance = startBalance.add(transferAmountWei);
final BigInteger actualEndBalance = besu.accounts().balance(RECIPIENT);
assertThat(actualEndBalance).isEqualTo(expectedEndBalance);

// assert tx is EIP1559 type
final var receipt = besu.transactions().getTransactionReceipt(hash).orElseThrow();
assertThat(receipt.getType()).isEqualTo(EIP1559);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import io.restassured.response.Response;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.nio.file.Path;

import org.jdbi.v3.core.Jdbi;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

Expand Down
1 change: 1 addition & 0 deletions acceptance-tests/src/test/resources/besu/genesis.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"constantinopleFixBlock": 0,
"londonBlock": 0,
"contractSizeLimit": 2147483647,
"ethash": {
"fixeddifficulty": 100
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ public String toString() {

@Override
protected RawPrivateTransaction createTransaction() {
if (transactionJsonParameters.maxPriorityFeePerGas().isPresent()
&& transactionJsonParameters.maxFeePerGas().isPresent()) {
if (isEip1559()) {
return RawPrivateTransaction.createTransaction(
chainId,
nonce,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ public JsonRpcRequestId getId() {
return id;
}

@Override
public boolean isEip1559() {
return transactionJsonParameters.maxPriorityFeePerGas().isPresent()
&& transactionJsonParameters.maxFeePerGas().isPresent();
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
Expand All @@ -104,8 +110,7 @@ public String toString() {
}

protected RawTransaction createTransaction() {
if (transactionJsonParameters.maxPriorityFeePerGas().isPresent()
&& transactionJsonParameters.maxFeePerGas().isPresent()) {
if (isEip1559()) {
return RawTransaction.createTransaction(
chainId,
nonce,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ public JsonRpcRequestId getId() {
return id;
}

@Override
public boolean isEip1559() {
return transactionJsonParameters.maxPriorityFeePerGas().isPresent()
&& transactionJsonParameters.maxFeePerGas().isPresent();
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,6 @@ static JsonRpcRequest jsonRpcRequest(
}

JsonRpcRequestId getId();

boolean isEip1559();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
import tech.pegasys.web3signer.signing.SecpArtifactSignature;
import tech.pegasys.web3signer.signing.secp256k1.Signature;

import java.nio.ByteBuffer;
import java.util.Optional;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
import org.web3j.crypto.Sign.SignatureData;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.crypto.transaction.type.TransactionType;
import org.web3j.utils.Numeric;

public class TransactionSerializer {
Expand All @@ -43,14 +45,44 @@ public TransactionSerializer(final ArtifactSignerProvider signer, final long cha
}

public String serialize(final Transaction transaction) {
final SignatureData signatureData =
new SignatureData(longToBytes(chainId), new byte[] {}, new byte[] {});
final byte[] bytesToSign = transaction.rlpEncode(signatureData);

Optional<String> publicKey = signerPublicKeyFromAddress(signer, transaction.sender());
SignatureData signatureData = null;

if (!transaction.isEip1559()) {
signatureData = new SignatureData(longToBytes(chainId), new byte[] {}, new byte[] {});
}

byte[] bytesToSign = transaction.rlpEncode(signatureData);

if (transaction.isEip1559()) {
bytesToSign = prependEip1559TransactionType(bytesToSign);
}

final Signature signature = sign(transaction.sender(), bytesToSign);

SignatureData web3jSignature =
new SignatureData(
signature.getV().toByteArray(),
signature.getR().toByteArray(),
signature.getS().toByteArray());

if (!transaction.isEip1559()) {
web3jSignature = TransactionEncoder.createEip155SignatureData(web3jSignature, chainId);
}

byte[] serializedBytes = transaction.rlpEncode(web3jSignature);
if (transaction.isEip1559()) {
serializedBytes = prependEip1559TransactionType(serializedBytes);
}

return Numeric.toHexString(serializedBytes);
}

private Signature sign(final String sender, final byte[] bytesToSign) {
Optional<String> publicKey = signerPublicKeyFromAddress(signer, sender);

if (publicKey.isEmpty()) {
LOG.debug("From address ({}) does not match any available account", transaction.sender());
LOG.debug("From address ({}) does not match any available account", sender);
throw new JsonRpcException(SIGNING_FROM_IS_NOT_AN_UNLOCKED_ACCOUNT);
}

Expand All @@ -63,17 +95,13 @@ public String serialize(final Transaction transaction) {
.get();

final Signature signature = artifactSignature.getSignatureData();
return signature;
}

final SignatureData web3jSignature =
new SignatureData(
signature.getV().toByteArray(),
signature.getR().toByteArray(),
signature.getS().toByteArray());

final SignatureData eip155Signature =
TransactionEncoder.createEip155SignatureData(web3jSignature, chainId);

final byte[] serializedBytes = transaction.rlpEncode(eip155Signature);
return Numeric.toHexString(serializedBytes);
private static byte[] prependEip1559TransactionType(byte[] bytesToSign) {
return ByteBuffer.allocate(bytesToSign.length + 1)
.put(TransactionType.EIP1559.getRlpType())
.put(bytesToSign)
.array();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.sendtransaction.transaction.PrivateTransaction;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.web3j.crypto.Sign.SignatureData;
import org.web3j.crypto.transaction.type.Transaction1559;
import org.web3j.crypto.transaction.type.TransactionType;
import org.web3j.protocol.eea.crypto.PrivateTransactionDecoder;
import org.web3j.protocol.eea.crypto.RawPrivateTransaction;
import org.web3j.protocol.eea.crypto.SignedRawPrivateTransaction;
import org.web3j.utils.Base64String;
import org.web3j.utils.Numeric;
Expand Down Expand Up @@ -92,6 +97,62 @@ public void rlpEncodesTransaction() {
assertThat(trimLeadingZeroes(decodedSignatureData.getS())).isEqualTo(new byte[] {3});
}

@Disabled("TODO SLD")
@Test
public void rlpEncodesEip1559Transaction() {
final EeaSendTransactionJsonParameters params =
new EeaSendTransactionJsonParameters(
"0x7577919ae5df4941180eac211965f275cdce314d",
"ZlapEsl9qDLPy/e88+/6yvCUEVIvH83y0N4A6wHuKXI=",
"restricted");
params.receiver("0xd46e8dd67c5d32be8058bb8eb970870f07244567");
params.gas("0x76c0");
params.maxPriorityFeePerGas("0x9184e72a000");
params.maxFeePerGas("0x9184e72a001");
params.value("0x0");
params.nonce("0x1");
params.data(
"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675");
params.privateFor(new String[] {"GV8m0VZAccYGAAYMBuYQtKEj0XtpXeaw2APcoBmtA2w="});

privateTransaction =
EeaPrivateTransaction.from(1337L, params, () -> BigInteger.ZERO, new JsonRpcRequestId(1));

final SignatureData signatureData = null;
final byte[] rlpEncodedBytes = privateTransaction.rlpEncode(signatureData);
final String rlpString = Numeric.toHexString(prependEip1559TransactionType(rlpEncodedBytes));

final RawPrivateTransaction decodedTransaction = PrivateTransactionDecoder.decode(rlpString);
final Transaction1559 decoded1559Transaction =
(Transaction1559) decodedTransaction.getTransaction();
assertThat(decoded1559Transaction.getTo())
.isEqualTo("0xd46e8dd67c5d32be8058bb8eb970870f07244567");
assertThat(decoded1559Transaction.getGasLimit()).isEqualTo(decodeQuantity("0x76c0"));
assertThat(decoded1559Transaction.getMaxPriorityFeePerGas())
.isEqualTo(decodeQuantity("0x9184e72a000"));
assertThat(decoded1559Transaction.getMaxFeePerGas()).isEqualTo(decodeQuantity("0x9184e72a001"));
assertThat(decoded1559Transaction.getNonce()).isEqualTo(decodeQuantity("0x1"));
assertThat(decoded1559Transaction.getValue()).isEqualTo(decodeQuantity("0x0"));
assertThat(decoded1559Transaction.getData())
.isEqualTo(
"d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675");
assertThat(decodedTransaction.getRestriction()).isEqualTo(Restriction.RESTRICTED);

final Base64String expectedDecodedPrivateFrom = params.privateFrom();
final Base64String expectedDecodedPrivateFor = params.privateFor().get().get(0);

assertThat(decodedTransaction.getPrivateFrom()).isEqualTo(expectedDecodedPrivateFrom);
assertThat(decodedTransaction.getPrivateFor().get().get(0))
.isEqualTo(expectedDecodedPrivateFor);
}

private static byte[] prependEip1559TransactionType(byte[] bytesToSign) {
return ByteBuffer.allocate(bytesToSign.length + 1)
.put(TransactionType.EIP1559.getRlpType())
.put(bytesToSign)
.array();
}

@Test
@SuppressWarnings("unchecked")
public void createsJsonEeaRequest() {
Expand Down
Loading

0 comments on commit 7258533

Please sign in to comment.