Commit ca1f7773 authored by Inphi's avatar Inphi Committed by GitHub

op-supervisor: Implement supervisor_superRootAtTimestamp RPC (#13736)

* supervisor: Early start of RPC

* op-supervisor: Add supervisor_superRootAtTimstamp RPC

Add a new RPC to get the super root at a given timestamp.

* op-supervisor: add chainID to SuperRootResponse

* op-supervisor: fix comment

* op-supervisor: Add super root to response

* return stubbed canonical output for interop_pendingOutputV0AtTimestamp

* add ref to reorg PR

---------
Co-authored-by: default avatarAdrian Sutton <adrian@oplabs.co>
parent 395c01d0
......@@ -55,6 +55,14 @@ func (ib *InteropAPI) ChainID(ctx context.Context) (supervisortypes.ChainID, err
return ib.backend.ChainID(ctx)
}
func (ib *InteropAPI) OutputV0AtTimestamp(ctx context.Context, timestamp uint64) (*eth.OutputV0, error) {
return ib.backend.OutputV0AtTimestamp(ctx, timestamp)
}
func (ib *InteropAPI) PendingOutputV0ATTimestamp(ctx context.Context, timestamp uint64) (*eth.OutputV0, error) {
return ib.backend.PendingOutputV0AtTimestamp(ctx, timestamp)
}
func (ib *InteropAPI) ProvideL1(ctx context.Context, nextL1 eth.BlockRef) error {
return ib.backend.ProvideL1(ctx, nextL1)
}
......@@ -25,6 +25,7 @@ type L2Source interface {
L2BlockRefByNumber(ctx context.Context, num uint64) (eth.L2BlockRef, error)
BlockRefByNumber(ctx context.Context, num uint64) (eth.BlockRef, error)
FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error)
OutputV0AtBlock(ctx context.Context, blockHash common.Hash) (*eth.OutputV0, error)
}
type L1Source interface {
......@@ -275,3 +276,30 @@ func (m *ManagedMode) BlockRefByNumber(ctx context.Context, num uint64) (eth.Blo
func (m *ManagedMode) ChainID(ctx context.Context) (supervisortypes.ChainID, error) {
return supervisortypes.ChainIDFromBig(m.cfg.L2ChainID), nil
}
func (m *ManagedMode) OutputV0AtTimestamp(ctx context.Context, timestamp uint64) (*eth.OutputV0, error) {
num, err := m.cfg.TargetBlockNumber(timestamp)
if err != nil {
return nil, err
}
ref, err := m.l2.L2BlockRefByNumber(ctx, num)
if err != nil {
return nil, err
}
return m.l2.OutputV0AtBlock(ctx, ref.Hash)
}
func (m *ManagedMode) PendingOutputV0AtTimestamp(ctx context.Context, timestamp uint64) (*eth.OutputV0, error) {
num, err := m.cfg.TargetBlockNumber(timestamp)
if err != nil {
return nil, err
}
ref, err := m.l2.L2BlockRefByNumber(ctx, num)
if err != nil {
return nil, err
}
// TODO: Once interop reorgs are supported (see #13645), replace with the output root preimage of an actual pending
// block contained in the optimistic block deposited transaction - https://github.com/ethereum-optimism/specs/pull/489
// For now, we use the output at timestamp as-if it didn't contain invalid messages for happy path testing.
return m.l2.OutputV0AtBlock(ctx, ref.Hash)
}
......@@ -8,6 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
type SupervisorClient struct {
......@@ -153,6 +154,16 @@ func (cl *SupervisorClient) UpdateLocalSafe(ctx context.Context, chainID types.C
lastDerived)
}
func (cl *SupervisorClient) SuperRootAtTimestamp(ctx context.Context, timestamp hexutil.Uint64) (types.SuperRootResponse, error) {
var result types.SuperRootResponse
err := cl.client.CallContext(
ctx,
&result,
"supervisor_superRootAtTimestamp",
timestamp)
return result, err
}
func (cl *SupervisorClient) Close() {
cl.client.Close()
}
......@@ -4,9 +4,11 @@ import (
"context"
"errors"
"fmt"
"slices"
"sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/client"
......@@ -45,6 +47,7 @@ type SupervisorBackend struct {
// crossProcessors are used to index cross-chain dependency validity data once the log events are indexed
crossSafeProcessors locks.RWMap[types.ChainID, *cross.Worker]
crossUnsafeProcessors locks.RWMap[types.ChainID, *cross.Worker]
syncSources locks.RWMap[types.ChainID, syncnode.SyncSource]
// syncNodesController controls the derivation or reset of the sync nodes
syncNodesController *syncnode.SyncNodesController
......@@ -151,6 +154,10 @@ func (su *SupervisorBackend) initResources(ctx context.Context, cfg *config.Conf
chainProcessor := processors.NewChainProcessor(su.logger, chainID, logProcessor, su.chainDBs, su.onIndexedLocalUnsafeData)
su.chainProcessors.Set(chainID, chainProcessor)
}
// initialize sync sources
for _, chainID := range chains {
su.syncSources.Set(chainID, nil)
}
if cfg.L1RPC != "" {
if err := su.attachL1RPC(ctx, cfg.L1RPC); err != nil {
......@@ -251,6 +258,10 @@ func (su *SupervisorBackend) AttachSyncNode(ctx context.Context, src syncnode.Sy
if err != nil {
return nil, fmt.Errorf("failed to attach sync source to processor: %w", err)
}
err = su.AttachSyncSource(chainID, src)
if err != nil {
return nil, fmt.Errorf("failed to attach sync source to node: %w", err)
}
return su.syncNodesController.AttachNodeController(chainID, src, noSubscribe)
}
......@@ -263,6 +274,15 @@ func (su *SupervisorBackend) AttachProcessorSource(chainID types.ChainID, src pr
return nil
}
func (su *SupervisorBackend) AttachSyncSource(chainID types.ChainID, src syncnode.SyncSource) error {
_, ok := su.syncSources.Get(chainID)
if !ok {
return fmt.Errorf("unknown chain %s, cannot attach RPC to sync source", chainID)
}
su.syncSources.Set(chainID, src)
return nil
}
func (su *SupervisorBackend) attachL1RPC(ctx context.Context, l1RPCAddr string) error {
su.logger.Info("attaching L1 RPC to L1 processor", "rpc", l1RPCAddr)
......@@ -497,6 +517,46 @@ func (su *SupervisorBackend) L1BlockRefByNumber(ctx context.Context, number uint
return su.l1Accessor.L1BlockRefByNumber(ctx, number)
}
func (su *SupervisorBackend) SuperRootAtTimestamp(ctx context.Context, timestamp hexutil.Uint64) (types.SuperRootResponse, error) {
chains := su.depSet.Chains()
slices.SortFunc(chains, func(a, b types.ChainID) int {
return a.Cmp(b)
})
chainInfos := make([]types.ChainRootInfo, len(chains))
superRootChains := make([]eth.ChainIDAndOutput, len(chains))
for i, chainID := range chains {
src, ok := su.syncSources.Get(chainID)
if !ok {
su.logger.Error("bug: unknown chain %s, cannot get sync source", chainID)
return types.SuperRootResponse{}, fmt.Errorf("unknown chain %s, cannot get sync source", chainID)
}
output, err := src.OutputV0AtTimestamp(ctx, uint64(timestamp))
if err != nil {
return types.SuperRootResponse{}, err
}
pending, err := src.PendingOutputV0AtTimestamp(ctx, uint64(timestamp))
if err != nil {
return types.SuperRootResponse{}, err
}
canonicalRoot := eth.OutputRoot(output)
chainInfos[i] = types.ChainRootInfo{
ChainID: chainID,
Canonical: canonicalRoot,
Pending: pending.Marshal(),
}
superRootChains[i] = eth.ChainIDAndOutput{ChainID: chainID.ToBig().Uint64(), Output: canonicalRoot}
}
superRoot := eth.SuperRoot(&eth.SuperV1{
Timestamp: uint64(timestamp),
Chains: superRootChains,
})
return types.SuperRootResponse{
Timestamp: uint64(timestamp),
SuperRoot: superRoot,
Chains: chainInfos,
}, nil
}
// Update methods
// ----------------------------
......
......@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
type MockBackend struct {
......@@ -70,6 +71,10 @@ func (m *MockBackend) CrossDerivedFrom(ctx context.Context, chainID types.ChainI
return eth.BlockRef{}, nil
}
func (m *MockBackend) SuperRootAtTimestamp(ctx context.Context, timestamp hexutil.Uint64) (types.SuperRootResponse, error) {
return types.SuperRootResponse{}, nil
}
func (m *MockBackend) Close() error {
return nil
}
......@@ -25,6 +25,8 @@ type SyncSource interface {
BlockRefByNumber(ctx context.Context, number uint64) (eth.BlockRef, error)
FetchReceipts(ctx context.Context, blockHash common.Hash) (gethtypes.Receipts, error)
ChainID(ctx context.Context) (types.ChainID, error)
OutputV0AtTimestamp(ctx context.Context, timestamp uint64) (*eth.OutputV0, error)
PendingOutputV0AtTimestamp(ctx context.Context, timestamp uint64) (*eth.OutputV0, error)
// String identifies the sync source
String() string
}
......
......@@ -69,6 +69,18 @@ func (rs *RPCSyncNode) ChainID(ctx context.Context) (types.ChainID, error) {
return chainID, err
}
func (rs *RPCSyncNode) OutputV0AtTimestamp(ctx context.Context, timestamp uint64) (*eth.OutputV0, error) {
var out *eth.OutputV0
err := rs.cl.CallContext(ctx, &out, "interop_outputV0AtTimestamp", timestamp)
return out, err
}
func (rs *RPCSyncNode) PendingOutputV0AtTimestamp(ctx context.Context, timestamp uint64) (*eth.OutputV0, error) {
var out *eth.OutputV0
err := rs.cl.CallContext(ctx, &out, "interop_pendingOutputV0AtTimestamp", timestamp)
return out, err
}
func (rs *RPCSyncNode) String() string {
return rs.name
}
......
......@@ -6,6 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
type AdminBackend interface {
......@@ -22,6 +23,7 @@ type QueryBackend interface {
CrossSafe(ctx context.Context, chainID types.ChainID) (types.DerivedIDPair, error)
Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error)
FinalizedL1() eth.BlockRef
SuperRootAtTimestamp(ctx context.Context, timestamp hexutil.Uint64) (types.SuperRootResponse, error)
}
type Backend interface {
......@@ -69,6 +71,10 @@ func (q *QueryFrontend) CrossDerivedFrom(ctx context.Context, chainID types.Chai
return q.Supervisor.CrossDerivedFrom(ctx, chainID, derived)
}
func (q *QueryFrontend) SuperRootAtTimestamp(ctx context.Context, timestamp hexutil.Uint64) (types.SuperRootResponse, error) {
return q.Supervisor.SuperRootAtTimestamp(ctx, timestamp)
}
type AdminFrontend struct {
Supervisor Backend
}
......
......@@ -352,3 +352,72 @@ type ManagedEvent struct {
DerivationUpdate *DerivedBlockRefPair `json:"derivationUpdate,omitempty"`
ExhaustL1 *DerivedBlockRefPair `json:"exhaustL1,omitempty"`
}
type ChainRootInfo struct {
ChainID ChainID `json:"chainID"`
// Canonical is the output root of the latest canonical block at a particular Timestamp.
Canonical eth.Bytes32 `json:"canonical"`
// Pending is the output root preimage for the latest block at a particular Timestamp prior to validation of
// executing messages. If the original block was valid, this will be the preimage of the
// output root from the Canonical array. If it was invalid, it will be the output root preimage from the
// Optimistic Block Deposited Transaction added to the deposit-only block.
Pending []byte `json:"pending"`
}
type chainRootInfoMarshalling struct {
ChainID ChainID `json:"chainID"`
Canonical common.Hash `json:"canonical"`
Pending hexutil.Bytes `json:"pending"`
}
func (i ChainRootInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(&chainRootInfoMarshalling{
ChainID: i.ChainID,
Canonical: common.Hash(i.Canonical),
Pending: i.Pending,
})
}
func (i *ChainRootInfo) UnmarshalJSON(input []byte) error {
var dec chainRootInfoMarshalling
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
i.ChainID = dec.ChainID
i.Canonical = eth.Bytes32(dec.Canonical)
i.Pending = dec.Pending
return nil
}
type SuperRootResponse struct {
Timestamp uint64 `json:"timestamp"`
SuperRoot eth.Bytes32 `json:"superRoot"`
// Chains is the list of ChainRootInfo for each chain in the dependency set.
// It represents the state of the chain at or before the Timestamp.
Chains []ChainRootInfo `json:"chains"`
}
type superRootResponseMarshalling struct {
Timestamp hexutil.Uint64 `json:"timestamp"`
SuperRoot common.Hash `json:"superRoot"`
Chains []ChainRootInfo `json:"chains"`
}
func (r SuperRootResponse) MarshalJSON() ([]byte, error) {
return json.Marshal(&superRootResponseMarshalling{
Timestamp: hexutil.Uint64(r.Timestamp),
SuperRoot: common.Hash(r.SuperRoot),
Chains: r.Chains,
})
}
func (r *SuperRootResponse) UnmarshalJSON(input []byte) error {
var dec superRootResponseMarshalling
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
r.Timestamp = uint64(dec.Timestamp)
r.SuperRoot = eth.Bytes32(dec.SuperRoot)
r.Chains = dec.Chains
return nil
}
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