Skip to content

Commit

Permalink
Build executable heap with baseFee when London fork is enabled (#1857)
Browse files Browse the repository at this point in the history
* corrected upfront gas deduction for type-2 transactions

* solve the bug reported in EVM-804

* code optimization

* testCase fix

* added logic

* fix linting error

* refactored code

* consensus should also have baseFee and gasPrice/gasFeeCap check for legacy and dynamic tx respecitively

* linter fix

* cr fix

* don't build executable heap with baseFee

* fixed txpool e2e tests

* fixed e2e migration test

* logic for fork handler

* fix linting

* fix unit test

* added hard fork logic for PR #1849

* fixed TestE2E_TxPool_Transfer_Linear

* added e2e test case for EIP-1559 spec

* fix Test_Transition_checkDynamicFees
  • Loading branch information
rachit77 committed Sep 5, 2023
1 parent 054bef4 commit 0635afe
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 79 deletions.
6 changes: 5 additions & 1 deletion chain/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const (
EIP155 = "EIP155"
QuorumCalcAlignment = "quorumcalcalignment"
TxHashWithType = "txHashWithType"
LondonFix = "londonfix"
)

// Forks is map which contains all forks and their starting blocks from genesis
Expand Down Expand Up @@ -125,6 +126,7 @@ func (f *Forks) At(block uint64) ForksInTime {
EIP155: f.IsActive(EIP155, block),
QuorumCalcAlignment: f.IsActive(QuorumCalcAlignment, block),
TxHashWithType: f.IsActive(TxHashWithType, block),
LondonFix: f.IsActive(LondonFix, block),
}
}

Expand Down Expand Up @@ -153,7 +155,8 @@ type ForksInTime struct {
EIP158,
EIP155,
QuorumCalcAlignment,
TxHashWithType bool
TxHashWithType,
LondonFix bool
}

// AllForksEnabled should contain all supported forks by current edge version
Expand All @@ -169,4 +172,5 @@ var AllForksEnabled = &Forks{
London: NewFork(0),
QuorumCalcAlignment: NewFork(0),
TxHashWithType: NewFork(0),
LondonFix: NewFork(0),
}
120 changes: 120 additions & 0 deletions e2e-polybft/e2e/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/umbracle/ethgo"
"github.com/umbracle/ethgo/abi"
Expand Down Expand Up @@ -618,3 +619,122 @@ func TestE2E_Consensus_CustomRewardToken(t *testing.T) {
require.NoError(t, err)
require.True(t, validatorInfo.WithdrawableRewards.Cmp(big.NewInt(0)) > 0)
}

// TestE2E_Consensus_EIP1559Check sends a legacy and a dynamic tx to the cluster
// and check if balance of sender, receiver, burn contract and miner is updates correctly
// in accordance with EIP-1559 specifications
func TestE2E_Consensus_EIP1559Check(t *testing.T) {
sender1, err := wallet.GenerateKey()
require.NoError(t, err)

sender2, err := wallet.GenerateKey()
require.NoError(t, err)

recipientKey, err := wallet.GenerateKey()
require.NoError(t, err)

recipient := recipientKey.Address()

// first account should have some matics premined
cluster := framework.NewTestCluster(t, 5,
framework.WithNativeTokenConfig(fmt.Sprintf(nativeTokenMintableTestCfg, sender1.Address())),
framework.WithPremine(types.Address(sender1.Address()), types.Address(sender2.Address())),
framework.WithBurnContract(&polybft.BurnContractInfo{BlockNumber: 0, Address: types.ZeroAddress}),
)
defer cluster.Stop()

cluster.WaitForReady(t)

client := cluster.Servers[0].JSONRPC().Eth()

waitUntilBalancesChanged := func(acct ethgo.Address, bal *big.Int) error {
err := cluster.WaitUntil(30*time.Second, 2*time.Second, func() bool {
balance, err := client.GetBalance(recipient, ethgo.Latest)
if err != nil {
return true
}

return balance.Cmp(bal) > 0
})

return err
}

relayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(cluster.Servers[0].JSONRPCAddr()))
require.NoError(t, err)

