[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 com.google.common.base.Preconditions.checkState;
import static org.hyperledger.besu.crypto.Hash.keccak256; 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.crypto.SECP256K1;
import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPException; import org.hyperledger.besu.ethereum.rlp.RLPException;
import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.hyperledger.besu.ethereum.rlp.RLPOutput; import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import org.hyperledger.besu.plugin.data.Quantity;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Objects; 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 gasPrice;
private final Wei gasPremium;
private final Wei feeCap;
private final long gasLimit; private final long gasLimit;
private final Optional<Address> to; 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 { public static Transaction readFrom(final RLPInput input) throws RLPException {
if (ExperimentalEIPs.eip1559Enabled) {
return readFromExperimental(input);
}
input.enterList(); input.enterList();
final Builder builder = final Builder builder =
builder() builder()
.nonce(input.readLongScalar()) .nonce(input.readLongScalar())
@ -113,11 +121,60 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return builder.signature(signature).build(); 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. * Instantiates a transaction instance.
* *
* @param nonce the nonce * @param nonce the nonce
* @param gasPrice the gas price * @param gasPrice the gas price
* @param gasPremium the gas premium
* @param feeCap the fee cap
* @param gasLimit the gas limit * @param gasLimit the gas limit
* @param to the transaction recipient * @param to the transaction recipient
* @param value the value being transferred to the 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( public Transaction(
final long nonce, final long nonce,
final Wei gasPrice, final Wei gasPrice,
final Wei gasPremium,
final Wei feeCap,
final long gasLimit, final long gasLimit,
final Optional<Address> to, final Optional<Address> to,
final Wei value, final Wei value,
@ -142,6 +201,8 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
final Optional<BigInteger> chainId) { final Optional<BigInteger> chainId) {
this.nonce = nonce; this.nonce = nonce;
this.gasPrice = gasPrice; this.gasPrice = gasPrice;
this.gasPremium = gasPremium;
this.feeCap = feeCap;
this.gasLimit = gasLimit; this.gasLimit = gasLimit;
this.to = to; this.to = to;
this.value = value; this.value = value;
@ -151,6 +212,35 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
this.chainId = chainId; 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. * Returns the transaction nonce.
* *
@ -171,6 +261,26 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return gasPrice; 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. * Returns the transaction gas limit.
* *
@ -279,7 +389,15 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
if (hashNoSignature == null) { if (hashNoSignature == null) {
hashNoSignature = hashNoSignature =
computeSenderRecoveryHash( computeSenderRecoveryHash(
nonce, gasPrice, gasLimit, to.orElse(null), value, payload, chainId); nonce,
gasPrice,
gasPremium,
feeCap,
gasLimit,
to.orElse(null),
value,
payload,
chainId);
} }
return hashNoSignature; 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.writeBytes(getTo().isPresent() ? getTo().get() : Bytes.EMPTY);
out.writeUInt256Scalar(getValue()); out.writeUInt256Scalar(getValue());
out.writeBytes(getPayload()); out.writeBytes(getPayload());
if (ExperimentalEIPs.eip1559Enabled && gasPremium != null && feeCap != null) {
out.writeUInt256Scalar(gasPremium);
out.writeUInt256Scalar(feeCap);
}
writeSignature(out); writeSignature(out);
out.endList(); out.endList();
@ -376,9 +498,31 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return getUpfrontGasCost().add(getValue()); 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( private static Bytes32 computeSenderRecoveryHash(
final long nonce, final long nonce,
final Wei gasPrice, final Wei gasPrice,
final Wei gasPremium,
final Wei feeCap,
final long gasLimit, final long gasLimit,
final Address to, final Address to,
final Wei value, 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.writeBytes(to == null ? Bytes.EMPTY : to);
out.writeUInt256Scalar(value); out.writeUInt256Scalar(value);
out.writeBytes(payload); out.writeBytes(payload);
if (ExperimentalEIPs.eip1559Enabled && gasPremium != null && feeCap != null) {
out.writeUInt256Scalar(gasPremium);
out.writeUInt256Scalar(feeCap);
}
if (chainId.isPresent()) { if (chainId.isPresent()) {
out.writeBigIntegerScalar(chainId.get()); out.writeBigIntegerScalar(chainId.get());
out.writeUInt256Scalar(UInt256.ZERO); out.writeUInt256Scalar(UInt256.ZERO);
@ -411,7 +559,9 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
final Transaction that = (Transaction) other; final Transaction that = (Transaction) other;
return this.chainId.equals(that.chainId) return this.chainId.equals(that.chainId)
&& this.gasLimit == that.gasLimit && 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.nonce == that.nonce
&& this.payload.equals(that.payload) && this.payload.equals(that.payload)
&& this.signature.equals(that.signature) && this.signature.equals(that.signature)
@ -421,7 +571,8 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
@Override @Override
public int hashCode() { 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 @Override
@ -430,6 +581,10 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
sb.append(isContractCreation() ? "ContractCreation" : "MessageCall").append("{"); sb.append(isContractCreation() ? "ContractCreation" : "MessageCall").append("{");
sb.append("nonce=").append(getNonce()).append(", "); sb.append("nonce=").append(getNonce()).append(", ");
sb.append("gasPrice=").append(getGasPrice()).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(", "); sb.append("gasLimit=").append(getGasLimit()).append(", ");
if (getTo().isPresent()) sb.append("to=").append(getTo().get()).append(", "); if (getTo().isPresent()) sb.append("to=").append(getTo().get()).append(", ");
sb.append("value=").append(getValue()).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 gasPrice;
protected Wei gasPremium;
protected Wei feeCap;
protected long gasLimit = -1L; protected long gasLimit = -1L;
protected Address to; protected Address to;
@ -476,6 +635,16 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return this; 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) { public Builder gasLimit(final long gasLimit) {
this.gasLimit = gasLimit; this.gasLimit = gasLimit;
return this; return this;
@ -515,6 +684,8 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
return new Transaction( return new Transaction(
nonce, nonce,
gasPrice, gasPrice,
gasPremium,
feeCap,
gasLimit, gasLimit,
Optional.ofNullable(to), Optional.ofNullable(to),
value, value,
@ -534,7 +705,8 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
SECP256K1.Signature computeSignature(final SECP256K1.KeyPair keys) { SECP256K1.Signature computeSignature(final SECP256K1.KeyPair keys) {
final Bytes32 hash = 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); 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) { tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought" description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files files = sourceSets.main.allJava.files
knownHash = 'nUbCiB6FbQJavvvmlnnZBAPWErD7aKyxR+Qpk24ZtrE=' knownHash = 'NBD1ERyo7NhRpiiRvFg1s5O7mulJGUAOJNldnzhD3M4='
} }
check.dependsOn('checkAPIChanges') check.dependsOn('checkAPIChanges')

@ -14,6 +14,8 @@
*/ */
package org.hyperledger.besu.plugin.data; package org.hyperledger.besu.plugin.data;
import org.hyperledger.besu.plugin.Unstable;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Optional; import java.util.Optional;
@ -53,6 +55,27 @@ public interface Transaction {
*/ */
Quantity getGasPrice(); 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 * 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 * 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 * @return the transaction payload
*/ */
Bytes getPayload(); 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