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 }