Skip to content

Commit

Permalink
Eth1 RPC - Support public 1559 transactions (#836)
Browse files Browse the repository at this point in the history
- Support maxPriorityFeePerGas and maxFeePerGas json fields
- Wire in chainId to EthTransaction and PrivateTransaction variants.
- Encode/Decode 1559 transactions for public transactions
- Support eth_sendTransaction and eth_signTransaction
- Bring replay protection AT up to latest supported fork: londonBlock
  • Loading branch information
siladu authored Jul 19, 2023
1 parent 02e9c55 commit 2ef5c4c
Show file tree
Hide file tree
Showing 22 changed files with 360 additions and 61 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- Add --key-config-path as preferred alias to --key-store-path [#826](https://github.com/Consensys/web3signer/pull/826)
- Add eth_signTransaction RPC method under the eth1 subcommand [#822](https://github.com/ConsenSys/web3signer/pull/822)
- Add eth_sendTransaction RPC method under the eth1 subcommand [#835](https://github.com/Consensys/web3signer/pull/835)

- Add EIP-1559 support for eth1 public transactions for eth_sendTransaction and eth_signTransaction [#836](https://github.com/Consensys/web3signer/pull/836)

### Bugs fixed
- Support long name aliases in environment variables and YAML configuration [#825](https://github.com/Consensys/web3signer/pull/825)
Expand Down
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 @@ -53,7 +57,16 @@ public void awaitBlockContaining(final String hash) {
waitFor(() -> assertThat(eth.getTransactionReceipt(hash).isPresent()).isTrue());
} catch (final ConditionTimeoutException e) {
LOG.error("Timed out waiting for a block containing the transaction receipt hash: " + hash);
throw new RuntimeException("No receipt found for hash: " + hash);
throw new RuntimeException("No receipt found for hash: " + hash, e);
}
}

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, e);
}
}
}
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 All @@ -56,10 +59,10 @@ public void setup() {
}

@Test
public void valueTransfer() {
public void valueTransferFrontier() {
final BigInteger transferAmountWei = Convert.toWei("1.75", Unit.ETHER).toBigIntegerExact();
final BigInteger startBalance = besu.accounts().balance(RECIPIENT);
final Transaction transaction =
final Transaction frontierTransaction =
Transaction.createEtherTransaction(
richBenefactor().address(),
null,
Expand All @@ -68,12 +71,45 @@ public void valueTransfer() {
RECIPIENT,
transferAmountWei);

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

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 Expand Up @@ -178,7 +214,7 @@ public void multipleValueTransfers() {

@Test
public void valueTransferNonceTooLow() {
valueTransfer(); // call this test to increment the nonce
valueTransferFrontier(); // call this test to increment the nonce
final BigInteger transferAmountWei = Convert.toWei("15.5", Unit.ETHER).toBigIntegerExact();
final Transaction transaction =
Transaction.createEtherTransaction(
Expand Down
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
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 @@ -30,8 +30,6 @@ void ensureSignaturesCreatedHavePositiveValues() {
// transaction being signed with a given key, resulted in the transaction being rejected by
// Besu due to "INVALID SIGNATURE" - ultimately, it was came down to byte[] --> BigInt resulting
// in negative value.
final long chainId = 44844;

final EthSendTransactionJsonParameters txnParams =
new EthSendTransactionJsonParameters("0xf17f52151ebef6c7334fad080c5704d77216b732");
txnParams.gasPrice("0x0");
Expand All @@ -41,8 +39,8 @@ void ensureSignaturesCreatedHavePositiveValues() {
txnParams.data("0x0");
txnParams.receiver("0x627306090abaB3A6e1400e9345bC60c78a8BEf57");

final EthTransaction txn = new EthTransaction(txnParams, null, null);
final byte[] serialisedBytes = txn.rlpEncode(chainId);
final EthTransaction txn = new EthTransaction(1337L, txnParams, null, null);
final byte[] serialisedBytes = txn.rlpEncode(null);

final CredentialSigner signer =
new CredentialSigner(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,7 @@ private RequestMapper createRequestMapper(
new PassThroughHandler(transmitterFactory, jsonDecoder);

final TransactionFactory transactionFactory =
new TransactionFactory(jsonDecoder, transmitterFactory);

new TransactionFactory(chainId, jsonDecoder, transmitterFactory);
final SendTransactionHandler sendTransactionHandler =
new SendTransactionHandler(chainId, signerProvider, transactionFactory, transmitterFactory);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public class EeaSendTransactionJsonParameters {
private String data;
private Base64String privacyGroupId;
private List<Base64String> privateFor;
private BigInteger maxFeePerGas;
private BigInteger maxPriorityFeePerGas;

@JsonCreator
public EeaSendTransactionJsonParameters(
Expand Down Expand Up @@ -98,6 +100,16 @@ public void privacyGroupId(final String privacyGroupId) {
this.privacyGroupId = Base64String.wrap(privacyGroupId);
}

@JsonSetter("maxPriorityFeePerGas")
public void maxPriorityFeePerGas(final String maxPriorityFeePerGas) {
this.maxPriorityFeePerGas = decodeBigInteger(maxPriorityFeePerGas);
}

@JsonSetter("maxFeePerGas")
public void maxFeePerGas(final String maxFeePerGas) {
this.maxFeePerGas = decodeBigInteger(maxFeePerGas);
}

public Optional<String> data() {
return Optional.ofNullable(data);
}
Expand Down Expand Up @@ -142,6 +154,14 @@ public String restriction() {
return restriction;
}

public Optional<BigInteger> maxPriorityFeePerGas() {
return Optional.ofNullable(maxPriorityFeePerGas);
}

public Optional<BigInteger> maxFeePerGas() {
return Optional.ofNullable(maxFeePerGas);
}

public static EeaSendTransactionJsonParameters from(final JsonRpcRequest request) {
return fromRpcRequestToJsonParam(EeaSendTransactionJsonParameters.class, request);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public class EthSendTransactionJsonParameters {
private BigInteger value;
private String receiver;
private String data;
private BigInteger maxFeePerGas;
private BigInteger maxPriorityFeePerGas;

@JsonCreator
public EthSendTransactionJsonParameters(@JsonProperty("from") final String sender) {
Expand Down Expand Up @@ -69,6 +71,16 @@ public void data(final String data) {
this.data = data;
}

@JsonSetter("maxPriorityFeePerGas")
public void maxPriorityFeePerGas(final String maxPriorityFeePerGas) {
this.maxPriorityFeePerGas = decodeBigInteger(maxPriorityFeePerGas);
}

@JsonSetter("maxFeePerGas")
public void maxFeePerGas(final String maxFeePerGas) {
this.maxFeePerGas = decodeBigInteger(maxFeePerGas);
}

public Optional<String> data() {
return Optional.ofNullable(data);
}
Expand Down Expand Up @@ -96,4 +108,12 @@ public Optional<BigInteger> nonce() {
public String sender() {
return sender;
}

public Optional<BigInteger> maxPriorityFeePerGas() {
return Optional.ofNullable(maxPriorityFeePerGas);
}

public Optional<BigInteger> maxFeePerGas() {
return Optional.ofNullable(maxFeePerGas);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ public String createResponseResult(final JsonRpcRequest request) {
try {
ethSendTransactionJsonParameters =
fromRpcRequestToJsonParam(EthSendTransactionJsonParameters.class, request);
transaction = new EthTransaction(ethSendTransactionJsonParameters, null, request.getId());
transaction =
new EthTransaction(chainId, ethSendTransactionJsonParameters, null, request.getId());

} catch (final NumberFormatException e) {
LOG.debug("Parsing values failed for request: {}", request.getParams(), e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
public class BesuPrivateTransaction extends PrivateTransaction {

public static BesuPrivateTransaction from(
final long chainId,
final EeaSendTransactionJsonParameters transactionJsonParameters,
final NonceProvider nonceProvider,
final JsonRpcRequestId id) {
Expand All @@ -32,17 +33,19 @@ public static BesuPrivateTransaction from(
}

final Base64String privacyId = transactionJsonParameters.privacyGroupId().get();
return new BesuPrivateTransaction(transactionJsonParameters, nonceProvider, id, privacyId);
return new BesuPrivateTransaction(
chainId, transactionJsonParameters, nonceProvider, id, privacyId);
}

private final Base64String privacyGroupId;

private BesuPrivateTransaction(
final long chainId,
final EeaSendTransactionJsonParameters transactionJsonParameters,
final NonceProvider nonceProvider,
final JsonRpcRequestId id,
final Base64String privacyGroupId) {
super(transactionJsonParameters, nonceProvider, id);
super(chainId, transactionJsonParameters, nonceProvider, id);
this.privacyGroupId = privacyGroupId;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class EeaPrivateTransaction extends PrivateTransaction {
private final List<Base64String> privateFor;

public static EeaPrivateTransaction from(
final long chainId,
final EeaSendTransactionJsonParameters transactionJsonParameters,
final NonceProvider nonceProvider,
final JsonRpcRequestId id) {
Expand All @@ -36,21 +37,27 @@ public static EeaPrivateTransaction from(
}

return new EeaPrivateTransaction(
transactionJsonParameters, nonceProvider, id, transactionJsonParameters.privateFor().get());
chainId,
transactionJsonParameters,
nonceProvider,
id,
transactionJsonParameters.privateFor().get());
}

private EeaPrivateTransaction(
final long chainId,
final EeaSendTransactionJsonParameters transactionJsonParameters,
final NonceProvider nonceProvider,
final JsonRpcRequestId id,
final List<Base64String> privateFor) {
super(transactionJsonParameters, nonceProvider, id);
super(chainId, transactionJsonParameters, nonceProvider, id);
this.privateFor = privateFor;
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("chainId", chainId)
.add("transactionJsonParameters", transactionJsonParameters)
.add("id", id)
.add("nonceProvider", nonceProvider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,18 @@
public class EthTransaction implements Transaction {

private static final String JSON_RPC_METHOD = "eth_sendRawTransaction";
private final long chainId;
protected final EthSendTransactionJsonParameters transactionJsonParameters;
protected final NonceProvider nonceProvider;
protected final JsonRpcRequestId id;
protected BigInteger nonce;

public EthTransaction(
final long chainId,
final EthSendTransactionJsonParameters transactionJsonParameters,
final NonceProvider nonceProvider,
final JsonRpcRequestId id) {
this.chainId = chainId;
this.transactionJsonParameters = transactionJsonParameters;
this.id = id;
this.nonceProvider = nonceProvider;
Expand Down Expand Up @@ -89,9 +92,16 @@ public JsonRpcRequestId getId() {
return id;
}

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

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("chainId", chainId)
.add("transactionJsonParameters", transactionJsonParameters)
.add("nonceProvider", nonceProvider)
.add("id", id)
Expand All @@ -100,12 +110,24 @@ public String toString() {
}

protected RawTransaction createTransaction() {
return RawTransaction.createTransaction(
nonce,
transactionJsonParameters.gasPrice().orElse(DEFAULT_GAS_PRICE),
transactionJsonParameters.gas().orElse(DEFAULT_GAS),
transactionJsonParameters.receiver().orElse(DEFAULT_TO),
transactionJsonParameters.value().orElse(DEFAULT_VALUE),
transactionJsonParameters.data().orElse(DEFAULT_DATA));
if (isEip1559()) {
return RawTransaction.createTransaction(
chainId,
nonce,
transactionJsonParameters.gas().orElse(DEFAULT_GAS),
transactionJsonParameters.receiver().orElse(DEFAULT_TO),
transactionJsonParameters.value().orElse(DEFAULT_VALUE),
transactionJsonParameters.data().orElse(DEFAULT_DATA),
transactionJsonParameters.maxPriorityFeePerGas().orElseThrow(),
transactionJsonParameters.maxFeePerGas().orElseThrow());
} else {
return RawTransaction.createTransaction(
nonce,
transactionJsonParameters.gasPrice().orElse(DEFAULT_GAS_PRICE),
transactionJsonParameters.gas().orElse(DEFAULT_GAS),
transactionJsonParameters.receiver().orElse(DEFAULT_TO),
transactionJsonParameters.value().orElse(DEFAULT_VALUE),
transactionJsonParameters.data().orElse(DEFAULT_DATA));
}
}
}
Loading

0 comments on commit 2ef5c4c

Please sign in to comment.