Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-1559: Fee market change for transaction gas #20618

Closed
wants to merge 84 commits into from

Conversation

i-norden
Copy link
Contributor

@i-norden i-norden commented Feb 3, 2020

This PR implements EIP-1559. The updated documentation for EIP-1559 can be found here. The magicians thread for this implementation can be found here. I'll continue to incorporate feedback from that thread in addition to the review received here. Thanks!

Edit: A mirror repo was created to track progress on this EIP with issues/PRs before switching over to a forked branch to check the CI prior to opening this PR. Those issues/PRs may be helpful in review/duplicating the code but keep in mind compartmentalization was not perfect and there have been updates on this branch since the PR has been opened.

Copy link
Member

@ligi ligi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some things that caught my eye while getting an overview

@@ -301,6 +304,18 @@ func (c *Clique) verifyHeader(chain consensus.ChainReader, header *types.Header,
return c.verifyCascadingFields(chain, header, parents)
}

// VerifyTransactions verifies a the transactions in a block do not exceed the per-transaction gas limit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// VerifyTransactions verifies a the transactions in a block do not exceed the per-transaction gas limit
// VerifyTransactions verifies that the transactions in a block do not exceed the per-transaction gas limit

@@ -169,6 +170,18 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*type
return abort, errorsOut
}

// VerifyTransactions verifies a the transactions in a block do not exceed the per-transaction gas limit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// VerifyTransactions verifies a the transactions in a block do not exceed the per-transaction gas limit
// VerifyTransactions verifies that the transactions in a block do not exceed the per-transaction gas limit

"github.com/ethereum/go-ethereum/params"

//"fmt"
//"github.com/ethereum/go-ethereum/params"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ligi making these updates now!

@fjl fjl changed the title EIP-1559 EIP-1559: Fee market change for transaction gas Feb 18, 2020
@fjl
Copy link
Contributor

fjl commented Feb 18, 2020

This will get reviewed when the EIP is through ACD.

Comment on lines 734 to 771
if header.BaseFee == nil {
err = rlp.Encode(w, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:len(header.Extra)-crypto.SignatureLength], // Yes, this will panic if extra is too short
header.MixDigest,
header.Nonce,
})
} else {
err = rlp.Encode(w, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:len(header.Extra)-crypto.SignatureLength], // Yes, this will panic if extra is too short
header.MixDigest,
header.Nonce,
header.BaseFee,
})
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this be reduced to:

