[EIP-1559] Step 2 - Apply transaction structure modification (#620)

* Added changelog entries for PR:
- https://github.com/hyperledger/besu/pull/430
- https://github.com/hyperledger/besu/pull/440

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* ### Description
BlockHeader object needs to be modified in order to add the new field (baseFee) as specified in the EIP.
We should take care about the RLP encoding/decoding of this structure since it has to include or not the new fields depending on whether we are pre fork or post fork.

- Update `core.BlockHeader.java`
    - Add `baseFee`
    - Update `readFrom` method for RLP decoding
    - Update `writeTo` method for RLP encoding
- Update `plugin.data.BlockHeader.java`
    - Add `getBaseFee` method

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* ### Description
Transaction object needs to be modified in order to add the 2 new fields (`gasPremium` and `feeCap`) as specified in the EIP.
We should take care about the RLP encoding/decoding of this structure since it has to include or not the new fields depending on whether we are pre fork or post fork.

- Update core `Transaction` object
    - Add gasPremium and feeCap fields
    - Update readFrom method for RLP decoding
    - Update writeTo method for RLP encoding
- Update plugin `Transaction` interface
    - Add `getGasPremium` and `getFeeCap` methods

This EIP introduces gasPremium and feeCap fields in the transaction. They need to be included in the signing mechanism.

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* TODO Add CLI command line flag `--Xeip1559-enabled`.

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* Remove TODO

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* spotless apply

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* Merge step 0 and make EIP-1559 specific fields optional
Add feature flag guard for RLP encoding / decoding of transactions

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* fix error

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* Address PR comments

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* update plugin api known hash

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>
pull/658/head
Abdelhamid Bakhta 5 years ago committed by GitHub
parent 2148116b32
commit 0680f5ea8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 182
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java
  2. 70
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionEIP1559Test.java
  3. 2
      plugin-api/build.gradle
  4. 43
      plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Transaction.java

@ -17,11 +17,13 @@ package org.hyperledger.besu.ethereum.core;
import static com.google.common.base.Preconditions.checkState;
import static org.hyperledger.besu.crypto.Hash.keccak256;
import org.hyperledger.besu.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPException;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import org.hyperledger.besu.plugin.data.Quantity;
import java.math.BigInteger;
import java.util.Objects;
@ -51,6 +53,10 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
private final Wei gasPrice;
private final Wei gasPremium;
private final Wei feeCap;
private final long gasLimit;
private final Optional<Address> to;
@ -80,8 +86,10 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
}
public static Transaction readFrom(final RLPInput input) throws RLPException {
if (ExperimentalEIPs.eip1559Enabled) {
return readFromExperimental(input);
}
input.enterList();
final Builder builder =
builder()
.nonce(input.readLongScalar())
@ -113,11 +121,60 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return builder.signature(signature).build();
}
public static Transaction readFromExperimental(final RLPInput input) throws RLPException {
input.enterList();
final Builder builder =
builder()
.nonce(input.readLongScalar())
.gasPrice(Wei.of(input.readUInt256Scalar()))
.gasLimit(input.readLongScalar())
.to(input.readBytes(v -> v.size() == 0 ? null : Address.wrap(v)))
.value(Wei.of(input.readUInt256Scalar()))
.payload(input.readBytes());
final Bytes maybeGasPremiumOrV = input.readBytes();
final Bytes maybeFeeCapOrR = input.readBytes();
final Bytes maybeVOrS = input.readBytes();
final BigInteger v, r, s;
// if this is the end of the list we are processing a legacy transaction
if (input.isEndOfCurrentList()) {
v = maybeGasPremiumOrV.toUnsignedBigInteger();
r = maybeFeeCapOrR.toUnsignedBigInteger();
s = maybeVOrS.toUnsignedBigInteger();
} else {
// otherwise this is an EIP-1559 transaction
builder
.gasPremium(Wei.of(maybeGasPremiumOrV.toBigInteger()))
.feeCap(Wei.of(maybeFeeCapOrR.toBigInteger()));
v = maybeVOrS.toBigInteger();
r = input.readUInt256Scalar().toBytes().toUnsignedBigInteger();
s = input.readUInt256Scalar().toBytes().toUnsignedBigInteger();
}
final byte recId;
Optional<BigInteger> chainId = Optional.empty();
if (v.equals(REPLAY_UNPROTECTED_V_BASE) || v.equals(REPLAY_UNPROTECTED_V_BASE_PLUS_1)) {
recId = v.subtract(REPLAY_UNPROTECTED_V_BASE).byteValueExact();
} else if (v.compareTo(REPLAY_PROTECTED_V_MIN) > 0) {
chainId = Optional.of(v.subtract(REPLAY_PROTECTED_V_BASE).divide(TWO));
recId = v.subtract(TWO.multiply(chainId.get()).add(REPLAY_PROTECTED_V_BASE)).byteValueExact();
} else {
throw new RuntimeException(
String.format("An unsupported encoded `v` value of %s was found", v));
}
final SECP256K1.Signature signature = SECP256K1.Signature.create(r, s, recId);
input.leaveList();
chainId.ifPresent(builder::chainId);
return builder.signature(signature).build();
}
/**
* Instantiates a transaction instance.
*
* @param nonce the nonce
* @param gasPrice the gas price
* @param gasPremium the gas premium
* @param feeCap the fee cap
* @param gasLimit the gas limit
* @param to the transaction recipient
* @param value the value being transferred to the recipient
@ -133,6 +190,8 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
public Transaction(
final long nonce,
final Wei gasPrice,
final Wei gasPremium,
final Wei feeCap,
final long gasLimit,
final Optional<Address> to,
final Wei value,
@ -142,6 +201,8 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
final Optional<BigInteger> chainId) {
this.nonce = nonce;
this.gasPrice = gasPrice;
this.gasPremium = gasPremium;
this.feeCap = feeCap;
this.gasLimit = gasLimit;
this.to = to;
this.value = value;
@ -151,6 +212,35 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
this.chainId = chainId;
}
/**
* Instantiates a transaction instance.
*
* @param nonce the nonce
* @param gasPrice the gas price
* @param gasLimit the gas limit
* @param to the transaction recipient
* @param value the value being transferred to the recipient
* @param signature the signature
* @param payload the payload
* @param sender the transaction sender
* @param chainId the chain id to apply the transaction to
* <p>The {@code to} will be an {@code Optional.empty()} for a contract creation transaction;
* otherwise it should contain an address.
* <p>The {@code chainId} must be greater than 0 to be applied to a specific chain; otherwise
*/
public Transaction(
final long nonce,
final Wei gasPrice,
final long gasLimit,
final Optional<Address> to,
final Wei value,
final SECP256K1.Signature signature,
final Bytes payload,
final Address sender,
final Optional<BigInteger> chainId) {
this(nonce, gasPrice, null, null, gasLimit, to, value, signature, payload, sender, chainId);
}
/**
* Returns the transaction nonce.
*
@ -171,6 +261,26 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return gasPrice;
}
/**
* Return the transaction gas premium.
*
* @return the transaction gas premium
*/
@Override
public Optional<Quantity> getGasPremium() {
return Optional.ofNullable(gasPremium);
}
/**
* Return the transaction fee cap.
*
* @return the transaction fee cap
*/
@Override
public Optional<Quantity> getFeeCap() {
return Optional.ofNullable(feeCap);
}
/**
* Returns the transaction gas limit.
*
@ -279,7 +389,15 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
if (hashNoSignature == null) {
hashNoSignature =
computeSenderRecoveryHash(
nonce, gasPrice, gasLimit, to.orElse(null), value, payload, chainId);
nonce,
gasPrice,
gasPremium,
feeCap,
gasLimit,
to.orElse(null),
value,
payload,
chainId);
}
return hashNoSignature;
}
@ -298,6 +416,10 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
out.writeBytes(getTo().isPresent() ? getTo().get() : Bytes.EMPTY);
out.writeUInt256Scalar(getValue());
out.writeBytes(getPayload());
if (ExperimentalEIPs.eip1559Enabled && gasPremium != null && feeCap != null) {
out.writeUInt256Scalar(gasPremium);
out.writeUInt256Scalar(feeCap);
}
writeSignature(out);
out.endList();
@ -376,9 +498,31 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return getUpfrontGasCost().add(getValue());
}
/**
* Returns whether or not the transaction is a legacy transaction.
*
* @return true if legacy transaction, false otherwise
*/
@Override
public boolean isFrontierTransaction() {
return getGasPrice() != null && (getGasPremium().isEmpty() && getFeeCap().isEmpty());
}
/**
* Returns whether or not the transaction is an EIP-1559 transaction.
*
* @return true if EIP-1559 transaction, false otherwise
*/
@Override
public boolean isEIP1559Transaction() {
return getGasPremium().isPresent() && getFeeCap().isPresent();
}
private static Bytes32 computeSenderRecoveryHash(
final long nonce,
final Wei gasPrice,
final Wei gasPremium,
final Wei feeCap,
final long gasLimit,
final Address to,
final Wei value,
@ -394,6 +538,10 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
out.writeBytes(to == null ? Bytes.EMPTY : to);
out.writeUInt256Scalar(value);
out.writeBytes(payload);
if (ExperimentalEIPs.eip1559Enabled && gasPremium != null && feeCap != null) {
out.writeUInt256Scalar(gasPremium);
out.writeUInt256Scalar(feeCap);
}
if (chainId.isPresent()) {
out.writeBigIntegerScalar(chainId.get());
out.writeUInt256Scalar(UInt256.ZERO);
@ -411,7 +559,9 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
final Transaction that = (Transaction) other;
return this.chainId.equals(that.chainId)
&& this.gasLimit == that.gasLimit
&& this.gasPrice.equals(that.gasPrice)
&& Objects.equals(this.gasPrice, that.gasPrice)
&& Objects.equals(this.gasPremium, that.gasPremium)
&& Objects.equals(this.feeCap, that.feeCap)
&& this.nonce == that.nonce
&& this.payload.equals(that.payload)
&& this.signature.equals(that.signature)
@ -421,7 +571,8 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
@Override
public int hashCode() {
return Objects.hash(nonce, gasPrice, gasLimit, to, value, payload, signature, chainId);
return Objects.hash(
nonce, gasPrice, gasPremium, feeCap, gasLimit, to, value, payload, signature, chainId);
}
@Override
@ -430,6 +581,10 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
sb.append(isContractCreation() ? "ContractCreation" : "MessageCall").append("{");
sb.append("nonce=").append(getNonce()).append(", ");
sb.append("gasPrice=").append(getGasPrice()).append(", ");
if (getGasPremium().isPresent() && getFeeCap().isPresent()) {
sb.append("gasPremium=").append(getGasPremium()).append(", ");
sb.append("feeCap=").append(getFeeCap()).append(", ");
}
sb.append("gasLimit=").append(getGasLimit()).append(", ");
if (getTo().isPresent()) sb.append("to=").append(getTo().get()).append(", ");
sb.append("value=").append(getValue()).append(", ");
@ -452,6 +607,10 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
protected Wei gasPrice;
protected Wei gasPremium;
protected Wei feeCap;
protected long gasLimit = -1L;
protected Address to;
@ -476,6 +635,16 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return this;
}
public Builder gasPremium(final Wei gasPremium) {
this.gasPremium = gasPremium;
return this;
}
public Builder feeCap(final Wei feeCap) {
this.feeCap = feeCap;
return this;
}
public Builder gasLimit(final long gasLimit) {
this.gasLimit = gasLimit;
return this;
@ -515,6 +684,8 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return new Transaction(
nonce,
gasPrice,
gasPremium,
feeCap,
gasLimit,
Optional.ofNullable(to),
value,
@ -534,7 +705,8 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
SECP256K1.Signature computeSignature(final SECP256K1.KeyPair keys) {
final Bytes32 hash =
computeSenderRecoveryHash(nonce, gasPrice, gasLimit, to, value, payload, chainId);
computeSenderRecoveryHash(
nonce, gasPrice, gasPremium, feeCap, gasLimit, to, value, payload, chainId);
return SECP256K1.sign(hash, keys);
}
}

@ -0,0 +1,70 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.core;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.lang.reflect.Field;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Test;
public class TransactionEIP1559Test {
private final RLPInput legacyRLPInput =
RLP.input(
Bytes.fromHexString(
"0xf901fc8032830138808080b901ae60056013565b6101918061001d6000396000f35b3360008190555056006001600060e060020a6000350480630a874df61461003a57806341c0e1b514610058578063a02b161e14610066578063dbbdf0831461007757005b610045600435610149565b80600160a060020a031660005260206000f35b610060610161565b60006000f35b6100716004356100d4565b60006000f35b61008560043560243561008b565b60006000f35b600054600160a060020a031632600160a060020a031614156100ac576100b1565b6100d0565b8060018360005260205260406000208190555081600060005260206000a15b5050565b600054600160a060020a031633600160a060020a031614158015610118575033600160a060020a0316600182600052602052604060002054600160a060020a031614155b61012157610126565b610146565b600060018260005260205260406000208190555080600060005260206000a15b50565b60006001826000526020526040600020549050919050565b600054600160a060020a031633600160a060020a0316146101815761018f565b600054600160a060020a0316ff5b561ca0c5689ed1ad124753d54576dfb4b571465a41900a1dff4058d8adf16f752013d0a01221cbd70ec28c94a3b55ec771bcbc70778d6ee0b51ca7ea9514594c861b1884"));
private final Wei expectedGasPremium = Wei.of(527);
private final Wei expectedFeeCap = Wei.of(369);
@Test
public void givenLegacyTransaction_assertThatRlpEncodingWorks() {
final Transaction legacyTransaction = Transaction.readFrom(legacyRLPInput);
assertThat(legacyTransaction.isFrontierTransaction()).isTrue();
assertThat(legacyTransaction.isEIP1559Transaction()).isFalse();
}
@Test
public void givenEIP1559Transaction_assertThatRlpDecodingWorks() {
ExperimentalEIPs.eip1559Enabled = true;
final Transaction legacyTransaction = Transaction.readFrom(legacyRLPInput);
set(legacyTransaction, "gasPremium", expectedGasPremium);
set(legacyTransaction, "feeCap", expectedFeeCap);
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
legacyTransaction.writeTo(rlpOutput);
final Transaction eip1559Transaction =
Transaction.readFrom(new BytesValueRLPInput(rlpOutput.encoded(), false));
assertThat(eip1559Transaction.isFrontierTransaction()).isFalse();
assertThat(eip1559Transaction.isEIP1559Transaction()).isTrue();
assertThat(eip1559Transaction.getGasPremium()).hasValue(expectedGasPremium);
assertThat(eip1559Transaction.getFeeCap()).hasValue(expectedFeeCap);
}
private void set(final Object object, final String fieldName, final Object fieldValue) {
try {
final Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, fieldValue);
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}

@ -65,7 +65,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files
knownHash = 'nUbCiB6FbQJavvvmlnnZBAPWErD7aKyxR+Qpk24ZtrE='
knownHash = 'NBD1ERyo7NhRpiiRvFg1s5O7mulJGUAOJNldnzhD3M4='
}
check.dependsOn('checkAPIChanges')

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.plugin.data;
import org.hyperledger.besu.plugin.Unstable;
import java.math.BigInteger;
import java.util.Optional;
@ -53,6 +55,27 @@ public interface Transaction {
*/
Quantity getGasPrice();
/**
* A scalar value equal to the number of Wei to be paid on top of base fee, as specified in
* EIP-1559.
*
* @return the quantity of Wei for gas premium.
*/
@Unstable
default Optional<Quantity> getGasPremium() {
return Optional.empty();
}
/**
* A scalar value equal to the number of Wei to be paid in total, as specified in EIP-1559.
*
* @return the quantity of Wei for fee cap.
*/
@Unstable
default Optional<Quantity> getFeeCap() {
return Optional.empty();
}
/**
* A scalar value equal to the maximum amount of gas that should be used in executing this
* transaction. This is paid up-front, before any computation is done and may not be increased
@ -147,4 +170,24 @@ public interface Transaction {
* @return the transaction payload
*/
Bytes getPayload();
/**
* Returns whether or not the transaction is a legacy transaction.
*
* @return true if legacy transaction, false otherwise
*/
@Unstable
default boolean isFrontierTransaction() {
return true;
}
/**
* Returns whether or not the transaction is an EIP-1559 transaction.
*
* @return true if EIP-1559 transaction, false otherwise
*/
@Unstable
default boolean isEIP1559Transaction() {
return false;
}
}

Loading…
Cancel
Save