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 ...@@ -55,6 +55,14 @@ func (ib *InteropAPI) ChainID(ctx context.Context) (supervisortypes.ChainID, err
return ib.backend.ChainID(ctx) 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 { func (ib *InteropAPI) ProvideL1(ctx context.Context, nextL1 eth.BlockRef) error {
return ib.backend.ProvideL1(ctx, nextL1) return ib.backend.ProvideL1(ctx, nextL1)
} }
...@@ -25,6 +25,7 @@ type L2Source interface { ...@@ -25,6 +25,7 @@ type L2Source interface {
L2BlockRefByNumber(ctx context.Context, num uint64) (eth.L2BlockRef, error) L2BlockRefByNumber(ctx context.Context, num uint64) (eth.L2BlockRef, error)
BlockRefByNumber(ctx context.Context, num uint64) (eth.BlockRef, error) BlockRefByNumber(ctx context.Context, num uint64) (eth.BlockRef, error)
FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, 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 { type L1Source interface {
...@@ -275,3 +276,30 @@ func (m *ManagedMode) BlockRefByNumber(ctx context.Context, num uint64) (eth.Blo ...@@ -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) { func (m *ManagedMode) ChainID(ctx context.Context) (supervisortypes.ChainID, error) {
return supervisortypes.ChainIDFromBig(m.cfg.L2ChainID), nil 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 ( ...@@ -8,6 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
) )
type SupervisorClient struct { type SupervisorClient struct {
...@@ -153,6 +154,16 @@ func (cl *SupervisorClient) UpdateLocalSafe(ctx context.Context, chainID types.C ...@@ -153,6 +154,16 @@ func (cl *SupervisorClient) UpdateLocalSafe(ctx context.Context, chainID types.C
lastDerived) 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() { func (cl *SupervisorClient) Close() {
cl.client.Close() cl.client.Close()
} }
...@@ -4,9 +4,11 @@ import ( ...@@ -4,9 +4,11 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"slices"
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/client"
...@@ -45,6 +47,7 @@ type SupervisorBackend struct { ...@@ -45,6 +47,7 @@ type SupervisorBackend struct {
// crossProcessors are used to index cross-chain dependency validity data once the log events are indexed // crossProcessors are used to index cross-chain dependency validity data once the log events are indexed
crossSafeProcessors locks.RWMap[types.ChainID, *cross.Worker] crossSafeProcessors locks.RWMap[types.ChainID, *cross.Worker]
crossUnsafeProcessors 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 controls the derivation or reset of the sync nodes
syncNodesController *syncnode.SyncNodesController syncNodesController *syncnode.SyncNodesController
...@@ -151,6 +154,10 @@ func (su *SupervisorBackend) initResources(ctx context.Context, cfg *config.Conf ...@@ -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) chainProcessor := processors.NewChainProcessor(su.logger, chainID, logProcessor, su.chainDBs, su.onIndexedLocalUnsafeData)
su.chainProcessors.Set(chainID, chainProcessor) su.chainProcessors.Set(chainID, chainProcessor)
} }
// initialize sync sources
for _, chainID := range chains {
su.syncSources.Set(chainID, nil)
}
if cfg.L1RPC != "" { if cfg.L1RPC != "" {
if err := su.attachL1RPC(ctx, cfg.L1RPC); err != nil { if err := su.attachL1RPC(ctx, cfg.L1RPC); err != nil {
...@@ -251,6 +258,10 @@ func (su *SupervisorBackend) AttachSyncNode(ctx context.Context, src syncnode.Sy ...@@ -251,6 +258,10 @@ func (su *SupervisorBackend) AttachSyncNode(ctx context.Context, src syncnode.Sy
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to attach sync source to processor: %w", err) 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) return su.syncNodesController.AttachNodeController(chainID, src, noSubscribe)
} }
...@@ -263,6 +274,15 @@ func (su *SupervisorBackend) AttachProcessorSource(chainID types.ChainID, src pr ...@@ -263,6 +274,15 @@ func (su *SupervisorBackend) AttachProcessorSource(chainID types.ChainID, src pr
return nil 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 { func (su *SupervisorBackend) attachL1RPC(ctx context.Context, l1RPCAddr string) error {
su.logger.Info("attaching L1 RPC to L1 processor", "rpc", l1RPCAddr) su.logger.Info("attaching L1 RPC to L1 processor", "rpc", l1RPCAddr)
...@@ -497,6 +517,46 @@ func (su *SupervisorBackend) L1BlockRefByNumber(ctx context.Context, number uint ...@@ -497,6 +517,46 @@ func (su *SupervisorBackend) L1BlockRefByNumber(ctx context.Context, number uint
return su.l1Accessor.L1BlockRefByNumber(ctx, number) 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 // Update methods
// ---------------------------- // ----------------------------
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
) )
type MockBackend struct { type MockBackend struct {
...@@ -70,6 +71,10 @@ func (m *MockBackend) CrossDerivedFrom(ctx context.Context, chainID types.ChainI ...@@ -70,6 +71,10 @@ func (m *MockBackend) CrossDerivedFrom(ctx context.Context, chainID types.ChainI
return eth.BlockRef{}, nil 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 { func (m *MockBackend) Close() error {
return nil return nil
} }
...@@ -25,6 +25,8 @@ type SyncSource interface { ...@@ -25,6 +25,8 @@ type SyncSource interface {
BlockRefByNumber(ctx context.Context, number uint64) (eth.BlockRef, error) BlockRefByNumber(ctx context.Context, number uint64) (eth.BlockRef, error)
FetchReceipts(ctx context.Context, blockHash common.Hash) (gethtypes.Receipts, error) FetchReceipts(ctx context.Context, blockHash common.Hash) (gethtypes.Receipts, error)
ChainID(ctx context.Context) (types.ChainID, 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 identifies the sync source
String() string String() string
} }
......
...@@ -69,6 +69,18 @@ func (rs *RPCSyncNode) ChainID(ctx context.Context) (types.ChainID, error) { ...@@ -69,6 +69,18 @@ func (rs *RPCSyncNode) ChainID(ctx context.Context) (types.ChainID, error) {
return chainID, err 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 { func (rs *RPCSyncNode) String() string {
return rs.name return rs.name
} }
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
) )
type AdminBackend interface { type AdminBackend interface {
...@@ -22,6 +23,7 @@ type QueryBackend interface { ...@@ -22,6 +23,7 @@ type QueryBackend interface {
CrossSafe(ctx context.Context, chainID types.ChainID) (types.DerivedIDPair, error) CrossSafe(ctx context.Context, chainID types.ChainID) (types.DerivedIDPair, error)
Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error) Finalized(ctx context.Context, chainID types.ChainID) (eth.BlockID, error)
FinalizedL1() eth.BlockRef FinalizedL1() eth.BlockRef
SuperRootAtTimestamp(ctx context.Context, timestamp hexutil.Uint64) (types.SuperRootResponse, error)
} }
type Backend interface { type Backend interface {
...@@ -69,6 +71,10 @@ func (q *QueryFrontend) CrossDerivedFrom(ctx context.Context, chainID types.Chai ...@@ -69,6 +71,10 @@ func (q *QueryFrontend) CrossDerivedFrom(ctx context.Context, chainID types.Chai
return q.Supervisor.CrossDerivedFrom(ctx, chainID, derived) 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 { type AdminFrontend struct {
Supervisor Backend Supervisor Backend
} }
......
...@@ -352,3 +352,72 @@ type ManagedEvent struct { ...@@ -352,3 +352,72 @@ type ManagedEvent struct {
DerivationUpdate *DerivedBlockRefPair `json:"derivationUpdate,omitempty"` DerivationUpdate *DerivedBlockRefPair `json:"derivationUpdate,omitempty"`
ExhaustL1 *DerivedBlockRefPair `json:"exhaustL1,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