github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/rollup/sync_service/sync_service.go (about)

     1  package sync_service
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"time"
     8  
     9  	"github.com/scroll-tech/go-ethereum/core"
    10  	"github.com/scroll-tech/go-ethereum/core/rawdb"
    11  	"github.com/scroll-tech/go-ethereum/ethdb"
    12  	"github.com/scroll-tech/go-ethereum/event"
    13  	"github.com/scroll-tech/go-ethereum/log"
    14  	"github.com/scroll-tech/go-ethereum/node"
    15  	"github.com/scroll-tech/go-ethereum/params"
    16  )
    17  
    18  const (
    19  	// DefaultFetchBlockRange is the number of blocks that we collect in a single eth_getLogs query.
    20  	DefaultFetchBlockRange = uint64(100)
    21  
    22  	// DefaultPollInterval is the frequency at which we query for new L1 messages.
    23  	DefaultPollInterval = time.Second * 10
    24  
    25  	// LogProgressInterval is the frequency at which we log progress.
    26  	LogProgressInterval = time.Second * 10
    27  
    28  	// DbWriteThresholdBytes is the size of batched database writes in bytes.
    29  	DbWriteThresholdBytes = 10 * 1024
    30  
    31  	// DbWriteThresholdBlocks is the number of blocks scanned after which we write to the database
    32  	// even if we have not collected DbWriteThresholdBytes bytes of data yet. This way, if there is
    33  	// a long section of L1 blocks with no messages and we stop or crash, we will not need to re-scan
    34  	// this secion.
    35  	DbWriteThresholdBlocks = 1000
    36  )
    37  
    38  // SyncService collects all L1 messages and stores them in a local database.
    39  type SyncService struct {
    40  	ctx                  context.Context
    41  	cancel               context.CancelFunc
    42  	client               *BridgeClient
    43  	db                   ethdb.Database
    44  	msgCountFeed         event.Feed
    45  	pollInterval         time.Duration
    46  	latestProcessedBlock uint64
    47  	scope                event.SubscriptionScope
    48  }
    49  
    50  func NewSyncService(ctx context.Context, genesisConfig *params.ChainConfig, nodeConfig *node.Config, db ethdb.Database, l1Client EthClient) (*SyncService, error) {
    51  	// terminate if the caller does not provide an L1 client (e.g. in tests)
    52  	if l1Client == nil || (reflect.ValueOf(l1Client).Kind() == reflect.Ptr && reflect.ValueOf(l1Client).IsNil()) {
    53  		log.Warn("No L1 client provided, L1 sync service will not run")
    54  		return nil, nil
    55  	}
    56  
    57  	if genesisConfig.Scroll.L1Config == nil {
    58  		return nil, fmt.Errorf("missing L1 config in genesis")
    59  	}
    60  
    61  	client, err := newBridgeClient(ctx, l1Client, genesisConfig.Scroll.L1Config.L1ChainId, nodeConfig.L1Confirmations, genesisConfig.Scroll.L1Config.L1MessageQueueAddress)
    62  	if err != nil {
    63  		return nil, fmt.Errorf("failed to initialize bridge client: %w", err)
    64  	}
    65  
    66  	// assume deployment block has 0 messages
    67  	latestProcessedBlock := nodeConfig.L1DeploymentBlock
    68  	block := rawdb.ReadSyncedL1BlockNumber(db)
    69  	if block != nil {
    70  		// restart from latest synced block number
    71  		latestProcessedBlock = *block
    72  	}
    73  
    74  	ctx, cancel := context.WithCancel(ctx)
    75  
    76  	service := SyncService{
    77  		ctx:                  ctx,
    78  		cancel:               cancel,
    79  		client:               client,
    80  		db:                   db,
    81  		pollInterval:         DefaultPollInterval,
    82  		latestProcessedBlock: latestProcessedBlock,
    83  	}
    84  
    85  	return &service, nil
    86  }
    87  
    88  func (s *SyncService) Start() {
    89  	if s == nil {
    90  		return
    91  	}
    92  
    93  	// wait for initial sync before starting node
    94  	log.Info("Starting L1 message sync service", "latestProcessedBlock", s.latestProcessedBlock)
    95  
    96  	// block node startup during initial sync and print some helpful logs
    97  	latestConfirmed, err := s.client.getLatestConfirmedBlockNumber(s.ctx)
    98  	if err == nil && latestConfirmed > s.latestProcessedBlock+1000 {
    99  		log.Warn("Running initial sync of L1 messages before starting l2geth, this might take a while...")
   100  		s.fetchMessages()
   101  		log.Info("L1 message initial sync completed", "latestProcessedBlock", s.latestProcessedBlock)
   102  	}
   103  
   104  	go func() {
   105  		t := time.NewTicker(s.pollInterval)
   106  		defer t.Stop()
   107  
   108  		for {
   109  			// don't wait for ticker during startup
   110  			s.fetchMessages()
   111  
   112  			select {
   113  			case <-s.ctx.Done():
   114  				return
   115  			case <-t.C:
   116  				continue
   117  			}
   118  		}
   119  	}()
   120  }
   121  
   122  func (s *SyncService) Stop() {
   123  	if s == nil {
   124  		return
   125  	}
   126  
   127  	log.Info("Stopping sync service")
   128  
   129  	// Unsubscribe all subscriptions registered
   130  	s.scope.Close()
   131  
   132  	if s.cancel != nil {
   133  		s.cancel()
   134  	}
   135  }
   136  
   137  // SubscribeNewL1MsgsEvent registers a subscription of NewL1MsgsEvent and
   138  // starts sending event to the given channel.
   139  func (s *SyncService) SubscribeNewL1MsgsEvent(ch chan<- core.NewL1MsgsEvent) event.Subscription {
   140  	return s.scope.Track(s.msgCountFeed.Subscribe(ch))
   141  }
   142  
   143  func (s *SyncService) fetchMessages() {
   144  	latestConfirmed, err := s.client.getLatestConfirmedBlockNumber(s.ctx)
   145  	if err != nil {
   146  		log.Warn("Failed to get latest confirmed block number", "err", err)
   147  		return
   148  	}
   149  
   150  	log.Trace("Sync service fetchMessages", "latestProcessedBlock", s.latestProcessedBlock, "latestConfirmed", latestConfirmed)
   151  
   152  	batchWriter := s.db.NewBatch()
   153  	numBlocksPendingDbWrite := uint64(0)
   154  	numMessagesPendingDbWrite := 0
   155  
   156  	// helper function to flush database writes cached in memory
   157  	flush := func(lastBlock uint64) {
   158  		// update sync progress
   159  		rawdb.WriteSyncedL1BlockNumber(batchWriter, lastBlock)
   160  
   161  		// write batch in a single transaction
   162  		err := batchWriter.Write()
   163  		if err != nil {
   164  			// crash on database error, no risk of inconsistency here
   165  			log.Crit("Failed to write L1 messages to database", "err", err)
   166  		}
   167  
   168  		batchWriter.Reset()
   169  		numBlocksPendingDbWrite = 0
   170  
   171  		if numMessagesPendingDbWrite > 0 {
   172  			s.msgCountFeed.Send(core.NewL1MsgsEvent{Count: numMessagesPendingDbWrite})
   173  			numMessagesPendingDbWrite = 0
   174  		}
   175  
   176  		s.latestProcessedBlock = lastBlock
   177  	}
   178  
   179  	// ticker for logging progress
   180  	t := time.NewTicker(LogProgressInterval)
   181  	numMsgsCollected := 0
   182  
   183  	// query in batches
   184  	for from := s.latestProcessedBlock + 1; from <= latestConfirmed; from += DefaultFetchBlockRange {
   185  		select {
   186  		case <-s.ctx.Done():
   187  			// flush pending writes to database
   188  			if from > 0 {
   189  				flush(from - 1)
   190  			}
   191  			return
   192  		case <-t.C:
   193  			progress := 100 * float64(s.latestProcessedBlock) / float64(latestConfirmed)
   194  			log.Info("Syncing L1 messages", "processed", s.latestProcessedBlock, "confirmed", latestConfirmed, "collected", numMsgsCollected, "progress(%)", progress)
   195  		default:
   196  		}
   197  
   198  		to := from + DefaultFetchBlockRange - 1
   199  		if to > latestConfirmed {
   200  			to = latestConfirmed
   201  		}
   202  
   203  		msgs, err := s.client.fetchMessagesInRange(s.ctx, from, to)
   204  		if err != nil {
   205  			// flush pending writes to database
   206  			if from > 0 {
   207  				flush(from - 1)
   208  			}
   209  			log.Warn("Failed to fetch L1 messages in range", "fromBlock", from, "toBlock", to, "err", err)
   210  			return
   211  		}
   212  
   213  		if len(msgs) > 0 {
   214  			log.Debug("Received new L1 events", "fromBlock", from, "toBlock", to, "count", len(msgs))
   215  			rawdb.WriteL1Messages(batchWriter, msgs) // collect messages in memory
   216  			numMsgsCollected += len(msgs)
   217  		}
   218  
   219  		numBlocksPendingDbWrite += to - from
   220  		numMessagesPendingDbWrite += len(msgs)
   221  
   222  		// flush new messages to database periodically
   223  		if to == latestConfirmed || batchWriter.ValueSize() >= DbWriteThresholdBytes || numBlocksPendingDbWrite >= DbWriteThresholdBlocks {
   224  			flush(to)
   225  		}
   226  	}
   227  }