Commit 19599c7d authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into dependabot/go_modules/github.com/docker/docker-20.10.24incompatible

parents 4761fede d0ee681f
[submodule "tests"]
path = l2geth/tests/testdata
url = https://github.com/ethereum/tests
[submodule "packages/contracts-periphery/lib/multicall"]
path = packages/contracts-periphery/lib/multicall
url = https://github.com/mds1/multicall
[submodule "lib/multicall"]
branch = v3.1.0
......@@ -447,7 +447,7 @@ func TestBigL2Txs(gt *testing.T) {
require.NoError(t, err)
gas, err := core.IntrinsicGas(data, nil, false, true, true, false)
require.NoError(t, err)
if gas > engine.l2GasPool.Gas() {
if gas > engine.engineApi.RemainingBlockGas() {
break
}
tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{
......
......@@ -3,12 +3,12 @@ package actions
import (
"errors"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-program/l2/engineapi"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
geth "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
......@@ -39,21 +39,9 @@ type L2Engine struct {
// L2 evm / chain
l2Chain *core.BlockChain
l2Database ethdb.Database
l2Cfg *core.Genesis
l2Signer types.Signer
// L2 block building data
l2BuildingHeader *types.Header // block header that we add txs to for block building
l2BuildingState *state.StateDB // state used for block building
l2GasPool *core.GasPool // track gas used of ongoing building
pendingIndices map[common.Address]uint64 // per account, how many txs from the pool were already included in the block, since the pool is lagging behind block mining.
l2Transactions []*types.Transaction // collects txs that were successfully included into current block build
l2Receipts []*types.Receipt // collect receipts of ongoing building
l2ForceEmpty bool // when no additional txs may be processed (i.e. when sequencer drift runs out)
l2TxFailed []*types.Transaction // log of failed transactions which could not be included
payloadID engine.PayloadID // ID of payload that is currently being built
engineApi *engineapi.L2EngineAPI
failL2RPC error // mock error
}
......@@ -61,6 +49,38 @@ type L2Engine struct {
type EngineOption func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error
func NewL2Engine(t Testing, log log.Logger, genesis *core.Genesis, rollupGenesisL1 eth.BlockID, jwtPath string, options ...EngineOption) *L2Engine {
n, ethBackend, apiBackend := newBackend(t, genesis, jwtPath, options)
engineApi := engineapi.NewL2EngineAPI(log, apiBackend)
chain := ethBackend.BlockChain()
genesisBlock := chain.Genesis()
eng := &L2Engine{
log: log,
node: n,
eth: ethBackend,
rollupGenesis: &rollup.Genesis{
L1: rollupGenesisL1,
L2: eth.BlockID{Hash: genesisBlock.Hash(), Number: genesisBlock.NumberU64()},
L2Time: genesis.Timestamp,
},
l2Chain: chain,
l2Signer: types.LatestSigner(genesis.Config),
engineApi: engineApi,
}
// register the custom engine API, so we can serve engine requests while having more control
// over sequencing of individual txs.
n.RegisterAPIs([]rpc.API{
{
Namespace: "engine",
Service: eng.engineApi,
Authenticated: true,
},
})
require.NoError(t, n.Start(), "failed to start L2 op-geth node")
return eng
}
func newBackend(t e2eutils.TestingBase, genesis *core.Genesis, jwtPath string, options []EngineOption) (*node.Node, *geth.Ethereum, *engineApiBackend) {
ethCfg := &ethconfig.Config{
NetworkId: genesis.Config.ChainID.Uint64(),
Genesis: genesis,
......@@ -89,33 +109,26 @@ func NewL2Engine(t Testing, log log.Logger, genesis *core.Genesis, rollupGenesis
chain := backend.BlockChain()
db := backend.ChainDb()
genesisBlock := chain.Genesis()
eng := &L2Engine{
log: log,
node: n,
eth: backend,
rollupGenesis: &rollup.Genesis{
L1: rollupGenesisL1,
L2: eth.BlockID{Hash: genesisBlock.Hash(), Number: genesisBlock.NumberU64()},
L2Time: genesis.Timestamp,
},
l2Chain: chain,
l2Database: db,
l2Cfg: genesis,
l2Signer: types.LatestSigner(genesis.Config),
apiBackend := &engineApiBackend{
BlockChain: chain,
db: db,
genesis: genesis,
}
// register the custom engine API, so we can serve engine requests while having more control
// over sequencing of individual txs.
n.RegisterAPIs([]rpc.API{
{
Namespace: "engine",
Service: (*L2EngineAPI)(eng),
Authenticated: true,
},
})
require.NoError(t, n.Start(), "failed to start L2 op-geth node")
return n, backend, apiBackend
}
return eng
type engineApiBackend struct {
*core.BlockChain
db ethdb.Database
genesis *core.Genesis
}
func (e *engineApiBackend) Database() ethdb.Database {
return e.db
}
func (e *engineApiBackend) Genesis() *core.Genesis {
return e.genesis
}
func (s *L2Engine) EthClient() *ethclient.Client {
......@@ -158,39 +171,25 @@ func (e *L2Engine) ActL2RPCFail(t Testing) {
// ActL2IncludeTx includes the next transaction from the given address in the block that is being built
func (e *L2Engine) ActL2IncludeTx(from common.Address) Action {
return func(t Testing) {
if e.l2BuildingHeader == nil {
t.InvalidAction("not currently building a block, cannot include tx from queue")
return
}
if e.l2ForceEmpty {
if e.engineApi.ForcedEmpty() {
e.log.Info("Skipping including a transaction because e.L2ForceEmpty is true")
// t.InvalidAction("cannot include any sequencer txs")
return
}
i := e.pendingIndices[from]
i := e.engineApi.PendingIndices(from)
txs, q := e.eth.TxPool().ContentFrom(from)
if uint64(len(txs)) <= i {
t.Fatalf("no pending txs from %s, and have %d unprocessable queued txs from this account", from, len(q))
}
tx := txs[i]
if tx.Gas() > e.l2BuildingHeader.GasLimit {
t.Fatalf("tx consumes %d gas, more than available in L2 block %d", tx.Gas(), e.l2BuildingHeader.GasLimit)
}
if tx.Gas() > uint64(*e.l2GasPool) {
t.InvalidAction("action takes too much gas: %d, only have %d", tx.Gas(), uint64(*e.l2GasPool))
return
}
e.pendingIndices[from] = i + 1 // won't retry the tx
e.l2BuildingState.SetTxContext(tx.Hash(), len(e.l2Transactions))
receipt, err := core.ApplyTransaction(e.l2Cfg.Config, e.l2Chain, &e.l2BuildingHeader.Coinbase,
e.l2GasPool, e.l2BuildingState, e.l2BuildingHeader, tx, &e.l2BuildingHeader.GasUsed, *e.l2Chain.GetVMConfig())
if err != nil {
e.l2TxFailed = append(e.l2TxFailed, tx)
t.Fatalf("failed to apply transaction to L2 block (tx %d): %v", len(e.l2Transactions), err)
err := e.engineApi.IncludeTx(tx, from)
if errors.Is(err, engineapi.ErrNotBuildingBlock) {
t.InvalidAction(err.Error())
} else if errors.Is(err, engineapi.ErrUsesTooMuchGas) {
t.InvalidAction("included tx uses too much gas: %v", err)
} else if err != nil {
t.Fatalf("include tx: %v", err)
}
e.l2Receipts = append(e.l2Receipts, receipt)
e.l2Transactions = append(e.l2Transactions, tx)
}
}
......
......@@ -4,6 +4,8 @@ import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-program/l2/engineapi"
"github.com/ethereum-optimism/optimism/op-program/l2/engineapi/test"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
......@@ -187,3 +189,15 @@ func TestL2EngineAPIFail(gt *testing.T) {
require.NoError(t, err)
require.Equal(gt, sd.L2Cfg.ToBlock().Hash(), head.Hash(), "expecting engine to start at genesis")
}
func TestEngineAPITests(t *testing.T) {
test.RunEngineAPITests(t, func() engineapi.EngineBackend {
jwtPath := e2eutils.WriteDefaultJWT(t)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
sd := e2eutils.Setup(t, dp, defaultAlloc)
n, _, apiBackend := newBackend(t, sd.L2Cfg, jwtPath, nil)
err := n.Start()
require.NoError(t, err)
return apiBackend
})
}
......@@ -98,7 +98,7 @@ func TestL2Sequencer_SequencerDrift(gt *testing.T) {
// We passed the sequencer drift: we can still keep the old origin, but can't include any txs
sequencer.ActL2KeepL1Origin(t)
sequencer.ActL2StartBlock(t)
require.True(t, engine.l2ForceEmpty, "engine should not be allowed to include anything after sequencer drift is surpassed")
require.True(t, engine.engineApi.ForcedEmpty(), "engine should not be allowed to include anything after sequencer drift is surpassed")
}
// TestL2Sequencer_SequencerOnlyReorg regression-tests a Goerli halt where the sequencer
......
This diff is collapsed.
......@@ -5,3 +5,5 @@ import "github.com/ethereum/go-ethereum/core/types"
type NoopTxMetrics struct{}
func (*NoopTxMetrics) RecordL1GasFee(*types.Receipt) {}
func (*NoopTxMetrics) RecordGasBumpCount(int) {}
func (*NoopTxMetrics) RecordTxConfirmationLatency(int64) {}
......@@ -10,10 +10,14 @@ import (
type TxMetricer interface {
RecordL1GasFee(receipt *types.Receipt)
RecordGasBumpCount(times int)
RecordTxConfirmationLatency(latency int64)
}
type TxMetrics struct {
TxL1GasFee prometheus.Gauge
TxGasBump prometheus.Gauge
LatencyConfirmedTx prometheus.Gauge
}
var _ TxMetricer = (*TxMetrics)(nil)
......@@ -26,9 +30,29 @@ func MakeTxMetrics(ns string, factory metrics.Factory) TxMetrics {
Help: "L1 gas fee for transactions in GWEI",
Subsystem: "txmgr",
}),
TxGasBump: factory.NewGauge(prometheus.GaugeOpts{
Namespace: ns,
Name: "tx_gas_bump",
Help: "Number of times a transaction gas needed to be bumped before it got included",
Subsystem: "txmgr",
}),
LatencyConfirmedTx: factory.NewGauge(prometheus.GaugeOpts{
Namespace: ns,
Name: "tx_confirmed_latency_ms",
Help: "Latency of a confirmed transaction in milliseconds",
Subsystem: "txmgr",
}),
}
}
func (t *TxMetrics) RecordL1GasFee(receipt *types.Receipt) {
t.TxL1GasFee.Set(float64(receipt.EffectiveGasPrice.Uint64() * receipt.GasUsed / params.GWei))
}
func (t *TxMetrics) RecordGasBumpCount(times int) {
t.TxGasBump.Set(float64(times))
}
func (t *TxMetrics) RecordTxConfirmationLatency(latency int64) {
t.LatencyConfirmedTx.Set(float64(latency))
}
......@@ -216,6 +216,7 @@ func (m *SimpleTxManager) send(ctx context.Context, tx *types.Transaction) (*typ
ticker := time.NewTicker(m.cfg.ResubmissionTimeout)
defer ticker.Stop()
bumpCounter := 0
for {
select {
case <-ticker.C:
......@@ -231,12 +232,14 @@ func (m *SimpleTxManager) send(ctx context.Context, tx *types.Transaction) (*typ
// Increase the gas price & submit the new transaction
tx = m.increaseGasPrice(ctx, tx)
wg.Add(1)
bumpCounter += 1
go sendTxAsync(tx)
case <-ctx.Done():
return nil, ctx.Err()
case receipt := <-receiptChan:
m.metr.RecordGasBumpCount(bumpCounter)
return receipt, nil
}
}
......@@ -251,6 +254,7 @@ func (m *SimpleTxManager) publishAndWaitForTx(ctx context.Context, tx *types.Tra
cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout)
defer cancel()
t := time.Now()
err := m.backend.SendTransaction(cCtx, tx)
sendState.ProcessSendError(err)
......@@ -282,6 +286,7 @@ func (m *SimpleTxManager) publishAndWaitForTx(ctx context.Context, tx *types.Tra
}
select {
case receiptChan <- receipt:
m.metr.RecordTxConfirmationLatency(time.Since(t).Milliseconds())
m.metr.RecordL1GasFee(receipt)
default:
}
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Multicall3 } from "multicall/src/Multicall3.sol";
/**
* Just exists so we can compile this contract.
*/
contract MulticallContractCompiler {
}
......@@ -6,6 +6,7 @@ import {
ERC721BurnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import { AttestationStation } from "./AttestationStation.sol";
import { OptimistAllowlist } from "./OptimistAllowlist.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
/**
......@@ -15,31 +16,44 @@ import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
* @notice A Soul Bound Token for real humans only(tm).
*/
contract Optimist is ERC721BurnableUpgradeable, Semver {
/**
* @notice Attestation key used by the attestor to attest the baseURI.
*/
bytes32 public constant BASE_URI_ATTESTATION_KEY = bytes32("optimist.base-uri");
/**
* @notice Attestor who attests to baseURI.
*/
address public immutable BASE_URI_ATTESTOR;
/**
* @notice Address of the AttestationStation contract.
*/
AttestationStation public immutable ATTESTATION_STATION;
/**
* @notice Attestor who attests to baseURI and allowlist.
* @notice Address of the OptimistAllowlist contract.
*/
address public immutable ATTESTOR;
OptimistAllowlist public immutable OPTIMIST_ALLOWLIST;
/**
* @custom:semver 1.0.0
* @custom:semver 2.0.0
* @param _name Token name.
* @param _symbol Token symbol.
* @param _attestor Address of the attestor.
* @param _baseURIAttestor Address of the baseURI attestor.
* @param _attestationStation Address of the AttestationStation contract.
* @param _optimistAllowlist Address of the OptimistAllowlist contract
*/
constructor(
string memory _name,
string memory _symbol,
address _attestor,
AttestationStation _attestationStation
) Semver(1, 0, 0) {
ATTESTOR = _attestor;
address _baseURIAttestor,
AttestationStation _attestationStation,
OptimistAllowlist _optimistAllowlist
) Semver(2, 0, 0) {
BASE_URI_ATTESTOR = _baseURIAttestor;
ATTESTATION_STATION = _attestationStation;
OPTIMIST_ALLOWLIST = _optimistAllowlist;
initialize(_name, _symbol);
}
......@@ -76,7 +90,7 @@ contract Optimist is ERC721BurnableUpgradeable, Semver {
string(
abi.encodePacked(
ATTESTATION_STATION.attestations(
ATTESTOR,
BASE_URI_ATTESTOR,
address(this),
bytes32("optimist.base-uri")
)
......@@ -105,17 +119,15 @@ contract Optimist is ERC721BurnableUpgradeable, Semver {
}
/**
* @notice Checks whether a given address is allowed to mint the Optimist NFT yet. Since the
* Optimist NFT will also be used as part of the Citizens House, mints are currently
* restricted. Eventually anyone will be able to mint.
* @notice Checks OptimistAllowlist to determine whether a given address is allowed to mint
* the Optimist NFT. Since the Optimist NFT will also be used as part of the
* Citizens House, mints are currently restricted. Eventually anyone will be able
* to mint.
*
* @return Whether or not the address is allowed to mint yet.
*/
function isOnAllowList(address _recipient) public view returns (bool) {
return
ATTESTATION_STATION
.attestations(ATTESTOR, _recipient, bytes32("optimist.can-mint"))
.length > 0;
return OPTIMIST_ALLOWLIST.isAllowedToMint(_recipient);
}
/**
......
......@@ -16,9 +16,13 @@ remappings = [
'@rari-capital/solmate/=node_modules/@rari-capital/solmate',
'forge-std/=node_modules/forge-std/src',
'ds-test/=node_modules/ds-test/src',
'multicall/=lib/multicall',
'@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/',
'@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/',
'@eth-optimism/contracts-bedrock/=../../node_modules/@eth-optimism/contracts-bedrock',
]
# The metadata hash can be removed from the bytecode by setting "none"
bytecode_hash = "none"
libs = ["node_modules", "lib"]
# Required to use `deployCode` to deploy the multicall contract which has incompatible version
fs_permissions = [{ access = "read", path = "./forge-artifacts/Multicall3.sol/Multicall3.json"}]
Subproject commit a1fa0644fa412cd3237ef7081458ecb2ffad7dbe
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment