Skip to content

Commit

Permalink
Implement Gas Price Floor Stability Mechanism (ethereum#248)
Browse files Browse the repository at this point in the history
  • Loading branch information
jarmg authored and Asa Oines committed Jun 29, 2019
1 parent 2ac37b6 commit 5ec2ffe
Show file tree
Hide file tree
Showing 39 changed files with 508 additions and 539 deletions.
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ jobs:
mkdir -p ~/.ssh/
echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
export CELO_MONOREPO_DIR="./celo-monorepo"
# TODO(nguo) change this back to master
git clone --depth 1 https://${GH_AUTH_USERNAME}:${GH_AUTH_TOKEN}@github.com/celo-org/celo-monorepo.git ${CELO_MONOREPO_DIR} -b nguo/add-fields-tx-signature
# TODO(jarmg): Reset this to master once the corresponding monorepo PR is merged
git clone --depth 1 https://${GH_AUTH_USERNAME}:${GH_AUTH_TOKEN}@github.com/celo-org/celo-monorepo.git ${CELO_MONOREPO_DIR} -b jarmg/multi-currency-gas-estimates
# Change these paths to use https login since the SSH key does not have access to these repositories.
# Once we open source this code, these modifications can be eliminated.
# These environment variables are configured at https://circleci.com/gh/celo-org/geth/edit#env-vars
Expand Down
2 changes: 1 addition & 1 deletion accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{})
gaspool := new(core.GasPool).AddGas(math.MaxUint64)

return core.NewStateTransition(vmenv, msg, gaspool, nil).TransitionDb()
return core.NewStateTransition(vmenv, msg, gaspool, nil, core.FallbackGasPriceMinimum, core.FallbackInfraFraction, nil).TransitionDb()
}

