Node/EVM: Scroll finality (#3518)

This commit is contained in:
bruce-riley 2023-11-13 09:47:12 -06:00 committed by GitHub
parent 38f02fa2fc
commit 3f0abc727c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 50 additions and 30 deletions

View File

@ -20,11 +20,12 @@ import (
// BatchPollConnector uses batch requests to poll for latest, safe and finalized blocks.
type BatchPollConnector struct {
Connector
logger *zap.Logger
Delay time.Duration
blockFeed ethEvent.Feed
errFeed ethEvent.Feed
batchData []BatchEntry
logger *zap.Logger
Delay time.Duration
blockFeed ethEvent.Feed
errFeed ethEvent.Feed
batchData []BatchEntry
generateSafe bool
}
type (
@ -43,18 +44,22 @@ type (
const MAX_GAP_BATCH_SIZE uint64 = 5
func NewBatchPollConnector(ctx context.Context, logger *zap.Logger, baseConnector Connector, delay time.Duration) (*BatchPollConnector, error) {
func NewBatchPollConnector(ctx context.Context, logger *zap.Logger, baseConnector Connector, safeSupported bool, delay time.Duration) (*BatchPollConnector, error) {
// Create the batch data in the order we want to report them to the watcher, so finalized is most important, latest is least.
batchData := []BatchEntry{
{tag: "finalized", finality: Finalized},
{tag: "safe", finality: Safe},
}
if safeSupported {
batchData = append(batchData, BatchEntry{tag: "safe", finality: Safe})
}
connector := &BatchPollConnector{
Connector: baseConnector,
logger: logger,
Delay: delay,
batchData: batchData,
Connector: baseConnector,
logger: logger,
Delay: delay,
batchData: batchData,
generateSafe: !safeSupported,
}
err := supervisor.Run(ctx, "batchPoller", common.WrapWithScissors(connector.runFromSupervisor, "batchPoller"))
if err != nil {
@ -195,6 +200,9 @@ func (b *BatchPollConnector) pollBlocks(ctx context.Context, logger *zap.Logger,
}
b.blockFeed.Send(block)
if b.generateSafe && b.batchData[idx].finality == Finalized {
b.blockFeed.Send(block.Copy(Safe))
}
lastPublishedBlock = block
}
}
@ -205,6 +213,9 @@ func (b *BatchPollConnector) pollBlocks(ctx context.Context, logger *zap.Logger,
if !errorFound {
// The original value of newBlocks is still good.
b.blockFeed.Send(newBlock)
if b.generateSafe && b.batchData[idx].finality == Finalized {
b.blockFeed.Send(newBlock.Copy(Safe))
}
} else {
newBlocks[idx] = lastPublishedBlock
}

View File

@ -219,12 +219,6 @@ func (w *Watcher) Run(parentCtx context.Context) error {
ctx, watcherContextCancelFunc := context.WithCancel(parentCtx)
defer watcherContextCancelFunc()
var useFinalizedBlocks bool
useFinalizedBlocks, err = w.getFinality(ctx)
if err != nil {
return fmt.Errorf("failed to determine finality: %w", err)
}
// Initialize gossip metrics (we want to broadcast the address even if we're not yet syncing)
p2p.DefaultRegistry.SetNetworkStats(w.chainID, &gossipv1.Heartbeat_Network{
ContractAddress: w.contract.Hex(),
@ -233,15 +227,24 @@ func (w *Watcher) Run(parentCtx context.Context) error {
timeout, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel()
if useFinalizedBlocks {
logger.Info("using finalized blocks")
finalizedPollingSupported, safePollingSupported, err := w.getFinality(ctx)
if err != nil {
return fmt.Errorf("failed to determine finality: %w", err)
}
if finalizedPollingSupported {
if safePollingSupported {
logger.Info("polling for finalized and safe blocks")
} else {
logger.Info("polling for finalized blocks, will generate safe blocks")
}
baseConnector, err := connectors.NewEthereumBaseConnector(timeout, w.networkName, w.url, w.contract, logger)
if err != nil {
ethConnectionErrors.WithLabelValues(w.networkName, "dial_error").Inc()
p2p.DefaultRegistry.AddErrorCount(w.chainID, 1)
return fmt.Errorf("dialing eth client failed: %w", err)
}
w.ethConn, err = connectors.NewBatchPollConnector(ctx, logger, baseConnector, 1000*time.Millisecond)
w.ethConn, err = connectors.NewBatchPollConnector(ctx, logger, baseConnector, safePollingSupported, 1000*time.Millisecond)
if err != nil {
ethConnectionErrors.WithLabelValues(w.networkName, "dial_error").Inc()
p2p.DefaultRegistry.AddErrorCount(w.chainID, 1)
@ -848,9 +851,10 @@ func fetchCurrentGuardianSet(ctx context.Context, ethConn connectors.Connector)
}
// getFinality determines if the chain supports "finalized" and "safe". This is hard coded so it requires thought to change something. However, it also reads the RPC
// to make sure the node actually supports the expected values, and returns an error if it doesn't. Note that we do not support using finalized mode but not safe mode.
func (w *Watcher) getFinality(ctx context.Context) (bool, error) {
// to make sure the node actually supports the expected values, and returns an error if it doesn't. Note that we do not support using safe mode but not finalized mode.
func (w *Watcher) getFinality(ctx context.Context) (bool, bool, error) {
finalized := false
safe := false
if w.unsafeDevMode {
// Devnet supports finalized and safe (although they returns the same value as latest).
finalized = true
@ -862,9 +866,12 @@ func (w *Watcher) getFinality(ctx context.Context) (bool, error) {
w.chainID == vaa.ChainIDKarura ||
w.chainID == vaa.ChainIDMoonbeam ||
w.chainID == vaa.ChainIDOptimism ||
w.chainID == vaa.ChainIDScroll ||
w.chainID == vaa.ChainIDSepolia {
finalized = true
safe = true
} else if w.chainID == vaa.ChainIDScroll {
// As of 11/10/2023 Scroll supports polling for finalized but not safe.
finalized = true
}
// If finalized / safe should be supported, read the RPC to make sure they actually are.
@ -874,7 +881,7 @@ func (w *Watcher) getFinality(ctx context.Context) (bool, error) {
c, err := rpc.DialContext(timeout, w.url)
if err != nil {
return false, fmt.Errorf("failed to connect to endpoint: %w", err)
return false, false, fmt.Errorf("failed to connect to endpoint: %w", err)
}
type Marshaller struct {
@ -883,17 +890,19 @@ func (w *Watcher) getFinality(ctx context.Context) (bool, error) {
var m Marshaller
err = c.CallContext(ctx, &m, "eth_getBlockByNumber", "finalized", false)
if err != nil {
return false, fmt.Errorf("finalized not supported by the node when it should be: %w", err)
if err != nil || m.Number == nil {
return false, false, fmt.Errorf("finalized not supported by the node when it should be")
}
err = c.CallContext(ctx, &m, "eth_getBlockByNumber", "safe", false)
if err != nil {
return false, fmt.Errorf("safe not supported by the node when it should be: %w", err)
if safe {
err = c.CallContext(ctx, &m, "eth_getBlockByNumber", "safe", false)
if err != nil || m.Number == nil {
return false, false, fmt.Errorf("safe not supported by the node when it should be")
}
}
}
return finalized, nil
return finalized, safe, nil
}
// SetL1Finalizer is used to set the layer one finalizer.