Skip to content

Commit

Permalink
Add GASLIMIT opcode (#2062)
Browse files Browse the repository at this point in the history
* Add gasLimit to block header

...and include it in JSON-RPC responses.

* Set GasLimit header field on prepareBlock

* Add GASLIMIT opcode

* No gasLimit when EthCompatible is off

Preserve old Celo behavior when EthCompatible is turned off.

* tests: gasLimit is now available for pruned blocks

* Optional gasLimit in RLP-serialized block headers

to provide compatibility with blocks before the gasLimit has been added
to the header.

* Keep gasLimit in RPC response if in block header

See also #2062 (comment)

* Move GASLIMIT opcode to G-fork

* mycelo: enable GFork by default

We always want to run and test the latest hard fork by default.

* Ensure correct presence of gasLimit in RPC

Before GFork, gasLimit is not in the header, so we have to
* Add it if RPCEthCompatibility
* Remove it if not RPCEthCompatibility, since it is now in the header
  struct and we would otherwise return a zero value

After GFork it should be returned directly from the header, even if not
RPCEthCompatibility, since it is now part of the header hash.

* Only add gasLimit to header after GFork

* Update monorepo_commit

Required changes:
* Using gasLimit in hash if present
* Turn GFork off for CIP-35 test

* Fix pending block when no RPCEthCompatibility

The RPCEthCompatibility check was not intended here. See
#2062 (comment)
  • Loading branch information
karlb committed Jun 6, 2023
1 parent fedfe26 commit 5aef595
Show file tree
Hide file tree
Showing 14 changed files with 113 additions and 36 deletions.
14 changes: 9 additions & 5 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,18 @@ type Header struct {
extraLock sync.Mutex
extraValue *IstanbulExtra
extraError error

GasLimit uint64 `json:"gasLimit" rlp:"optional"`
}

// field type overrides for gencodec
type headerMarshaling struct {
Number *hexutil.Big
GasUsed hexutil.Uint64
Time hexutil.Uint64
Extra hexutil.Bytes
Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON
Number *hexutil.Big
GasLimit hexutil.Uint64
GasUsed hexutil.Uint64
Time hexutil.Uint64
Extra hexutil.Bytes
Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON
}

// Hash returns the block hash of the header, which is simply the keccak256 hash of its
Expand Down Expand Up @@ -289,6 +292,7 @@ func CopyHeader(h *Header) *Header {
ReceiptHash: h.ReceiptHash,
Bloom: h.Bloom,
Number: new(big.Int),
GasLimit: h.GasLimit,
GasUsed: h.GasUsed,
Time: h.Time,
}
Expand Down
6 changes: 6 additions & 0 deletions core/types/gen_header_json.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ type BlockContext struct {

// Block information
Coinbase common.Address // Provides information for COINBASE
GasLimit uint64 // Provides information for GASLIMIT
BlockNumber *big.Int // Provides information for NUMBER
Time *big.Int // Provides information for TIME

Expand Down
5 changes: 5 additions & 0 deletions core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,11 @@ func opNumber(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
return nil, nil
}

func opGasLimit(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetUint64(interpreter.evm.Context.GasLimit))
return nil, nil
}

func opPop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.pop()
return nil, nil
Expand Down
6 changes: 6 additions & 0 deletions core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ type JumpTable [256]*operation
// constantinople, istanbul, petersburg, espresso and g-fork instructions.
func newGforkInstructionSet() JumpTable {
instructionSet := newEspressoInstructionSet()
instructionSet[GASLIMIT] = &operation{
execute: opGasLimit,
constantGas: GasQuickStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
return instructionSet
}

Expand Down
1 change: 1 addition & 0 deletions core/vm/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ const (
COINBASE
TIMESTAMP
NUMBER
GASLIMIT OpCode = 0x45
CHAINID OpCode = 0x46
SELFBALANCE OpCode = 0x47
BASEFEE OpCode = 0x48
Expand Down
1 change: 1 addition & 0 deletions core/vm/runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func TestEVM(t *testing.T) {

Execute([]byte{
byte(vm.TIMESTAMP),
byte(vm.GASLIMIT),
byte(vm.PUSH1),
byte(vm.ORIGIN),
byte(vm.BLOCKHASH),
Expand Down
1 change: 1 addition & 0 deletions core/vm/vmcontext/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func NewBlockContext(header *types.Header, chain chainContext, txFeeRecipient *c
GetHash: GetHashFn(header, chain),
VerifySeal: VerifySealFn(header, chain),
Coinbase: beneficiary,
GasLimit: header.GasLimit,
BlockNumber: new(big.Int).Set(header.Number),
Time: new(big.Int).SetUint64(header.Time),

Expand Down
51 changes: 49 additions & 2 deletions e2e_test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ func TestEthersJSCompatibilityDisable(t *testing.T) {
_, ok = result["baseFeePerGas"]
assert.True(t, ok, "baseFeePerGas field should be present on RPC block")

// Turn of compatibility and check fields are not present
// Turn off compatibility and check fields are not present
ec.RPCEthCompatibility = false
network, shutdown, err = test.NewNetwork(ac, gc, ec)
require.NoError(t, err)
Expand All @@ -619,8 +619,55 @@ func TestEthersJSCompatibilityDisable(t *testing.T) {
err = network[0].WsClient.GetRPCClient().CallContext(ctx, &result, "eth_getBlockByNumber", "latest", true)
require.NoError(t, err)

// After GFork, gasLimit should be returned directly from the header, even if
// RPCEthCompatibility is off, since it is now part of the header hash.
_, ok = result["gasLimit"]
assert.False(t, ok, "gasLimit field should not be present on RPC block")
assert.True(t, ok, "gasLimit field must be present on RPC block after GFork")
_, ok = result["baseFeePerGas"]
assert.False(t, ok, "baseFeePerGas field must be present on RPC block")
}

// This test checks the functionality of the configuration to enable/disable
// returning the 'gasLimit' and 'baseFeePerGas' fields on RPC blocks before the GFork happened.
// GFork is relevant because it added the gasLimit to the header.
func TestEthersJSCompatibilityDisableBeforeGFork(t *testing.T) {
ac := test.AccountConfig(1, 1)
gc, ec, err := test.BuildConfig(ac)
gc.Hardforks.GForkBlock = nil
require.NoError(t, err)

// Check fields present (compatibility set by default)
network, shutdown, err := test.NewNetwork(ac, gc, ec)
require.NoError(t, err)
defer shutdown()

ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()

result := make(map[string]interface{})
err = network[0].WsClient.GetRPCClient().CallContext(ctx, &result, "eth_getBlockByNumber", "latest", true)
require.NoError(t, err)

_, ok := result["gasLimit"]
assert.True(t, ok, "gasLimit field should be present on RPC block")
_, ok = result["baseFeePerGas"]
assert.True(t, ok, "baseFeePerGas field should be present on RPC block")

// Turn off compatibility and check fields are not present
ec.RPCEthCompatibility = false
network, shutdown, err = test.NewNetwork(ac, gc, ec)
require.NoError(t, err)
defer shutdown()

ctx, cancel = context.WithTimeout(context.Background(), time.Second*20)
defer cancel()

result = make(map[string]interface{})
err = network[0].WsClient.GetRPCClient().CallContext(ctx, &result, "eth_getBlockByNumber", "latest", true)
require.NoError(t, err)

_, ok = result["gasLimit"]
assert.False(t, ok, "gasLimit field should not be present on RPC block before GFork")
_, ok = result["baseFeePerGas"]
assert.False(t, ok, "baseFeePerGas field should not be present on RPC block")
}
14 changes: 2 additions & 12 deletions e2e_test/ethersjs-api-check/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,13 @@ describe('ethers.js compatibility tests with state', () => {

describe('ethers.js compatibility tests with no state', () => {

it('provider.getBlock throws exception (no gasLimit)', async () => {
let provider = new ethers.JsonRpcProvider(process.env.npm_config_networkaddr);
try {
await provider.getBlock(process.env.npm_config_blocknum as string);
} catch (e) {
return
}
assert.fail("Expecting exception to be thrown when getting block")
});

it('block has no gasLimit', async () => {
it('block has gasLimit', async () => {
let provider = new ethers.JsonRpcProvider(process.env.npm_config_networkaddr);
const fullBlock = await provider.send(
'eth_getBlockByNumber',
[ethers.toQuantity(process.env.npm_config_blocknum as string), true]
)
assert.isFalse(fullBlock.hasOwnProperty('gasLimit'))
assert.isTrue(fullBlock.hasOwnProperty('gasLimit'))
});

it('block has no baseFeePerGas', async () => {
Expand Down
43 changes: 27 additions & 16 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -709,8 +709,8 @@ func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.B
if block != nil && err == nil {
response, err := s.rpcMarshalBlock(ctx, block, true, fullTx)

if err == nil && s.b.RPCEthCompatibility() {
addEthCompatibilityFields(ctx, response, s.b, block.Header())
if err == nil {
addEthCompatibilityFields(ctx, response, s.b, block)
if number == rpc.PendingBlockNumber {
// Pending blocks need to nil out a few fields
for _, field := range []string{"hash", "nonce", "miner"} {
Expand All @@ -732,9 +732,7 @@ func (s *PublicBlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Ha
if err != nil {
return nil, err
}
if s.b.RPCEthCompatibility() {
addEthCompatibilityFields(ctx, result, s.b, block.Header())
}
addEthCompatibilityFields(ctx, result, s.b, block)
return result, nil
}
return nil, err
Expand All @@ -744,27 +742,39 @@ func (s *PublicBlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Ha
// and ethers.js (and potentially other web3 clients) by adding fields to our
// rpc response that ethers.js depends upon.
// See https://github.com/celo-org/celo-blockchain/issues/1945
func addEthCompatibilityFields(ctx context.Context, block map[string]interface{}, b Backend, header *types.Header) {
hash := header.Hash()
numhash := rpc.BlockNumberOrHash{
BlockHash: &hash,
func addEthCompatibilityFields(ctx context.Context, response map[string]interface{}, b Backend, block *types.Block) {
isGFork := b.ChainConfig().IsGFork(block.Number())
if !b.RPCEthCompatibility() {
if !isGFork {
delete(response, "gasLimit")
}
return
}
gasLimit, err := b.GetRealBlockGasLimit(ctx, numhash)
if err != nil {
log.Debug("Not adding gasLimit to RPC response, failed to retrieve it", "block", header.Number.Uint64(), "err", err)
} else {
block["gasLimit"] = hexutil.Uint64(gasLimit)

header := block.Header()
if !isGFork {
// Before GFork, the header did not include the gasLimit, so we have to manually add it for eth-compatible RPC responses.
hash := header.Hash()
numhash := rpc.BlockNumberOrHash{
BlockHash: &hash,
}
gasLimit, err := b.GetRealBlockGasLimit(ctx, numhash)
if err != nil {
log.Debug("Not adding gasLimit to RPC response, failed to retrieve it", "block", header.Number.Uint64(), "err", err)
} else {
response["gasLimit"] = hexutil.Uint64(gasLimit)
}
}

// Providing nil as the currency address gets the gas price minimum for the native celo asset.
baseFee, err := b.RealGasPriceMinimumForHeader(ctx, nil, header)
if err != nil {
log.Debug("Not adding baseFeePerGas to RPC response, failed to retrieve gas price minimum", "block", header.Number.Uint64(), "err", err)
} else {
block["baseFeePerGas"] = (*hexutil.Big)(baseFee)
response["baseFeePerGas"] = (*hexutil.Big)(baseFee)
}

block["difficulty"] = "0x0"
response["difficulty"] = "0x0"
}

// GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index. When fullTx is true
Expand Down Expand Up @@ -1133,6 +1143,7 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
"miner": head.Coinbase,
"extraData": hexutil.Bytes(head.Extra),
"size": hexutil.Uint64(head.Size()),
"gasLimit": hexutil.Uint64(head.GasLimit),
"gasUsed": hexutil.Uint64(head.GasUsed),
"timestamp": hexutil.Uint64(head.Time),
"transactionsRoot": head.TxHash,
Expand Down
3 changes: 3 additions & 0 deletions miner/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ func prepareBlock(w *worker) (*blockState, error) {
sysCtx: core.NewSysContractCallCtx(header, state.Copy(), w.chain),
}
b.gasPool = new(core.GasPool).AddGas(b.gasLimit)
if w.chainConfig.IsGFork(header.Number) {
header.GasLimit = b.gasLimit
}

// Play our part in generating the random beacon.
if w.isRunning() && random.IsRunning(vmRunner) {
Expand Down
2 changes: 1 addition & 1 deletion monorepo_commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ad770ace57ec3fa1bc1fd540d0321ab2e7317076
d216e7599f499adec2e0200e599f601ea5424cde
1 change: 1 addition & 0 deletions mycelo/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func CreateCommonGenesisConfig(chainID *big.Int, adminAccountAddress common.Addr
ChurritoBlock: common.Big0,
DonutBlock: common.Big0,
EspressoBlock: common.Big0,
GForkBlock: common.Big0,
}

// Make admin account manager of Governance & Reserve
Expand Down

0 comments on commit 5aef595

Please sign in to comment.