// SendTransaction updates the pending block to include the given transaction.
Expand Down
2 changes: 0 additions & 2 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,6 @@ var (
utils.MetricsEnabledFlag,
utils.FakePoWFlag,
utils.NoCompactionFlag,
utils.GpoBlocksFlag,
utils.GpoPercentileFlag,
utils.EWASMInterpreterFlag,
utils.EVMInterpreterFlag,
configFileFlag,
Expand Down
7 changes: 0 additions & 7 deletions cmd/geth/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,6 @@ var AppHelpFlagGroups = []flagGroup{
utils.MinerVerificationServiceUrlFlag,
},
},
{
Name: "GAS PRICE ORACLE",
Flags: []cli.Flag{
utils.GpoBlocksFlag,
utils.GpoPercentileFlag,
},
},
{
Name: "VIRTUAL MACHINE",
Flags: []cli.Flag{
Expand Down
22 changes: 0 additions & 22 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import (
"github.com/ethereum/go-ethereum/dashboard"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethstats"
"github.com/ethereum/go-ethereum/les"
Expand Down Expand Up @@ -563,17 +562,6 @@ var (
Value: ".",
}

// Gas price oracle settings
GpoBlocksFlag = cli.IntFlag{
Name: "gpoblocks",
Usage: "Number of recent blocks to check for gas prices",
Value: eth.DefaultConfig.GPO.Blocks,
}
GpoPercentileFlag = cli.IntFlag{
Name: "gpopercentile",
Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices",
Value: eth.DefaultConfig.GPO.Percentile,
}
WhisperEnabledFlag = cli.BoolFlag{
Name: "shh",
Usage: "Enable Whisper",
Expand Down Expand Up @@ -1040,15 +1028,6 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) {
}
}

func setGPO(ctx *cli.Context, cfg *gasprice.Config) {
if ctx.GlobalIsSet(GpoBlocksFlag.Name) {
cfg.Blocks = ctx.GlobalInt(GpoBlocksFlag.Name)
}
if ctx.GlobalIsSet(GpoPercentileFlag.Name) {
cfg.Percentile = ctx.GlobalInt(GpoPercentileFlag.Name)
}
}

func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) {
if ctx.GlobalIsSet(TxPoolLocalsFlag.Name) {
locals := strings.Split(ctx.GlobalString(TxPoolLocalsFlag.Name), ",")
Expand Down Expand Up @@ -1207,7 +1186,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {

ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
setEtherbase(ctx, ks, cfg)
setGPO(ctx, &cfg.GPO)
setTxPool(ctx, &cfg.TxPool)
setEthash(ctx, cfg)
setWhitelist(ctx, cfg)
Expand Down
10 changes: 9 additions & 1 deletion consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ type ConsensusIEvmH interface {
}

type ConsensusRegAdd interface {
GetRegisteredAddress(registryId string) (*common.Address, error)
GetRegisteredAddressAtStateAndHeader(registryId string, state *state.StateDB, header *types.Header) (*common.Address, error)
GetRegisteredAddressAtCurrentHeader(registryId string) (*common.Address, error)
GetRegisteredAddressMapAtStateAndHeader(state *state.StateDB, header *types.Header) map[string]*common.Address
}

type ConsensusGasPriceMinimum interface {
UpdateGasPriceMinimum(header *types.Header, state *state.StateDB) (*big.Int, error)
}

// Engine is an algorithm agnostic consensus engine.
Expand Down Expand Up @@ -149,6 +155,8 @@ type Istanbul interface {

SetRegisteredAddresses(regAdd ConsensusRegAdd)

SetGasPriceMinimum(gpm ConsensusGasPriceMinimum)

SetChain(chain ChainReader, currentBlock func() *types.Block)

// Start starts the engine
Expand Down
1 change: 1 addition & 0 deletions consensus/istanbul/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ type Backend struct {

iEvmH consensus.ConsensusIEvmH
regAdd consensus.ConsensusRegAdd
gpm consensus.ConsensusGasPriceMinimum

lastAnnounceGossiped map[common.Address]*AnnounceGossipTimestamp

Expand Down
43 changes: 23 additions & 20 deletions consensus/istanbul/backend/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ func (sb *Backend) Prepare(chain consensus.ChainReader, header *types.Header) er

func (sb *Backend) getValSet(header *types.Header, state *state.StateDB) ([]common.Address, error) {
var newValSet []common.Address
validatorsAddress, err := sb.regAdd.GetRegisteredAddress(params.ValidatorsRegistryId)
validatorsAddress, err := sb.regAdd.GetRegisteredAddressAtStateAndHeader(params.ValidatorsRegistryId, state, header)
if err == core.ErrSmartContractNotDeployed {
log.Warn("Registry address lookup failed", "err", err)
return newValSet, errValidatorsContractNotRegistered
Expand Down Expand Up @@ -478,42 +478,46 @@ func (sb *Backend) IsLastBlockOfEpoch(header *types.Header) bool {
//
// Note, the block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards).
func (sb *Backend) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header, receipts []*types.Receipt, randomness *types.Randomness) (*types.Block, error) {
func (sb *Backend) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, randomness *types.Randomness) (*types.Block, error) {
// Trigger an update to the gas price minimum in the GasPriceMinimum contract based on block congestion
updatedGasPriceMinimum, err := sb.gpm.UpdateGasPriceMinimum(header, state)

// Calculate a new gas price suggestion and push it to the GasPriceOracle SmartContract
sb.updateGasPriceSuggestion(state)
if err != nil {
log.Error("Error in updating gas price minimum", "error", err, "updatedGasPriceMinimum", updatedGasPriceMinimum)
}

// Add block rewards
goldTokenAddress, err := sb.regAdd.GetRegisteredAddress(params.GoldTokenRegistryId)
goldTokenAddress, err := sb.regAdd.GetRegisteredAddressAtStateAndHeader(params.GoldTokenRegistryId, state, header)
if err == core.ErrSmartContractNotDeployed {
log.Warn("Registry address lookup failed", "err", err)
} else if err != nil {
log.Error(err.Error())
}
if goldTokenAddress != nil { // add block rewards only if goldtoken smart contract has been initialized
// Add block rewards only if goldtoken smart contract has been initialized
if goldTokenAddress != nil {
totalBlockRewards := big.NewInt(0)

infrastructureBlockReward := big.NewInt(params.Ether)
governanceAddress, err := sb.regAdd.GetRegisteredAddress(params.GovernanceRegistryId)
governanceAddress, err := sb.regAdd.GetRegisteredAddressAtStateAndHeader(params.GovernanceRegistryId, state, header)
if err == core.ErrSmartContractNotDeployed {
log.Warn("Registry address lookup failed", "err", err)
} else if err != nil {
log.Error(err.Error())
}

if governanceAddress != nil {
state.AddBalance(*governanceAddress, infrastructureBlockReward)
totalBlockRewards.Add(totalBlockRewards, infrastructureBlockReward)
}

stakerBlockReward := big.NewInt(params.Ether)
bondedDepositsAddress, err := sb.regAdd.GetRegisteredAddress(params.BondedDepositsRegistryId)
bondedDepositsAddress, err := sb.regAdd.GetRegisteredAddressAtStateAndHeader(params.BondedDepositsRegistryId, state, header)
if err == core.ErrSmartContractNotDeployed {
log.Warn("Registry address lookup failed", "err", err)
} else if err != nil {
log.Error(err.Error())
}

if governanceAddress != nil && bondedDepositsAddress != nil {
state.AddBalance(*governanceAddress, infrastructureBlockReward)
totalBlockRewards.Add(totalBlockRewards, infrastructureBlockReward)

if bondedDepositsAddress != nil {
state.AddBalance(*bondedDepositsAddress, stakerBlockReward)
totalBlockRewards.Add(totalBlockRewards, stakerBlockReward)
_, err := sb.iEvmH.MakeCall(*bondedDepositsAddress, setCumulativeRewardWeightFuncABI, "setCumulativeRewardWeight", []interface{}{stakerBlockReward}, nil, 1000000, common.Big0, header, state)
Expand All @@ -523,7 +527,7 @@ func (sb *Backend) Finalize(chain consensus.ChainReader, header *types.Header, s
}
}

// update totalSupply of GoldToken
// Update totalSupply of GoldToken.
if totalBlockRewards.Cmp(common.Big0) > 0 {
var totalSupply *big.Int
if _, err := sb.iEvmH.MakeStaticCall(*goldTokenAddress, totalSupplyFuncABI, "totalSupply", []interface{}{}, &totalSupply, 1000000, header, state); err != nil || totalSupply == nil {
Expand Down Expand Up @@ -554,11 +558,6 @@ func (sb *Backend) Finalize(chain consensus.ChainReader, header *types.Header, s
return types.NewBlock(header, txs, nil, receipts, randomness), nil
}

// TODO (jarmg 5/23/18): Implement this
func (sb *Backend) updateGasPriceSuggestion(state *state.StateDB) *state.StateDB {
return (state)
}

// Seal generates a new block for the given input block with the local miner's
// seal place on top.
func (sb *Backend) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
Expand Down Expand Up @@ -673,6 +672,10 @@ func (sb *Backend) SetRegisteredAddresses(regAdd consensus.ConsensusRegAdd) {
sb.regAdd = regAdd
}

func (sb *Backend) SetGasPriceMinimum(gpm consensus.ConsensusGasPriceMinimum) {
sb.gpm = gpm
}

func (sb *Backend) SetChain(chain consensus.ChainReader, currentBlock func() *types.Block) {
sb.chain = chain
sb.currentBlock = currentBlock
Expand Down
2 changes: 2 additions & 0 deletions consensus/istanbul/backend/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,12 @@ func newBlockChain(n int, isFullChain bool) (*core.BlockChain, *Backend) {

iEvmH := core.NewInternalEVMHandler(blockchain)
regAdd := core.NewRegisteredAddresses(iEvmH)
gpm := core.NewGasPriceMinimum(iEvmH, regAdd)
iEvmH.SetRegisteredAddresses(regAdd)

b.SetInternalEVMHandler(iEvmH)
b.SetRegisteredAddresses(regAdd)
b.SetGasPriceMinimum(gpm)
b.SetChain(blockchain, blockchain.CurrentBlock)

b.Start(blockchain.HasBadBlock,
Expand Down
2 changes: 1 addition & 1 deletion consensus/istanbul/backend/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ var (
func (sb *Backend) retrieveRegisteredValidators() (map[common.Address]bool, error) {
var regVals []common.Address

validatorAddress, _ := sb.regAdd.GetRegisteredAddress(params.ValidatorsRegistryId)
validatorAddress, _ := sb.regAdd.GetRegisteredAddressAtCurrentHeader(params.ValidatorsRegistryId)
if validatorAddress == nil {
return nil, errValidatorsContractNotRegistered
} else {
Expand Down
2 changes: 1 addition & 1 deletion core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
b.SetCoinbase(common.Address{})
}
b.statedb.Prepare(tx.Hash(), common.Hash{}, len(b.txs))
receipt, _, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}, nil, nil)
receipt, _, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}, nil, nil, FallbackGasPriceMinimum, FallbackInfraFraction)
if err != nil {
panic(err)
}
Expand Down
8 changes: 4 additions & 4 deletions core/currency.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (co *CurrencyOperator) getExchangeRate(currency *common.Address) (*exchange
}

func (co *CurrencyOperator) ConvertToGold(val *big.Int, currencyFrom *common.Address) (*big.Int, error) {
celoGoldAddress, err := co.regAdd.GetRegisteredAddress(params.GoldTokenRegistryId)
celoGoldAddress, err := co.regAdd.GetRegisteredAddressAtCurrentHeader(params.GoldTokenRegistryId)
if err == ErrSmartContractNotDeployed || currencyFrom == celoGoldAddress {
log.Warn("Registry address lookup failed", "err", err)
return val, nil
Expand Down Expand Up @@ -202,7 +202,7 @@ func (co *CurrencyOperator) Cmp(val1 *big.Int, currency1 *common.Address, val2 *
// "function medianRate(address)"
func (co *CurrencyOperator) refreshExchangeRates() {
gasCurrencyAddresses := co.gcWl.Whitelist()
sortedOraclesAddress, err := co.regAdd.GetRegisteredAddress(params.SortedOraclesRegistryId)
sortedOraclesAddress, err := co.regAdd.GetRegisteredAddressAtCurrentHeader(params.SortedOraclesRegistryId)

if err == ErrSmartContractNotDeployed {
log.Warn("Registry address lookup failed", "err", err)
Expand All @@ -211,7 +211,7 @@ func (co *CurrencyOperator) refreshExchangeRates() {
log.Error(err.Error())
}

celoGoldAddress, err := co.regAdd.GetRegisteredAddress(params.GoldTokenRegistryId)
celoGoldAddress, err := co.regAdd.GetRegisteredAddressAtCurrentHeader(params.GoldTokenRegistryId)

if err == ErrSmartContractNotDeployed {
log.Warn("Registry address lookup failed", "err", err)
Expand Down Expand Up @@ -310,7 +310,7 @@ type GasCurrencyWhitelist struct {

func (gcWl *GasCurrencyWhitelist) retrieveWhitelist(state *state.StateDB, header *types.Header) ([]common.Address, error) {
returnList := []common.Address{}
gasCurrencyWhiteListAddress, err := gcWl.regAdd.GetRegisteredAddress(params.GasCurrencyWhitelistRegistryId)
gasCurrencyWhiteListAddress, err := gcWl.regAdd.GetRegisteredAddressAtCurrentHeader(params.GasCurrencyWhitelistRegistryId)
if err != nil {
if err == ErrSmartContractNotDeployed {
log.Warn("Registry address lookup failed", "err", err)
Expand Down
30 changes: 19 additions & 11 deletions core/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ type ChainContext interface {
}

// NewEVMContext creates a new context for use in the EVM.
func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address, registeredAddresses *RegisteredAddresses) vm.Context {
func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address, registeredAddressesMap map[string]*common.Address) vm.Context {
// If we don't have an explicit author (i.e. not mining), extract from the header
var beneficiary common.Address
if author == nil {
Expand All @@ -64,11 +64,6 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
beneficiary = *author
}

var registeredAddressMap map[string]*common.Address
if registeredAddresses != nil {
registeredAddressMap = registeredAddresses.GetRegisteredAddressMap()
}

return vm.Context{
CanTransfer: CanTransfer,
Transfer: Transfer,
Expand All @@ -80,7 +75,7 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
Difficulty: new(big.Int).Set(header.Difficulty),
GasLimit: header.GasLimit,
GasPrice: new(big.Int).Set(msg.GasPrice()),
RegisteredAddressMap: registeredAddressMap,
RegisteredAddressMap: registeredAddressesMap,
}
}

Expand Down Expand Up @@ -133,7 +128,15 @@ func (iEvmH *InternalEVMHandler) MakeStaticCall(scAddress common.Address, abi ab
return evm.ABIStaticCall(zeroCaller, scAddress, abi, funcName, args, returnObj, gas)
}

return iEvmH.makeCall(abiStaticCall, header, state)
return iEvmH.makeCall(abiStaticCall, header, state, iEvmH.regAdd)
}

func (iEvmH *InternalEVMHandler) MakeStaticCallNoRegisteredAddressMap(scAddress common.Address, abi abi.ABI, funcName string, args []interface{}, returnObj interface{}, gas uint64, header *types.Header, state *state.StateDB) (uint64, error) {
abiStaticCall := func(evm *vm.EVM) (uint64, error) {
return evm.ABIStaticCall(zeroCaller, scAddress, abi, funcName, args, returnObj, gas)
}

return iEvmH.makeCall(abiStaticCall, header, state, nil)
}

func (iEvmH *InternalEVMHandler) MakeCall(scAddress common.Address, abi abi.ABI, funcName string, args []interface{}, returnObj interface{}, gas uint64, value *big.Int, header *types.Header, state *state.StateDB) (uint64, error) {
Expand All @@ -144,10 +147,14 @@ func (iEvmH *InternalEVMHandler) MakeCall(scAddress common.Address, abi abi.ABI,
return gasLeft, err
}

return iEvmH.makeCall(abiCall, header, state)
return iEvmH.makeCall(abiCall, header, state, iEvmH.regAdd)
}

func (iEvmH *InternalEVMHandler) CurrentHeader() *types.Header {
return iEvmH.chain.CurrentHeader()
}

func (iEvmH *InternalEVMHandler) makeCall(call func(evm *vm.EVM) (uint64, error), header *types.Header, state *state.StateDB) (uint64, error) {
func (iEvmH *InternalEVMHandler) makeCall(call func(evm *vm.EVM) (uint64, error), header *types.Header, state *state.StateDB, regAdd *RegisteredAddresses) (uint64, error) {
// Normally, when making an evm call, we should use the current block's state. However,
// there are times (e.g. retrieving the set of validators when an epoch ends) that we need
// to call the evm using the currently mined block. In that case, the header and state params
Expand All @@ -167,9 +174,10 @@ func (iEvmH *InternalEVMHandler) makeCall(call func(evm *vm.EVM) (uint64, error)
}
}

registeredAddressesMap := regAdd.GetRegisteredAddressMapAtStateAndHeader(state, header)
// The EVM Context requires a msg, but the actual field values don't really matter for this case.
// Putting in zero values.
context := NewEVMContext(emptyMessage, header, iEvmH.chain, nil, iEvmH.regAdd)
context := NewEVMContext(emptyMessage, header, iEvmH.chain, nil, registeredAddressesMap)
evm := vm.NewEVM(context, state, iEvmH.chain.Config(), *iEvmH.chain.GetVMConfig())

return call(evm)
Expand Down
Loading

0 comments on commit 5ec2ffe

Please sign in to comment.