Suggested change
if header.BaseFee == nil {
err = rlp.Encode(w, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:len(header.Extra)-crypto.SignatureLength], // Yes, this will panic if extra is too short
header.MixDigest,
header.Nonce,
})
} else {
err = rlp.Encode(w, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:len(header.Extra)-crypto.SignatureLength], // Yes, this will panic if extra is too short
header.MixDigest,
header.Nonce,
header.BaseFee,
})
}
interfaces := []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:len(header.Extra)-crypto.SignatureLength], // Yes, this will panic if extra is too short
header.MixDigest,
header.Nonce,
}
if header.BaseFee == nil {
inferfaces = append(interfaces, header.BaseFee
}
if err = rlp.Encode(w, interfaces); err != nil {
panic("can't encode: " + err.Error())
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the reductions/simplifications, will add these changes. There's a missing closing parenthesis on the append on line 752 so I can't commit the suggestions directly.

Comment on lines 421 to 430
if err == nil && sender != block.Coinbase() {
price := tx.GasPrice()
if price == nil {
price = new(big.Int).Add(block.BaseFee(), tx.GasPremium())
if price.Cmp(tx.FeeCap()) > 0 {
price.Set(tx.FeeCap())
}
}
ch <- getBlockPricesResult{price, nil}
return
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if err == nil && sender != block.Coinbase() {
price := tx.GasPrice()
if price == nil {
price = new(big.Int).Add(block.BaseFee(), tx.GasPremium())
if price.Cmp(tx.FeeCap()) > 0 {
price.Set(tx.FeeCap())
}
}
ch <- getBlockPricesResult{price, nil}
return
}
if err == nil && sender != block.Coinbase() {
continue
}
price := tx.GasPrice()
if price == nil {
price = new(big.Int).Add(block.BaseFee(), tx.GasPremium())
if price.Cmp(tx.FeeCap()) > 0 {
price.Set(tx.FeeCap())
}
}
ch <- getBlockPricesResult{price, nil}
return

To reduce the amount of nested if statements.

Comment on lines 454 to 467
if err == nil && sender != block.Coinbase() {
premium := tx.GasPremium()
if premium == nil {
premium = new(big.Int).Sub(tx.GasPrice(), block.BaseFee())
if premium.Cmp(common.Big0) < 0 {
premium.Set(common.Big0)
}
}
ch <- getBlockPremiumsResult{premium, nil}
return
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if err == nil && sender != block.Coinbase() {
premium := tx.GasPremium()
if premium == nil {
premium = new(big.Int).Sub(tx.GasPrice(), block.BaseFee())
if premium.Cmp(common.Big0) < 0 {
premium.Set(common.Big0)
}
}
ch <- getBlockPremiumsResult{premium, nil}
return
}
}
if err == nil && sender != block.Coinbase() {
continue
}
premium := tx.GasPremium()
if premium == nil {
premium = new(big.Int).Sub(tx.GasPrice(), block.BaseFee())
if premium.Cmp(common.Big0) < 0 {
premium.Set(common.Big0)
}
}
ch <- getBlockPremiumsResult{premium, nil}
return
}

Same here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, although the conditions need to be switched on the continue check.

if header.BaseFee == nil {
return errMissingBaseFee
}
delta := new(big.Int).Sub(new(big.Int).SetUint64(parent.GasUsed), new(big.Int).SetUint64(params.TargetGasUsed))
Copy link

@AbdelStark AbdelStark Mar 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello,
I am wondering if there is an issue on the delta computation. The EIP says Let delta = block.gas_used - TARGET_GASUSED.
I would expect to have header.GasUsed instead of parent.GasUsed . Am i missing something ?

Copy link
Contributor Author

@i-norden i-norden Mar 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for bringing this up, this highlights an issue in the spec. It should clarify that we are using the delta/gasUsed of the current header to calculate the baseFee of the next header; the parent to calculate the current. So in this function we are confirming that the baseFee provided in a header is valid by deriving it from its parent using the consensus rules and checking that they are equal.

Currently, miners use the baseFee of the header for the block they are working on to calculate the gasPrice of the EIP1559 txs they include, so a header needs its baseFee value known before its gasUsed value is resolved.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for bringing this up, this highlights an issue in the spec. It should clarify that we are using the delta/gasUsed of the current header to calculate the baseFee of the next header; the parent to calculate the current. So in this function we are confirming that the baseFee provided in a header is valid by deriving it from its parent using the consensus rules and checking that they are equal.

Currently, miners use the baseFee of the header for the block they are working on to calculate the gasPrice of the EIP1559 txs they include, so a header needs its baseFee value known before its gasUsed value is resolved.

Thanks for the clarification 👍

Copy link

@0xKiwi 0xKiwi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had time to take another look, thanks for doing this!

It's a bit large of a PR, if its possible it would be nice to make a temporary branch or fork so you can split this up into smaller PRs (if it's possible of course).

Mainly more code quality improvements, functionality wise I still have to look deeper to provide commentary there.

if header.BaseFee == nil {
rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra,
})
} else {
rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra,
header.BaseFee,
})
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if header.BaseFee == nil {
rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra,
})
} else {
rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra,
header.BaseFee,
})
}
encodedHeader := []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra,
}
if header.BaseFee == nil {
encodedHeader= append(encodedHeader, header.BaseFee)
}
rlp.Encode(hasher, encodedHeader)

Can use a similar strategy here.