// create and send tx
sendAmount := ethgo.Gwei(1)

txn := []*ethgo.Transaction{
{
Value: sendAmount,
To: &recipient,
Gas: 21000,
Nonce: uint64(0),
GasPrice: ethgo.Gwei(1).Uint64(),
},
{
Value: sendAmount,
To: &recipient,
Gas: 21000,
Nonce: uint64(0),
Type: ethgo.TransactionDynamicFee,
MaxFeePerGas: ethgo.Gwei(1),
MaxPriorityFeePerGas: ethgo.Gwei(1),
},
}

initialMinerBalance := big.NewInt(0)

var prevMiner ethgo.Address

for i := 0; i < 2; i++ {
curTxn := txn[i]

senderInitialBalance, _ := client.GetBalance(sender1.Address(), ethgo.Latest)
receiverInitialBalance, _ := client.GetBalance(recipient, ethgo.Latest)
burnContractInitialBalance, _ := client.GetBalance(ethgo.Address(types.ZeroAddress), ethgo.Latest)

receipt, err := relayer.SendTransaction(curTxn, sender1)
require.NoError(t, err)

// wait for balance to get changed
err = waitUntilBalancesChanged(recipient, receiverInitialBalance)
require.NoError(t, err)

// Retrieve the transaction receipt
txReceipt, err := client.GetTransactionByHash(receipt.TransactionHash)
require.NoError(t, err)

block, _ := client.GetBlockByHash(txReceipt.BlockHash, true)
finalMinerFinalBalance, _ := client.GetBalance(block.Miner, ethgo.Latest)

if i == 0 {
prevMiner = block.Miner
}

senderFinalBalance, _ := client.GetBalance(sender1.Address(), ethgo.Latest)
receiverFinalBalance, _ := client.GetBalance(recipient, ethgo.Latest)
burnContractFinalBalance, _ := client.GetBalance(ethgo.Address(types.ZeroAddress), ethgo.Latest)

diffReciverBalance := new(big.Int).Sub(receiverFinalBalance, receiverInitialBalance)
assert.Equal(t, sendAmount, diffReciverBalance, "Receiver balance should be increased by send amount")

if i == 1 && prevMiner != block.Miner {
initialMinerBalance = big.NewInt(0)
}

diffBurnContractBalance := new(big.Int).Sub(burnContractFinalBalance, burnContractInitialBalance)
diffSenderBalance := new(big.Int).Sub(senderInitialBalance, senderFinalBalance)
diffMinerBalance := new(big.Int).Sub(finalMinerFinalBalance, initialMinerBalance)

diffSenderBalance.Sub(diffSenderBalance, diffReciverBalance)
diffSenderBalance.Sub(diffSenderBalance, diffBurnContractBalance)
diffSenderBalance.Sub(diffSenderBalance, diffMinerBalance)

assert.Zero(t, diffSenderBalance.Int64(), "Sender balance should be decreased by send amount + gas")

initialMinerBalance = finalMinerFinalBalance
}
}
16 changes: 9 additions & 7 deletions e2e-polybft/e2e/migration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,20 @@ func TestE2E_Migration(t *testing.T) {
//send transaction to user2
sendAmount := ethgo.Gwei(10000)
receipt, err := relayer.SendTransaction(&ethgo.Transaction{
From: userAddr,
To: &userAddr2,
Gas: 1000000,
Value: sendAmount,
From: userAddr,
To: &userAddr2,
Gas: 1000000,
Value: sendAmount,
GasPrice: ethgo.Gwei(2).Uint64(),
}, userKey)
require.NoError(t, err)
require.NotNil(t, receipt)

receipt, err = relayer.SendTransaction(&ethgo.Transaction{
From: userAddr,
Gas: 1000000,
Input: contractsapi.TestWriteBlockMetadata.Bytecode,
From: userAddr,
Gas: 1000000,
GasPrice: ethgo.Gwei(2).Uint64(),
Input: contractsapi.TestWriteBlockMetadata.Bytecode,
}, userKey)
require.NoError(t, err)
require.NotNil(t, receipt)
Expand Down
25 changes: 6 additions & 19 deletions e2e-polybft/e2e/txpool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,8 @@ func TestE2E_TxPool_Transfer(t *testing.T) {
txn.MaxFeePerGas = big.NewInt(1000000000)
txn.MaxPriorityFeePerGas = big.NewInt(100000000)
} else {
gasPrice, err := client.GasPrice()
require.NoError(t, err)

txn.Type = ethgo.TransactionLegacy
txn.GasPrice = gasPrice
txn.GasPrice = ethgo.Gwei(2).Uint64()
}

sendTransaction(t, client, sender, txn)
Expand Down Expand Up @@ -115,10 +112,6 @@ func TestE2E_TxPool_Transfer_Linear(t *testing.T) {

client := cluster.Servers[0].JSONRPC().Eth()

// estimate gas price
gasPrice, err := client.GasPrice()
require.NoError(t, err)

waitUntilBalancesChanged := func(acct ethgo.Address) error {
err := cluster.WaitUntil(30*time.Second, 2*time.Second, func() bool {
balance, err := client.GetBalance(acct, ethgo.Latest)
Expand All @@ -136,10 +129,10 @@ func TestE2E_TxPool_Transfer_Linear(t *testing.T) {
if i%2 == 0 {
txn.Type = ethgo.TransactionDynamicFee
txn.MaxFeePerGas = big.NewInt(1000000000)
txn.MaxPriorityFeePerGas = big.NewInt(100000000)
txn.MaxPriorityFeePerGas = big.NewInt(1000000000)
} else {
txn.Type = ethgo.TransactionLegacy
txn.GasPrice = gasPrice
txn.GasPrice = ethgo.Gwei(1).Uint64()
}
}

Expand Down Expand Up @@ -177,11 +170,8 @@ func TestE2E_TxPool_Transfer_Linear(t *testing.T) {
populateTxFees(txn, i-1)

// Add remaining fees to finish the cycle
for j := i; j < num; j++ {
copyTxn := txn.Copy()
populateTxFees(copyTxn, j)
txn.Value = txn.Value.Add(txn.Value, txCost(copyTxn))
}
gasCostTotal := new(big.Int).Mul(txCost(txn), new(big.Int).SetInt64(int64(num-i-1)))
txn.Value = txn.Value.Add(txn.Value, gasCostTotal)

sendTransaction(t, client, receivers[i-1], txn)

Expand Down Expand Up @@ -274,11 +264,8 @@ func TestE2E_TxPool_BroadcastTransactions(t *testing.T) {
txn.MaxFeePerGas = big.NewInt(1000000000)
txn.MaxPriorityFeePerGas = big.NewInt(100000000)
} else {
gasPrice, err := client.GasPrice()
require.NoError(t, err)

txn.Type = ethgo.TransactionLegacy
txn.GasPrice = gasPrice
txn.GasPrice = ethgo.Gwei(2).Uint64()
}

sendTransaction(t, client, sender, txn)
Expand Down
5 changes: 5 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,11 @@ func initForkManager(engineName string, config *chain.Chain) error {
return err
}

// Register Handler for London fork fix
if err := state.RegisterLondonFixFork(chain.LondonFix); err != nil {
return err
}

if factory := forkManagerFactory[ConsensusType(engineName)]; factory != nil {
if err := factory(config.Params.Forks); err != nil {
return err
Expand Down
58 changes: 6 additions & 52 deletions state/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/0xPolygon/polygon-edge/chain"
"github.com/0xPolygon/polygon-edge/contracts"
"github.com/0xPolygon/polygon-edge/crypto"
"github.com/0xPolygon/polygon-edge/helper/common"
"github.com/0xPolygon/polygon-edge/state/runtime"
"github.com/0xPolygon/polygon-edge/state/runtime/addresslist"
"github.com/0xPolygon/polygon-edge/state/runtime/evm"
Expand Down Expand Up @@ -454,18 +453,7 @@ func (t *Transition) ContextPtr() *runtime.TxContext {
}

func (t *Transition) subGasLimitPrice(msg *types.Transaction) error {
upfrontGasCost := new(big.Int).SetUint64(msg.Gas)

factor := new(big.Int)
if msg.GasFeeCap != nil && msg.GasFeeCap.BitLen() > 0 {
// Apply EIP-1559 tx cost calculation factor
factor = factor.Set(msg.GasFeeCap)
} else {
// Apply legacy tx cost calculation factor
factor = factor.Set(msg.GasPrice)
}

upfrontGasCost = upfrontGasCost.Mul(upfrontGasCost, factor)
upfrontGasCost := GetLondonFixHandler(uint64(t.ctx.Number)).getUpfrontGasCost(msg, t.ctx.BaseFee)

if err := t.state.SubBalance(msg.From, upfrontGasCost); err != nil {
if errors.Is(err, runtime.ErrNotEnoughFunds) {
Expand All @@ -489,39 +477,9 @@ func (t *Transition) nonceCheck(msg *types.Transaction) error {
}

// checkDynamicFees checks correctness of the EIP-1559 feature-related fields.
// Basically, makes sure gas tip cap and gas fee cap are good.
// Basically, makes sure gas tip cap and gas fee cap are good for dynamic and legacy transactions
func (t *Transition) checkDynamicFees(msg *types.Transaction) error {
if msg.Type != types.DynamicFeeTx {
return nil
}

if msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0 {
return nil
}

if l := msg.GasFeeCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, GasFeeCap bit length: %d", ErrFeeCapVeryHigh,
msg.From.String(), l)
}

if l := msg.GasTipCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, GasTipCap bit length: %d", ErrTipVeryHigh,
msg.From.String(), l)
}

if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
return fmt.Errorf("%w: address %v, GasTipCap: %s, GasFeeCap: %s", ErrTipAboveFeeCap,
msg.From.String(), msg.GasTipCap, msg.GasFeeCap)
}

// This will panic if baseFee is nil, but basefee presence is verified
// as part of header validation.
if msg.GasFeeCap.Cmp(t.ctx.BaseFee) < 0 {
return fmt.Errorf("%w: address %v, GasFeeCap: %s, BaseFee: %s", ErrFeeCapTooLow,
msg.From.String(), msg.GasFeeCap, t.ctx.BaseFee)
}

return nil
return GetLondonFixHandler(uint64(t.ctx.Number)).checkDynamicFees(msg, t)
}

// errors that can originate in the consensus rules checks of the apply method below
Expand Down Expand Up @@ -642,13 +600,9 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er
// Define effective tip based on tx type.
// We use EIP-1559 fields of the tx if the london hardfork is enabled.
// Effective tip became to be either gas tip cap or (gas fee cap - current base fee)
effectiveTip := new(big.Int).Set(gasPrice)
if t.config.London && msg.Type == types.DynamicFeeTx {
effectiveTip = common.BigMin(
new(big.Int).Sub(msg.GasFeeCap, t.ctx.BaseFee),
new(big.Int).Set(msg.GasTipCap),
)
}
effectiveTip := GetLondonFixHandler(uint64(t.ctx.Number)).getEffectiveTip(
msg, gasPrice, t.ctx.BaseFee, t.config.London,
)

// Pay the coinbase fee as a miner reward using the calculated effective tip.
coinbaseFee := new(big.Int).Mul(new(big.Int).SetUint64(result.GasUsed), effectiveTip)
Expand Down
3 changes: 3 additions & 0 deletions state/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ func Test_Transition_checkDynamicFees(t *testing.T) {
ctx: runtime.TxContext{
BaseFee: tt.baseFee,
},
config: chain.ForksInTime{
London: true,
},
}

err := tr.checkDynamicFees(tt.tx)
Expand Down
Loading

0 comments on commit 0635afe

Please sign in to comment.