-
Notifications
You must be signed in to change notification settings - Fork 589
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* initial commit * More skeleton code * Is this good for testing? * sync * better comment * fix * Update * add cli * fix code * ensure it doesn't go below min * export eip struct * Update smth * update * moire prints * change target to 40M to see what happens * reparam + config * Nicolas/1559 persist (#6812) * persisting to disk * better prints after testing * add max base fee * cloning everywhere to be safe * chore: add unit tests to eip code * Add a recheck bound * Cfg + adjust constants * Make a GlobalMempool * Ok testing * remove log * lint * Apply @Pipello 's code suggestions * chore: add comments to eip-1559 code (#6818) * chore: add comments to eip-1559 code * Update x/txfees/keeper/mempool-1559/code.go Co-authored-by: Roman <roman@osmosis.team> * Update x/txfees/keeper/mempool-1559/code.go Co-authored-by: Roman <roman@osmosis.team> * chore: add logger, use const instead of var * chore: remove sleep in test --------- Co-authored-by: Roman <roman@osmosis.team> * add app.toml options for all vars * Revert "add app.toml options for all vars" This reverts commit 6fa54ba. --------- Co-authored-by: Adam Tucker <adamleetucker@outlook.com> Co-authored-by: Nicolas Lara <nicolaslara@gmail.com> Co-authored-by: PaddyMc <paddymchale@hotmail.com> Co-authored-by: Roman <roman@osmosis.team>
- Loading branch information
1 parent
e67f86c
commit cadde31
Showing
13 changed files
with
813 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
eip1559state.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package mempool1559 | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
|
||
osmomath "github.com/osmosis-labs/osmosis/osmomath" | ||
) | ||
|
||
/* | ||
This is the logic for the Osmosis implementation for EIP-1559 fee market, | ||
the goal of this code is to prevent spam by charging more for transactions when the network is busy. | ||
This logic does two things: | ||
- Maintaining data parsed from chain transaction execution and updating eipState accordingly. | ||
- Resetting eipState to default every ResetInterval (1000) block height intervals to maintain consistency. | ||
Additionally: | ||
- Periodically evaluating CheckTx and RecheckTx for compliance with these parameters. | ||
Note: The reset interval is set to 1000 blocks, which is approximately 2 hours. Consider adjusting for a smaller time interval (e.g., 500 blocks = 1 hour) if necessary. | ||
Challenges: | ||
- Transactions falling under their gas bounds are currently discarded by nodes. This behavior can be modified for CheckTx, rather than RecheckTx. | ||
Global variables stored in memory: | ||
- DefaultBaseFee: Default base fee, initialized to 0.0025. | ||
- MinBaseFee: Minimum base fee, initialized to 0.0025. | ||
- MaxBaseFee: Maximum base fee, initialized to 10. | ||
- MaxBlockChangeRate: The maximum block change rate, initialized to 1/16. | ||
Global constants: | ||
- TargetGas: Gas wanted per block, initialized to 60,000,000. | ||
- ResetInterval: The interval at which eipState is reset, initialized to 1000 blocks. | ||
- BackupFile: File for backup, set to "eip1559state.json". | ||
- RecheckFeeConstant: A constant value for rechecking fees, initialized to 4. | ||
*/ | ||
|
||
var ( | ||
DefaultBaseFee = sdk.MustNewDecFromStr("0.0025") | ||
MinBaseFee = sdk.MustNewDecFromStr("0.0025") | ||
MaxBaseFee = sdk.MustNewDecFromStr("10") | ||
MaxBlockChangeRate = sdk.NewDec(1).Quo(sdk.NewDec(16)) | ||
) | ||
|
||
const ( | ||
TargetGas = int64(60_000_000) | ||
ResetInterval = int64(1000) | ||
BackupFile = "eip1559state.json" | ||
RecheckFeeConstant = int64(4) | ||
) | ||
|
||
// EipState tracks the current base fee and totalGasWantedThisBlock | ||
// this structure is never written to state | ||
type EipState struct { | ||
lastBlockHeight int64 | ||
totalGasWantedThisBlock int64 | ||
|
||
CurBaseFee osmomath.Dec `json:"cur_base_fee"` | ||
} | ||
|
||
// CurEipState is a global variable used in the BeginBlock, EndBlock and | ||
// DeliverTx (fee decorator AnteHandler) functions, it's also using when determining | ||
// if a transaction has enough gas to successfully execute | ||
var CurEipState = EipState{ | ||
lastBlockHeight: 0, | ||
totalGasWantedThisBlock: 0, | ||
CurBaseFee: sdk.NewDec(0), | ||
} | ||
|
||
// startBlock is executed at the start of each block and is responsible for reseting the state | ||
// of the CurBaseFee when the node reaches the reset interval | ||
func (e *EipState) startBlock(height int64) { | ||
e.lastBlockHeight = height | ||
e.totalGasWantedThisBlock = 0 | ||
|
||
if e.CurBaseFee.Equal(sdk.NewDec(0)) { | ||
// CurBaseFee has not been initialized yet. This only happens when the node has just started. | ||
// Try to read the previous value from the backup file and if not available, set it to the default. | ||
e.CurBaseFee = e.tryLoad() | ||
} | ||
|
||
// we reset the CurBaseFee every ResetInterval | ||
if height%ResetInterval == 0 { | ||
e.CurBaseFee = DefaultBaseFee.Clone() | ||
} | ||
} | ||
|
||
// deliverTxCode runs on every transaction in the feedecorator ante handler and sums the gas of each transaction | ||
func (e *EipState) deliverTxCode(ctx sdk.Context, tx sdk.FeeTx) { | ||
if ctx.BlockHeight() != e.lastBlockHeight { | ||
ctx.Logger().Error("Something is off here? ctx.BlockHeight() != e.lastBlockHeight", ctx.BlockHeight(), e.lastBlockHeight) | ||
} | ||
e.totalGasWantedThisBlock += int64(tx.GetGas()) | ||
} | ||
|
||
// updateBaseFee updates of a base fee in Osmosis. | ||
// It employs the following equation to calculate the new base fee: | ||
// | ||
// baseFeeMultiplier = 1 + (gasUsed - targetGas) / targetGas * maxChangeRate | ||
// newBaseFee = baseFee * baseFeeMultiplier | ||
// | ||
// updateBaseFee runs at the end of every block | ||
func (e *EipState) updateBaseFee(height int64) { | ||
if height != e.lastBlockHeight { | ||
fmt.Println("Something is off here? height != e.lastBlockHeight", height, e.lastBlockHeight) | ||
} | ||
e.lastBlockHeight = height | ||
|
||
gasUsed := e.totalGasWantedThisBlock | ||
gasDiff := gasUsed - TargetGas | ||
// (gasUsed - targetGas) / targetGas * maxChangeRate | ||
baseFeeIncrement := sdk.NewDec(gasDiff).Quo(sdk.NewDec(TargetGas)).Mul(MaxBlockChangeRate) | ||
baseFeeMultiplier := sdk.NewDec(1).Add(baseFeeIncrement) | ||
e.CurBaseFee.MulMut(baseFeeMultiplier) | ||
|
||
// Enforce the minimum base fee by resetting the CurBaseFee is it drops below the MinBaseFee | ||
if e.CurBaseFee.LT(MinBaseFee) { | ||
e.CurBaseFee = MinBaseFee.Clone() | ||
} | ||
|
||
// Enforce the maximum base fee by resetting the CurBaseFee is it goes above the MaxBaseFee | ||
if e.CurBaseFee.GT(MaxBaseFee) { | ||
e.CurBaseFee = MaxBaseFee.Clone() | ||
} | ||
|
||
go e.tryPersist() | ||
} | ||
|
||
// GetCurBaseFee returns a clone of the CurBaseFee to avoid overwriting the initial value in | ||
// the EipState, we use this in the AnteHandler to Check transactions | ||
func (e *EipState) GetCurBaseFee() osmomath.Dec { | ||
return e.CurBaseFee.Clone() | ||
} | ||
|
||
// GetCurRecheckBaseFee returns a clone of the CurBaseFee / RecheckFeeConstant to account for | ||
// rechecked transactions in the feedecorator ante handler | ||
func (e *EipState) GetCurRecheckBaseFee() osmomath.Dec { | ||
return e.CurBaseFee.Clone().Quo(sdk.NewDec(RecheckFeeConstant)) | ||
} | ||
|
||
// tryPersist persists the eip1559 state to disk in the form of a json file | ||
// we do this in case a node stops and it can continue functioning as normal | ||
func (e *EipState) tryPersist() { | ||
bz, err := json.Marshal(e) | ||
if err != nil { | ||
fmt.Println("Error marshalling eip1559 state", err) | ||
return | ||
} | ||
|
||
err = os.WriteFile(BackupFile, bz, 0644) | ||
if err != nil { | ||
fmt.Println("Error writing eip1559 state", err) | ||
return | ||
} | ||
} | ||
|
||
// tryLoad reads eip1559 state from disk and initializes the CurEipState to | ||
// the previous state when a node is restarted | ||
func (e *EipState) tryLoad() osmomath.Dec { | ||
bz, err := os.ReadFile(BackupFile) | ||
if err != nil { | ||
fmt.Println("Error reading eip1559 state", err) | ||
fmt.Println("Setting eip1559 state to default value", MinBaseFee) | ||
return MinBaseFee.Clone() | ||
} | ||
|
||
var loaded EipState | ||
err = json.Unmarshal(bz, &loaded) | ||
if err != nil { | ||
fmt.Println("Error unmarshalling eip1559 state", err) | ||
fmt.Println("Setting eip1559 state to default value", MinBaseFee) | ||
return MinBaseFee.Clone() | ||
} | ||
|
||
fmt.Println("Loaded eip1559 state. CurBaseFee=", loaded.CurBaseFee) | ||
return loaded.CurBaseFee.Clone() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package mempool1559 | ||
|
||
import ( | ||
"testing" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/tendermint/tendermint/libs/log" | ||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" | ||
"gotest.tools/assert" | ||
|
||
"github.com/osmosis-labs/osmosis/osmoutils/noapptest" | ||
) | ||
|
||
// TestUpdateBaseFee simulates the update of a base fee in Osmosis. | ||
// It employs the following equation to calculate the new base fee: | ||
// | ||
// baseFeeMultiplier = 1 + (gasUsed - targetGas) / targetGas * maxChangeRate | ||
// newBaseFee = baseFee * baseFeeMultiplier | ||
// | ||
// The function iterates through a series of simulated blocks and transactions, | ||
// updating and validating the base fee at each step to ensure it follows the equation. | ||
func TestUpdateBaseFee(t *testing.T) { | ||
// Create an instance of eipState | ||
eip := &EipState{ | ||
lastBlockHeight: 0, | ||
totalGasWantedThisBlock: 0, | ||
CurBaseFee: DefaultBaseFee.Clone(), | ||
} | ||
|
||
// we iterate over 1000 blocks as the reset happens after 1000 blocks | ||
for i := 1; i <= 1002; i++ { | ||
// create a new block | ||
ctx := sdk.NewContext(nil, tmproto.Header{Height: int64(i)}, false, log.NewNopLogger()) | ||
|
||
// start the new block | ||
eip.startBlock(int64(i)) | ||
|
||
// generate transactions | ||
if i%10 == 0 { | ||
for j := 1; j <= 3; j++ { | ||
tx := GenTx(uint64(500000000 + i)) | ||
eip.deliverTxCode(ctx, tx.(sdk.FeeTx)) | ||
} | ||
} | ||
baseFeeBeforeUpdate := eip.GetCurBaseFee() | ||
|
||
// update base fee | ||
eip.updateBaseFee(int64(i)) | ||
|
||
// calcualte the base fees | ||
expectedBaseFee := calculateBaseFee(eip.totalGasWantedThisBlock, baseFeeBeforeUpdate) | ||
|
||
// Assert that the actual result matches the expected result | ||
assert.DeepEqual(t, expectedBaseFee, eip.CurBaseFee) | ||
} | ||
} | ||
|
||
// calculateBaseFee is the same as in is defined on the eip1559 code | ||
func calculateBaseFee(totalGasWantedThisBlock int64, eipStateCurBaseFee sdk.Dec) (expectedBaseFee sdk.Dec) { | ||
gasUsed := totalGasWantedThisBlock | ||
gasDiff := gasUsed - TargetGas | ||
|
||
baseFeeIncrement := sdk.NewDec(gasDiff).Quo(sdk.NewDec(TargetGas)).Mul(MaxBlockChangeRate) | ||
expectedBaseFeeMultiplier := sdk.NewDec(1).Add(baseFeeIncrement) | ||
expectedBaseFee = eipStateCurBaseFee.MulMut(expectedBaseFeeMultiplier) | ||
|
||
if expectedBaseFee.LT(MinBaseFee) { | ||
expectedBaseFee = MinBaseFee | ||
} | ||
|
||
if expectedBaseFee.GT(MaxBaseFee) { | ||
expectedBaseFee = MaxBaseFee.Clone() | ||
} | ||
|
||
return expectedBaseFee | ||
} | ||
|
||
// GenTx generates a mock gas transaction. | ||
func GenTx(gas uint64) sdk.Tx { | ||
gen := noapptest.MakeTestEncodingConfig().TxConfig | ||
txBuilder := gen.NewTxBuilder() | ||
txBuilder.SetGasLimit(gas) | ||
return txBuilder.GetTx() | ||
} |
Oops, something went wrong.