Comment on lines +262 to +275
if tx.GasPrice() != nil {
if old.GasPrice().Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 {
return false, nil
}
} else {
eip1559 = true
newGasPrice := new(big.Int).Add(baseFee, tx.GasPremium())
if newGasPrice.Cmp(tx.FeeCap()) > 0 {
newGasPrice.Set(tx.FeeCap())
}
if old.GasPrice().Cmp(newGasPrice) >= 0 || threshold.Cmp(newGasPrice) > 0 {
return false, nil
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if tx.GasPrice() != nil {
if old.GasPrice().Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 {
return false, nil
}
} else {
eip1559 = true
newGasPrice := new(big.Int).Add(baseFee, tx.GasPremium())
if newGasPrice.Cmp(tx.FeeCap()) > 0 {
newGasPrice.Set(tx.FeeCap())
}
if old.GasPrice().Cmp(newGasPrice) >= 0 || threshold.Cmp(newGasPrice) > 0 {
return false, nil
}
}
if tx.GasPrice() != nil && old.GasPrice().Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 {
return false, nil
}
eip1559 = true
newGasPrice := new(big.Int).Add(baseFee, tx.GasPremium())
if newGasPrice.Cmp(tx.FeeCap()) > 0 {
newGasPrice.Set(tx.FeeCap())
}
if old.GasPrice().Cmp(newGasPrice) >= 0 || threshold.Cmp(newGasPrice) > 0 {
return false, nil
}

Lots of nested here so trying to clean it up. Would this work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Made all these suggested changes except this one as it's not quite equivalent.

Copy link
Contributor Author

@i-norden i-norden Apr 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also agree that the code could be broken up much better, apologies. If I find the time I will do that. In the original repo there are PRs/issues which follow the progress on specific sections. It's not perfect since the process was somewhat iterative, but that does provide some more structure to the commits: https://github.com/vulcanize/go-ethereum-EIP1559/pulls?q=is%3Apr+is%3Aclosed

h.Nonce,
})
}
return rlp.Encode(w, []interface{}{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar strategy as before here as well.

return rlpHash([]interface{}{
tx.data.AccountNonce,
tx.data.Price,
tx.data.GasLimit,
tx.data.Recipient,
tx.data.Amount,
tx.data.Payload,
tx.data.GasPremium,
tx.data.FeeCap,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar strategy possible here too right?

return rlpHash([]interface{}{
tx.data.AccountNonce,
tx.data.Price,
tx.data.GasLimit,
tx.data.Recipient,
tx.data.Amount,
tx.data.Payload,
tx.data.GasPremium,
tx.data.FeeCap,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here as well.

Comment on lines 169 to 192
delta := new(big.Int).Sub(new(big.Int).SetUint64(parent.GasUsed()), new(big.Int).SetUint64(params.TargetGasUsed))
mul := new(big.Int).Mul(parent.BaseFee(), delta)
div := new(big.Int).Div(mul, new(big.Int).SetUint64(params.TargetGasUsed))
div2 := new(big.Int).Div(div, new(big.Int).SetUint64(params.BaseFeeMaxChangeDenominator))
baseFee := new(big.Int).Add(parent.BaseFee(), div2)

// A valid BASEFEE is one such that abs(BASEFEE - PARENT_BASEFEE) <= max(1, PARENT_BASEFEE // BASEFEE_MAX_CHANGE_DENOMINATOR)
diff := new(big.Int).Sub(baseFee, parent.BaseFee())
neg := false
if diff.Sign() < 0 {
neg = true
diff.Neg(diff)
}
max := new(big.Int).Div(parent.BaseFee(), new(big.Int).SetUint64(params.BaseFeeMaxChangeDenominator))
if max.Cmp(common.Big1) < 0 {
max = common.Big1
}
// If derived BaseFee is not valid, restrict it within the bounds
if diff.Cmp(max) > 0 {
if neg {
max.Neg(max)
}
baseFee.Set(new(big.Int).Add(parent.BaseFee(), max))
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be possible to have a common method to do that ? This is the same code used in consensus/misc/forks.go

if st.isEIP1559 {
// block.coinbase gains (gasprice - BASEFEE) * gasused
coinBaseCredit := new(big.Int).Mul(new(big.Int).Sub(st.eip1559GasPrice, st.evm.BaseFee), new(big.Int).SetUint64(st.gasUsed()))
// If gasprice < BASEFEE (due to the fee_cap), this means that the block.coinbase loses funds from this operation;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we accept the block coinbase to lose funds from a transaction if gas price < BASEFEE because of fee cap ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vitalik thinks we should not accept. I quote : problem with allowing that is that a miner could choose someone else as coinbase and drain their money
See https://discordapp.com/channels/595666850260713488/692078615269212180/703225803143643238

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the heads up! Making the adjustment now.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW could you change the MaxGasEIP1559 to 20 million ? The EIP needs to be updated as well.
For context: https://discordapp.com/channels/595666850260713488/692078615269212180/705078808167841852

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you on the discord channel ? I am working on Besu implementation so that would be easier to sync our efforts if we could chat on discord ? Does that make sense ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated MaxGasEIP1559! Yeah I am in the discord channel, let's coordinate there.

@i-norden i-norden force-pushed the eip1559_rebase branch 5 times, most recently from 0ef5a0d to 215c210 Compare May 5, 2020 18:49
_, size, _ := s.Kind()
err := s.Decode(&tx.data)
if err == nil {
func (tx *Transaction) DecodeRLP(stream *rlp.Stream) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we've historically done in these kinds of situations, is to first attempt to decode into one, and then the other. E.g.

var newTx Eip1559Transaction 
var oldTx Transaction 
if err := s.Decode( &newTx); err != nil{
   err = s.Decode(&oldTx)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recall trying this approach originally and running into issues but it's been so long that I don't recall the exact nature of them. A quick conversion to this is yielding errors in unit tests but I need to look into it more!

@i-norden i-norden force-pushed the eip1559_rebase branch 3 times, most recently from 4345d31 to a683326 Compare August 25, 2020 07:37
@i-norden
Copy link
Contributor Author

i-norden commented Jan 4, 2021

Closing in favor of the updated version at #21971

@i-norden i-norden closed this Jan 4, 2021
@github-cerc-io github-cerc-io deleted the eip1559_rebase branch October 2, 2023 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants