hermez-node/synchronizer/synchronizer.go
synchronizer/synchronizer.goRaw
1package synchronizer
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7 "math/big"
8 "sync"
9 "time"
10
11 "github.com/ethereum/go-ethereum"
12 "github.com/hermeznetwork/hermez-node/common"
13 "github.com/hermeznetwork/hermez-node/db/historydb"
14 "github.com/hermeznetwork/hermez-node/db/statedb"
15 "github.com/hermeznetwork/hermez-node/eth"
16 "github.com/hermeznetwork/hermez-node/log"
17 "github.com/hermeznetwork/hermez-node/txprocessor"
18 "github.com/hermeznetwork/tracerr"
19)
20
21// Stats of the syncrhonizer
22type Stats struct {
23 Eth struct {
24 RefreshPeriod time.Duration
25 Updated time.Time
26 FirstBlockNum int64
27 LastBlock common.Block
28 LastBatch int64
29 }
30 Sync struct {
31 Updated time.Time
32 LastBlock common.Block
33 LastBatch int64
34 // LastL1BatchBlock is the last ethereum block in which an
35 // l1Batch was forged
36 LastL1BatchBlock int64
37 LastForgeL1TxsNum int64
38 Auction struct {
39 CurrentSlot common.Slot
40 }
41 }
42}
43
44// Synced returns true if the Synchronizer is up to date with the last ethereum block
45func (s *Stats) Synced() bool {
46 return s.Eth.LastBlock.Num == s.Sync.LastBlock.Num
47}
48
49// TODO(Edu): Consider removing all the mutexes from StatsHolder, make
50// Synchronizer.Stats not thread-safe, don't pass the synchronizer to the
51// debugAPI, and have a copy of the Stats in the DebugAPI that the node passes
52// when the Sync updates.
53
54// StatsHolder stores stats and that allows reading and writing them
55// concurrently
56type StatsHolder struct {
57 Stats
58 rw sync.RWMutex
59}
60
61// NewStatsHolder creates a new StatsHolder
62func NewStatsHolder(firstBlockNum int64, refreshPeriod time.Duration) *StatsHolder {
63 stats := Stats{}
64 stats.Eth.RefreshPeriod = refreshPeriod
65 stats.Eth.FirstBlockNum = firstBlockNum
66 return &StatsHolder{Stats: stats}
67}
68
69// UpdateCurrentSlot updates the auction stats
70func (s *StatsHolder) UpdateCurrentSlot(slot common.Slot) {
71 s.rw.Lock()
72 s.Sync.Auction.CurrentSlot = slot
73 s.rw.Unlock()
74}
75
76// UpdateSync updates the synchronizer stats
77func (s *StatsHolder) UpdateSync(lastBlock *common.Block, lastBatch *common.BatchNum,
78 lastL1BatchBlock *int64, lastForgeL1TxsNum *int64) {
79 now := time.Now()
80 s.rw.Lock()
81 s.Sync.LastBlock = *lastBlock
82 if lastBatch != nil {
83 s.Sync.LastBatch = int64(*lastBatch)
84 }
85 if lastL1BatchBlock != nil {
86 s.Sync.LastL1BatchBlock = *lastL1BatchBlock
87 s.Sync.LastForgeL1TxsNum = *lastForgeL1TxsNum
88 }
89 s.Sync.Updated = now
90 s.rw.Unlock()
91}
92
93// UpdateEth updates the ethereum stats, only if the previous stats expired
94func (s *StatsHolder) UpdateEth(ethClient eth.ClientInterface) error {
95 now := time.Now()
96 s.rw.RLock()
97 elapsed := now.Sub(s.Eth.Updated)
98 s.rw.RUnlock()
99 if elapsed < s.Eth.RefreshPeriod {
100 return nil
101 }
102
103 lastBlock, err := ethClient.EthBlockByNumber(context.TODO(), -1)
104 if err != nil {
105 return tracerr.Wrap(err)
106 }
107 lastBatch, err := ethClient.RollupLastForgedBatch()
108 if err != nil {
109 return tracerr.Wrap(err)
110 }
111 s.rw.Lock()
112 s.Eth.Updated = now
113 s.Eth.LastBlock = *lastBlock
114 s.Eth.LastBatch = lastBatch
115 s.rw.Unlock()
116 return nil
117}
118
119// CopyStats returns a copy of the inner Stats
120func (s *StatsHolder) CopyStats() *Stats {
121 s.rw.RLock()
122 sCopy := s.Stats
123 if s.Sync.Auction.CurrentSlot.BidValue != nil {
124 sCopy.Sync.Auction.CurrentSlot.BidValue =
125 common.CopyBigInt(s.Sync.Auction.CurrentSlot.BidValue)
126 }
127 if s.Sync.Auction.CurrentSlot.DefaultSlotBid != nil {
128 sCopy.Sync.Auction.CurrentSlot.DefaultSlotBid =
129 common.CopyBigInt(s.Sync.Auction.CurrentSlot.DefaultSlotBid)
130 }
131 s.rw.RUnlock()
132 return &sCopy
133}
134
135func (s *StatsHolder) blocksPerc() float64 {
136 syncLastBlockNum := s.Sync.LastBlock.Num
137 if s.Sync.LastBlock.Num == 0 {
138 syncLastBlockNum = s.Eth.FirstBlockNum - 1
139 }
140 return float64(syncLastBlockNum-(s.Eth.FirstBlockNum-1)) * 100.0 /
141 float64(s.Eth.LastBlock.Num-(s.Eth.FirstBlockNum-1))
142}
143
144func (s *StatsHolder) batchesPerc(batchNum int64) float64 {
145 return float64(batchNum) * 100.0 /
146 float64(s.Eth.LastBatch)
147}
148
149// StartBlockNums sets the first block used to start tracking the smart
150// contracts
151type StartBlockNums struct {
152 Rollup int64
153 Auction int64
154 WDelayer int64
155}
156
157// SCVariables joins all the smart contract variables in a single struct
158type SCVariables struct {
159 Rollup common.RollupVariables `validate:"required"`
160 Auction common.AuctionVariables `validate:"required"`
161 WDelayer common.WDelayerVariables `validate:"required"`
162}
163
164// SCVariablesPtr joins all the smart contract variables as pointers in a single
165// struct
166type SCVariablesPtr struct {
167 Rollup *common.RollupVariables `validate:"required"`
168 Auction *common.AuctionVariables `validate:"required"`
169 WDelayer *common.WDelayerVariables `validate:"required"`
170}
171
172// SCConsts joins all the smart contract constants in a single struct
173type SCConsts struct {
174 Rollup common.RollupConstants
175 Auction common.AuctionConstants
176 WDelayer common.WDelayerConstants
177}
178
179// Config is the Synchronizer configuration
180type Config struct {
181 StatsRefreshPeriod time.Duration
182}
183
184// Synchronizer implements the Synchronizer type
185type Synchronizer struct {
186 ethClient eth.ClientInterface
187 consts SCConsts
188 historyDB *historydb.HistoryDB
189 stateDB *statedb.StateDB
190 cfg Config
191 initVars SCVariables
192 startBlockNum int64
193 vars SCVariables
194 stats *StatsHolder
195}
196
197// NewSynchronizer creates a new Synchronizer
198func NewSynchronizer(ethClient eth.ClientInterface, historyDB *historydb.HistoryDB,
199 stateDB *statedb.StateDB, cfg Config) (*Synchronizer, error) {
200 auctionConstants, err := ethClient.AuctionConstants()
201 if err != nil {
202 return nil, tracerr.Wrap(fmt.Errorf("NewSynchronizer ethClient.AuctionConstants(): %w",
203 err))
204 }
205 rollupConstants, err := ethClient.RollupConstants()
206 if err != nil {
207 return nil, tracerr.Wrap(fmt.Errorf("NewSynchronizer ethClient.RollupConstants(): %w",
208 err))
209 }
210 wDelayerConstants, err := ethClient.WDelayerConstants()
211 if err != nil {
212 return nil, tracerr.Wrap(fmt.Errorf("NewSynchronizer ethClient.WDelayerConstants(): %w",
213 err))
214 }
215 consts := SCConsts{
216 Rollup: *rollupConstants,
217 Auction: *auctionConstants,
218 WDelayer: *wDelayerConstants,
219 }
220
221 initVars, startBlockNums, err := getInitialVariables(ethClient, &consts)
222 if err != nil {
223 return nil, tracerr.Wrap(err)
224 }
225 log.Infow("Synchronizer syncing from smart contract blocks",
226 "rollup", startBlockNums.Rollup,
227 "auction", startBlockNums.Auction,
228 "wdelayer", startBlockNums.WDelayer,
229 )
230 // Set startBlockNum to the minimum between Auction, Rollup and
231 // WDelayer StartBlockNum
232 startBlockNum := startBlockNums.Auction
233 if startBlockNums.Rollup < startBlockNum {
234 startBlockNum = startBlockNums.Rollup
235 }
236 if startBlockNums.WDelayer < startBlockNum {
237 startBlockNum = startBlockNums.WDelayer
238 }
239 stats := NewStatsHolder(startBlockNum, cfg.StatsRefreshPeriod)
240 s := &Synchronizer{
241 ethClient: ethClient,
242 consts: consts,
243 historyDB: historyDB,
244 stateDB: stateDB,
245 cfg: cfg,
246 initVars: *initVars,
247 startBlockNum: startBlockNum,
248 stats: stats,
249 }
250 return s, s.init()
251}
252
253// Stats returns a copy of the Synchronizer Stats. It is safe to call Stats()
254// during a Sync call
255func (s *Synchronizer) Stats() *Stats {
256 return s.stats.CopyStats()
257}
258
259// AuctionConstants returns the AuctionConstants read from the smart contract
260func (s *Synchronizer) AuctionConstants() *common.AuctionConstants {
261 return &s.consts.Auction
262}
263
264// RollupConstants returns the RollupConstants read from the smart contract
265func (s *Synchronizer) RollupConstants() *common.RollupConstants {
266 return &s.consts.Rollup
267}
268
269// WDelayerConstants returns the WDelayerConstants read from the smart contract
270func (s *Synchronizer) WDelayerConstants() *common.WDelayerConstants {
271 return &s.consts.WDelayer
272}
273
274// SCVars returns a copy of the Smart Contract Variables
275func (s *Synchronizer) SCVars() SCVariablesPtr {
276 return SCVariablesPtr{
277 Rollup: s.vars.Rollup.Copy(),
278 Auction: s.vars.Auction.Copy(),
279 WDelayer: s.vars.WDelayer.Copy(),
280 }
281}
282
283// firstBatchBlockNum is the blockNum of first batch in that block, if any
284func (s *Synchronizer) updateCurrentSlotIfSync(reset bool, firstBatchBlockNum *int64) error {
285 slot := common.Slot{
286 SlotNum: s.stats.Sync.Auction.CurrentSlot.SlotNum,
287 ForgerCommitment: s.stats.Sync.Auction.CurrentSlot.ForgerCommitment,
288 }
289 // We want the next block because the current one is already mined
290 blockNum := s.stats.Sync.LastBlock.Num + 1
291 slotNum := s.consts.Auction.SlotNum(blockNum)
292 if reset {
293 dbFirstBatchBlockNum, err := s.historyDB.GetFirstBatchBlockNumBySlot(slotNum)
294 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
295 return tracerr.Wrap(fmt.Errorf("historyDB.GetFirstBatchBySlot: %w", err))
296 } else if tracerr.Unwrap(err) == sql.ErrNoRows {
297 firstBatchBlockNum = nil
298 } else {
299 firstBatchBlockNum = &dbFirstBatchBlockNum
300 }
301 slot.ForgerCommitment = false
302 } else if slotNum > slot.SlotNum {
303 // We are in a new slotNum, start from default values
304 slot.ForgerCommitment = false
305 }
306 slot.SlotNum = slotNum
307 slot.StartBlock, slot.EndBlock = s.consts.Auction.SlotBlocks(slot.SlotNum)
308 // If Synced, update the current coordinator
309 if s.stats.Synced() && blockNum >= s.consts.Auction.GenesisBlockNum {
310 bidCoord, err := s.historyDB.GetBestBidCoordinator(slot.SlotNum)
311 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
312 return tracerr.Wrap(err)
313 }
314 if tracerr.Unwrap(err) == sql.ErrNoRows {
315 slot.BootCoord = true
316 slot.Forger = s.vars.Auction.BootCoordinator
317 slot.URL = s.vars.Auction.BootCoordinatorURL
318 } else if err == nil {
319 slot.BidValue = bidCoord.BidValue
320 slot.DefaultSlotBid = bidCoord.DefaultSlotSetBid[slot.SlotNum%6]
321 // Only if the highest bid value is higher than the
322 // default slot bid, the bidder is the winner of the
323 // slot. Otherwise the boot coordinator is the winner.
324 if slot.BidValue.Cmp(slot.DefaultSlotBid) >= 0 {
325 slot.Bidder = bidCoord.Bidder
326 slot.Forger = bidCoord.Forger
327 slot.URL = bidCoord.URL
328 } else {
329 slot.BootCoord = true
330 slot.Forger = s.vars.Auction.BootCoordinator
331 slot.URL = s.vars.Auction.BootCoordinatorURL
332 }
333 }
334 if firstBatchBlockNum != nil &&
335 s.consts.Auction.RelativeBlock(*firstBatchBlockNum) <
336 int64(s.vars.Auction.SlotDeadline) {
337 slot.ForgerCommitment = true
338 }
339
340 // TODO: Remove this SANITY CHECK once this code is tested enough
341 // BEGIN SANITY CHECK
342 canForge, err := s.ethClient.AuctionCanForge(slot.Forger, blockNum)
343 if err != nil {
344 return tracerr.Wrap(err)
345 }
346 if !canForge {
347 return tracerr.Wrap(fmt.Errorf("Synchronized value of forger address for closed slot "+
348 "differs from smart contract: %+v", slot))
349 }
350 // END SANITY CHECK
351 }
352 s.stats.UpdateCurrentSlot(slot)
353 return nil
354}
355
356func (s *Synchronizer) init() error {
357 // Update stats parameters so that they have valid values before the
358 // first Sync call
359 if err := s.stats.UpdateEth(s.ethClient); err != nil {
360 return tracerr.Wrap(err)
361 }
362 lastBlock := &common.Block{}
363 lastSavedBlock, err := s.historyDB.GetLastBlock()
364 // `s.historyDB.GetLastBlock()` will never return `sql.ErrNoRows`
365 // because we always have the default block 0 in the DB
366 if err != nil {
367 return tracerr.Wrap(err)
368 }
369 // If we only have the default block 0,
370 // make sure that the stateDB is clean
371 if lastSavedBlock.Num == 0 {
372 if err := s.stateDB.Reset(0); err != nil {
373 return tracerr.Wrap(err)
374 }
375 } else {
376 lastBlock = lastSavedBlock
377 }
378 if err := s.resetState(lastBlock); err != nil {
379 return tracerr.Wrap(err)
380 }
381
382 log.Infow("Sync init block",
383 "syncLastBlock", s.stats.Sync.LastBlock,
384 "syncBlocksPerc", s.stats.blocksPerc(),
385 "ethFirstBlockNum", s.stats.Eth.FirstBlockNum,
386 "ethLastBlock", s.stats.Eth.LastBlock,
387 )
388 log.Infow("Sync init batch",
389 "syncLastBatch", s.stats.Sync.LastBatch,
390 "syncBatchesPerc", s.stats.batchesPerc(s.stats.Sync.LastBatch),
391 "ethLastBatch", s.stats.Eth.LastBatch,
392 )
393 return nil
394}
395
396// Sync2 attems to synchronize an ethereum block starting from lastSavedBlock.
397// If lastSavedBlock is nil, the lastSavedBlock value is obtained from de DB.
398// If a block is synched, it will be returned and also stored in the DB. If a
399// reorg is detected, the number of discarded blocks will be returned and no
400// synchronization will be made.
401// TODO: Be smart about locking: only lock during the read/write operations
402func (s *Synchronizer) Sync2(ctx context.Context, lastSavedBlock *common.Block) (*common.BlockData, *int64, error) {
403 var nextBlockNum int64 // next block number to sync
404 if lastSavedBlock == nil {
405 var err error
406 // Get lastSavedBlock from History DB
407 lastSavedBlock, err = s.historyDB.GetLastBlock()
408 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
409 return nil, nil, tracerr.Wrap(err)
410 }
411 // If we don't have any stored block, we must do a full sync
412 // starting from the startBlockNum
413 if tracerr.Unwrap(err) == sql.ErrNoRows || lastSavedBlock.Num == 0 {
414 nextBlockNum = s.startBlockNum
415 lastSavedBlock = nil
416 }
417 }
418 if lastSavedBlock != nil {
419 nextBlockNum = lastSavedBlock.Num + 1
420 if lastSavedBlock.Num < s.startBlockNum {
421 return nil, nil, tracerr.Wrap(
422 fmt.Errorf("lastSavedBlock (%v) < startBlockNum (%v)",
423 lastSavedBlock.Num, s.startBlockNum))
424 }
425 }
426
427 ethBlock, err := s.ethClient.EthBlockByNumber(ctx, nextBlockNum)
428 if tracerr.Unwrap(err) == ethereum.NotFound {
429 return nil, nil, nil
430 } else if err != nil {
431 return nil, nil, tracerr.Wrap(err)
432 }
433 log.Debugf("ethBlock: num: %v, parent: %v, hash: %v",
434 ethBlock.Num, ethBlock.ParentHash.String(), ethBlock.Hash.String())
435
436 if err := s.stats.UpdateEth(s.ethClient); err != nil {
437 return nil, nil, tracerr.Wrap(err)
438 }
439
440 log.Debugw("Syncing...",
441 "block", nextBlockNum,
442 "ethLastBlock", s.stats.Eth.LastBlock,
443 )
444
445 // Check that the obtianed ethBlock.ParentHash == prevEthBlock.Hash; if not, reorg!
446 if lastSavedBlock != nil {
447 if lastSavedBlock.Hash != ethBlock.ParentHash {
448 // Reorg detected
449 log.Debugw("Reorg Detected",
450 "blockNum", ethBlock.Num,
451 "block.parent(got)", ethBlock.ParentHash, "parent.hash(exp)", lastSavedBlock.Hash)
452 lastDBBlockNum, err := s.reorg(lastSavedBlock)
453 if err != nil {
454 return nil, nil, tracerr.Wrap(err)
455 }
456 discarded := lastSavedBlock.Num - lastDBBlockNum
457 return nil, &discarded, nil
458 }
459 }
460
461 // Get data from the rollup contract
462 rollupData, err := s.rollupSync(ethBlock)
463 if err != nil {
464 return nil, nil, tracerr.Wrap(err)
465 }
466
467 // Get data from the auction contract
468 auctionData, err := s.auctionSync(ethBlock)
469 if err != nil {
470 return nil, nil, tracerr.Wrap(err)
471 }
472
473 // Get data from the WithdrawalDelayer contract
474 wDelayerData, err := s.wdelayerSync(ethBlock)
475 if err != nil {
476 return nil, nil, tracerr.Wrap(err)
477 }
478
479 for i := range rollupData.Withdrawals {
480 withdrawal := &rollupData.Withdrawals[i]
481 if !withdrawal.InstantWithdraw {
482 wDelayerTransfers := wDelayerData.DepositsByTxHash[withdrawal.TxHash]
483 if len(wDelayerTransfers) == 0 {
484 return nil, nil, tracerr.Wrap(fmt.Errorf("WDelayer deposit corresponding to " +
485 "non-instant rollup withdrawal not found"))
486 }
487 // Pop the first wDelayerTransfer to consume them in chronological order
488 wDelayerTransfer := wDelayerTransfers[0]
489 wDelayerData.DepositsByTxHash[withdrawal.TxHash] =
490 wDelayerData.DepositsByTxHash[withdrawal.TxHash][1:]
491
492 withdrawal.Owner = wDelayerTransfer.Owner
493 withdrawal.Token = wDelayerTransfer.Token
494 }
495 }
496
497 // Group all the block data into the structs to save into HistoryDB
498 blockData := common.BlockData{
499 Block: *ethBlock,
500 Rollup: *rollupData,
501 Auction: *auctionData,
502 WDelayer: *wDelayerData,
503 }
504
505 err = s.historyDB.AddBlockSCData(&blockData)
506 if err != nil {
507 return nil, nil, tracerr.Wrap(err)
508 }
509
510 batchesLen := len(rollupData.Batches)
511 if batchesLen == 0 {
512 s.stats.UpdateSync(ethBlock, nil, nil, nil)
513 } else {
514 var lastL1BatchBlock *int64
515 var lastForgeL1TxsNum *int64
516 for _, batchData := range rollupData.Batches {
517 if batchData.L1Batch {
518 lastL1BatchBlock = &batchData.Batch.EthBlockNum
519 lastForgeL1TxsNum = batchData.Batch.ForgeL1TxsNum
520 }
521 }
522 s.stats.UpdateSync(ethBlock,
523 &rollupData.Batches[batchesLen-1].Batch.BatchNum,
524 lastL1BatchBlock, lastForgeL1TxsNum)
525 }
526 var firstBatchBlockNum *int64
527 if len(rollupData.Batches) > 0 {
528 firstBatchBlockNum = &rollupData.Batches[0].Batch.EthBlockNum
529 }
530 if err := s.updateCurrentSlotIfSync(false, firstBatchBlockNum); err != nil {
531 return nil, nil, tracerr.Wrap(err)
532 }
533
534 log.Debugw("Synced block",
535 "syncLastBlockNum", s.stats.Sync.LastBlock.Num,
536 "syncBlocksPerc", s.stats.blocksPerc(),
537 "ethLastBlockNum", s.stats.Eth.LastBlock.Num,
538 )
539 for _, batchData := range rollupData.Batches {
540 log.Debugw("Synced batch",
541 "syncLastBatch", batchData.Batch.BatchNum,
542 "syncBatchesPerc", s.stats.batchesPerc(int64(batchData.Batch.BatchNum)),
543 "ethLastBatch", s.stats.Eth.LastBatch,
544 )
545 }
546
547 return &blockData, nil, nil
548}
549
550// reorg manages a reorg, updating History and State DB as needed. Keeps
551// checking previous blocks from the HistoryDB against the blockchain until a
552// block hash match is found. All future blocks in the HistoryDB and
553// corresponding batches in StateBD are discarded. Returns the last valid
554// blockNum from the HistoryDB.
555func (s *Synchronizer) reorg(uncleBlock *common.Block) (int64, error) {
556 blockNum := uncleBlock.Num
557
558 var block *common.Block
559 for blockNum >= s.startBlockNum {
560 ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), blockNum)
561 if err != nil {
562 return 0, tracerr.Wrap(fmt.Errorf("ethClient.EthBlockByNumber: %w", err))
563 }
564
565 block, err = s.historyDB.GetBlock(blockNum)
566 if err != nil {
567 return 0, tracerr.Wrap(fmt.Errorf("historyDB.GetBlock: %w", err))
568 }
569 if block.Hash == ethBlock.Hash {
570 log.Debugf("Found valid block: %v", blockNum)
571 break
572 }
573 blockNum--
574 }
575 total := uncleBlock.Num - block.Num
576 log.Debugw("Discarding blocks", "total", total, "from", uncleBlock.Num, "to", block.Num+1)
577
578 // Set History DB and State DB to the correct state
579 err := s.historyDB.Reorg(block.Num)
580 if err != nil {
581 return 0, tracerr.Wrap(err)
582 }
583
584 if err := s.resetState(block); err != nil {
585 return 0, tracerr.Wrap(err)
586 }
587
588 return block.Num, nil
589}
590
591func getInitialVariables(ethClient eth.ClientInterface,
592 consts *SCConsts) (*SCVariables, *StartBlockNums, error) {
593 rollupInit, rollupInitBlock, err := ethClient.RollupEventInit()
594 if err != nil {
595 return nil, nil, tracerr.Wrap(err)
596 }
597 auctionInit, auctionInitBlock, err := ethClient.AuctionEventInit()
598 if err != nil {
599 return nil, nil, tracerr.Wrap(err)
600 }
601 wDelayerInit, wDelayerInitBlock, err := ethClient.WDelayerEventInit()
602 if err != nil {
603 return nil, nil, tracerr.Wrap(err)
604 }
605 rollupVars := rollupInit.RollupVariables()
606 auctionVars := auctionInit.AuctionVariables(consts.Auction.InitialMinimalBidding)
607 wDelayerVars := wDelayerInit.WDelayerVariables()
608 return &SCVariables{
609 Rollup: *rollupVars,
610 Auction: *auctionVars,
611 WDelayer: *wDelayerVars,
612 }, &StartBlockNums{
613 Rollup: rollupInitBlock,
614 Auction: auctionInitBlock,
615 WDelayer: wDelayerInitBlock,
616 }, nil
617}
618
619func (s *Synchronizer) resetState(block *common.Block) error {
620 rollup, auction, wDelayer, err := s.historyDB.GetSCVars()
621 // If SCVars are not in the HistoryDB, this is probably the first run
622 // of the Synchronizer: store the initial vars taken from config
623 if tracerr.Unwrap(err) == sql.ErrNoRows {
624 vars := s.initVars
625 log.Info("Setting initial SCVars in HistoryDB")
626 if err = s.historyDB.SetInitialSCVars(&vars.Rollup, &vars.Auction, &vars.WDelayer); err != nil {
627 return tracerr.Wrap(fmt.Errorf("historyDB.SetInitialSCVars: %w", err))
628 }
629 s.vars.Rollup = *vars.Rollup.Copy()
630 s.vars.Auction = *vars.Auction.Copy()
631 s.vars.WDelayer = *vars.WDelayer.Copy()
632 // Add initial boot coordinator to HistoryDB
633 if err := s.historyDB.AddCoordinators([]common.Coordinator{{
634 Forger: s.initVars.Auction.BootCoordinator,
635 URL: s.initVars.Auction.BootCoordinatorURL,
636 EthBlockNum: s.initVars.Auction.EthBlockNum,
637 }}); err != nil {
638 return tracerr.Wrap(err)
639 }
640 } else if err != nil {
641 return tracerr.Wrap(err)
642 } else {
643 s.vars.Rollup = *rollup
644 s.vars.Auction = *auction
645 s.vars.WDelayer = *wDelayer
646 }
647
648 batchNum, err := s.historyDB.GetLastBatchNum()
649 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
650 return tracerr.Wrap(fmt.Errorf("historyDB.GetLastBatchNum: %w", err))
651 }
652 if tracerr.Unwrap(err) == sql.ErrNoRows {
653 batchNum = 0
654 }
655
656 lastL1BatchBlockNum, err := s.historyDB.GetLastL1BatchBlockNum()
657 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
658 return tracerr.Wrap(fmt.Errorf("historyDB.GetLastL1BatchBlockNum: %w", err))
659 }
660 if tracerr.Unwrap(err) == sql.ErrNoRows {
661 lastL1BatchBlockNum = 0
662 }
663
664 lastForgeL1TxsNum, err := s.historyDB.GetLastL1TxsNum()
665 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
666 return tracerr.Wrap(fmt.Errorf("historyDB.GetLastL1BatchBlockNum: %w", err))
667 }
668 if tracerr.Unwrap(err) == sql.ErrNoRows || lastForgeL1TxsNum == nil {
669 n := int64(-1)
670 lastForgeL1TxsNum = &n
671 }
672
673 err = s.stateDB.Reset(batchNum)
674 if err != nil {
675 return tracerr.Wrap(fmt.Errorf("stateDB.Reset: %w", err))
676 }
677
678 s.stats.UpdateSync(block, &batchNum, &lastL1BatchBlockNum, lastForgeL1TxsNum)
679
680 if err := s.updateCurrentSlotIfSync(true, nil); err != nil {
681 return tracerr.Wrap(err)
682 }
683 return nil
684}
685
686// rollupSync retreives all the Rollup Smart Contract Data that happened at
687// ethBlock.blockNum with ethBlock.Hash.
688func (s *Synchronizer) rollupSync(ethBlock *common.Block) (*common.RollupData, error) {
689 blockNum := ethBlock.Num
690 var rollupData = common.NewRollupData()
691 // var forgeL1TxsNum int64
692
693 // Get rollup events in the block, and make sure the block hash matches
694 // the expected one.
695 rollupEvents, blockHash, err := s.ethClient.RollupEventsByBlock(blockNum)
696 if err != nil {
697 return nil, tracerr.Wrap(err)
698 }
699 // No events in this block
700 if blockHash == nil {
701 return &rollupData, nil
702 }
703 if *blockHash != ethBlock.Hash {
704 log.Errorw("Block hash mismatch in Rollup events", "expected", ethBlock.Hash.String(),
705 "got", blockHash.String())
706 return nil, tracerr.Wrap(eth.ErrBlockHashMismatchEvent)
707 }
708
709 var nextForgeL1TxsNum int64 // forgeL1TxsNum for the next L1Batch
710 nextForgeL1TxsNumPtr, err := s.historyDB.GetLastL1TxsNum()
711 if err != nil {
712 return nil, tracerr.Wrap(err)
713 }
714 if nextForgeL1TxsNumPtr != nil {
715 nextForgeL1TxsNum = *nextForgeL1TxsNumPtr + 1
716 } else {
717 nextForgeL1TxsNum = 0
718 }
719
720 // Get L1UserTX
721 rollupData.L1UserTxs, err = getL1UserTx(rollupEvents.L1UserTx, blockNum)
722 if err != nil {
723 return nil, tracerr.Wrap(err)
724 }
725
726 // Get ForgeBatch events to get the L1CoordinatorTxs
727 for _, evtForgeBatch := range rollupEvents.ForgeBatch {
728 batchData := common.NewBatchData()
729 position := 0
730
731 // Get the input for each Tx
732 forgeBatchArgs, sender, err := s.ethClient.RollupForgeBatchArgs(evtForgeBatch.EthTxHash,
733 evtForgeBatch.L1UserTxsLen)
734 if err != nil {
735 return nil, tracerr.Wrap(err)
736 }
737
738 batchNum := common.BatchNum(evtForgeBatch.BatchNum)
739 var l1UserTxs []common.L1Tx
740 // Check if this is a L1Batch to get L1 Tx from it
741 if forgeBatchArgs.L1Batch {
742 // Get L1UserTxs with toForgeL1TxsNum, which correspond
743 // to the L1UserTxs that are forged in this batch, so
744 // that stateDB can process them.
745
746 // First try to find them in HistoryDB.
747 l1UserTxs, err = s.historyDB.GetUnforgedL1UserTxs(nextForgeL1TxsNum)
748 if err != nil {
749 return nil, tracerr.Wrap(err)
750 }
751 // Apart from the DB, try to find them in this block.
752 // This could happen because in a block there could be
753 // multiple batches with L1Batch = true (although it's
754 // a very rare case). If not found in the DB and the
755 // block doesn't contain the l1UserTxs, it means that
756 // the L1UserTxs queue with toForgeL1TxsNum was closed
757 // empty, so we leave `l1UserTxs` as an empty slice.
758 for _, l1UserTx := range rollupData.L1UserTxs {
759 if *l1UserTx.ToForgeL1TxsNum == nextForgeL1TxsNum {
760 l1UserTxs = append(l1UserTxs, l1UserTx)
761 }
762 }
763
764 position = len(l1UserTxs)
765 }
766 // Get L1 Coordinator Txs
767 for i := range forgeBatchArgs.L1CoordinatorTxs {
768 l1CoordinatorTx := forgeBatchArgs.L1CoordinatorTxs[i]
769 l1CoordinatorTx.Position = position
770 // l1CoordinatorTx.ToForgeL1TxsNum = &forgeL1TxsNum
771 l1CoordinatorTx.UserOrigin = false
772 l1CoordinatorTx.EthBlockNum = blockNum
773 l1CoordinatorTx.BatchNum = &batchNum
774 l1Tx, err := common.NewL1Tx(&l1CoordinatorTx)
775 if err != nil {
776 return nil, tracerr.Wrap(err)
777 }
778
779 batchData.L1CoordinatorTxs = append(batchData.L1CoordinatorTxs, *l1Tx)
780 position++
781 // fmt.Println("DGB l1coordtx")
782 }
783
784 // Insert all the txs forged in this batch (l1UserTxs,
785 // L1CoordinatorTxs, PoolL2Txs) into stateDB so that they are
786 // processed.
787
788 // Set TxType to the forged L2Txs
789 for i := range forgeBatchArgs.L2TxsData {
790 if err := forgeBatchArgs.L2TxsData[i].SetType(); err != nil {
791 return nil, tracerr.Wrap(err)
792 }
793 }
794
795 // Transform L2 txs to PoolL2Txs
796 poolL2Txs := common.L2TxsToPoolL2Txs(forgeBatchArgs.L2TxsData) // NOTE: This is a big ugly, find a better way
797
798 // ProcessTxs updates poolL2Txs adding: Nonce (and also TokenID, but we don't use it).
799 //nolint:gomnd
800 tpc := txprocessor.Config{ // TODO TMP
801 NLevels: 32,
802 MaxFeeTx: 64,
803 MaxTx: 512,
804 MaxL1Tx: 64,
805 ChainID: uint16(0),
806 }
807 tp := txprocessor.NewTxProcessor(s.stateDB, tpc)
808
809 processTxsOut, err := tp.ProcessTxs(forgeBatchArgs.FeeIdxCoordinator,
810 l1UserTxs, batchData.L1CoordinatorTxs, poolL2Txs)
811 if err != nil {
812 return nil, tracerr.Wrap(err)
813 }
814
815 // Transform processed PoolL2 txs to L2 and store in BatchData
816 l2Txs, err := common.PoolL2TxsToL2Txs(poolL2Txs) // NOTE: This is a big uggly, find a better way
817 if err != nil {
818 return nil, tracerr.Wrap(err)
819 }
820
821 // Set TxID, BlockNum, BatchNum and Position to the forged L2Txs
822 for i := range l2Txs {
823 if err := l2Txs[i].SetID(); err != nil {
824 return nil, tracerr.Wrap(err)
825 }
826 l2Txs[i].EthBlockNum = blockNum
827 l2Txs[i].BatchNum = batchNum
828 l2Txs[i].Position = position
829 position++
830 }
831 batchData.L2Txs = l2Txs
832
833 // Set the BatchNum in the forged L1UserTxs
834 for i := range l1UserTxs {
835 l1UserTxs[i].BatchNum = &batchNum
836 }
837 batchData.L1UserTxs = l1UserTxs
838
839 // Set batchNum in exits
840 for i := range processTxsOut.ExitInfos {
841 exit := &processTxsOut.ExitInfos[i]
842 exit.BatchNum = batchNum
843 }
844 batchData.ExitTree = processTxsOut.ExitInfos
845
846 for i := range processTxsOut.CreatedAccounts {
847 createdAccount := &processTxsOut.CreatedAccounts[i]
848 createdAccount.Nonce = 0
849 createdAccount.Balance = big.NewInt(0)
850 createdAccount.BatchNum = batchNum
851 }
852 batchData.CreatedAccounts = processTxsOut.CreatedAccounts
853
854 slotNum := int64(0)
855 if ethBlock.Num >= s.consts.Auction.GenesisBlockNum {
856 slotNum = (ethBlock.Num - s.consts.Auction.GenesisBlockNum) /
857 int64(s.consts.Auction.BlocksPerSlot)
858 }
859
860 // Get Batch information
861 batch := common.Batch{
862 BatchNum: batchNum,
863 EthBlockNum: blockNum,
864 ForgerAddr: *sender,
865 CollectedFees: processTxsOut.CollectedFees,
866 FeeIdxsCoordinator: forgeBatchArgs.FeeIdxCoordinator,
867 StateRoot: forgeBatchArgs.NewStRoot,
868 NumAccounts: len(batchData.CreatedAccounts),
869 LastIdx: forgeBatchArgs.NewLastIdx,
870 ExitRoot: forgeBatchArgs.NewExitRoot,
871 SlotNum: slotNum,
872 }
873 nextForgeL1TxsNumCpy := nextForgeL1TxsNum
874 if forgeBatchArgs.L1Batch {
875 batch.ForgeL1TxsNum = &nextForgeL1TxsNumCpy
876 batchData.L1Batch = true
877 nextForgeL1TxsNum++
878 }
879 batchData.Batch = batch
880 rollupData.Batches = append(rollupData.Batches, *batchData)
881 }
882
883 // Get Registered Tokens
884 for _, evtAddToken := range rollupEvents.AddToken {
885 var token common.Token
886
887 token.TokenID = common.TokenID(evtAddToken.TokenID)
888 token.EthAddr = evtAddToken.TokenAddress
889 token.EthBlockNum = blockNum
890
891 if consts, err := s.ethClient.EthERC20Consts(evtAddToken.TokenAddress); err != nil {
892 log.Warnw("Error retreiving ERC20 token constants", "addr", evtAddToken.TokenAddress)
893 token.Name = "ERC20_ETH_ERROR"
894 token.Symbol = "ERROR"
895 token.Decimals = 1
896 } else {
897 token.Name = cutStringMax(consts.Name, 20)
898 token.Symbol = cutStringMax(consts.Symbol, 10)
899 token.Decimals = consts.Decimals
900 }
901
902 rollupData.AddedTokens = append(rollupData.AddedTokens, token)
903 }
904
905 for _, evt := range rollupEvents.UpdateBucketWithdraw {
906 rollupData.UpdateBucketWithdraw = append(rollupData.UpdateBucketWithdraw,
907 common.BucketUpdate{
908 EthBlockNum: blockNum,
909 NumBucket: evt.NumBucket,
910 BlockStamp: evt.BlockStamp,
911 Withdrawals: evt.Withdrawals,
912 })
913 }
914
915 for _, evt := range rollupEvents.Withdraw {
916 rollupData.Withdrawals = append(rollupData.Withdrawals, common.WithdrawInfo{
917 Idx: common.Idx(evt.Idx),
918 NumExitRoot: common.BatchNum(evt.NumExitRoot),
919 InstantWithdraw: evt.InstantWithdraw,
920 TxHash: evt.TxHash,
921 })
922 }
923
924 for _, evt := range rollupEvents.UpdateTokenExchange {
925 if len(evt.AddressArray) != len(evt.ValueArray) {
926 return nil, tracerr.Wrap(fmt.Errorf("in RollupEventUpdateTokenExchange "+
927 "len(AddressArray) != len(ValueArray) (%v != %v)",
928 len(evt.AddressArray), len(evt.ValueArray)))
929 }
930 for i := range evt.AddressArray {
931 rollupData.TokenExchanges = append(rollupData.TokenExchanges,
932 common.TokenExchange{
933 EthBlockNum: blockNum,
934 Address: evt.AddressArray[i],
935 ValueUSD: int64(evt.ValueArray[i]),
936 })
937 }
938 }
939
940 varsUpdate := false
941
942 for _, evt := range rollupEvents.UpdateForgeL1L2BatchTimeout {
943 s.vars.Rollup.ForgeL1L2BatchTimeout = evt.NewForgeL1L2BatchTimeout
944 varsUpdate = true
945 }
946
947 for _, evt := range rollupEvents.UpdateFeeAddToken {
948 s.vars.Rollup.FeeAddToken = evt.NewFeeAddToken
949 varsUpdate = true
950 }
951
952 for _, evt := range rollupEvents.UpdateWithdrawalDelay {
953 s.vars.Rollup.WithdrawalDelay = evt.NewWithdrawalDelay
954 varsUpdate = true
955 }
956
957 // NOTE: We skip the event rollupEvents.SafeMode because the
958 // implementation RollupEventsByBlock already inserts a non-existing
959 // RollupEventUpdateBucketsParameters into UpdateBucketsParameters with
960 // all the bucket values at 0 and SafeMode = true
961
962 for _, evt := range rollupEvents.UpdateBucketsParameters {
963 for i, bucket := range evt.ArrayBuckets {
964 s.vars.Rollup.Buckets[i] = common.BucketParams{
965 CeilUSD: bucket.CeilUSD,
966 Withdrawals: bucket.Withdrawals,
967 BlockWithdrawalRate: bucket.BlockWithdrawalRate,
968 MaxWithdrawals: bucket.MaxWithdrawals,
969 }
970 }
971 s.vars.Rollup.SafeMode = evt.SafeMode
972 varsUpdate = true
973 }
974
975 if varsUpdate {
976 s.vars.Rollup.EthBlockNum = blockNum
977 rollupData.Vars = s.vars.Rollup.Copy()
978 }
979
980 return &rollupData, nil
981}
982
983func cutStringMax(s string, max int) string {
984 if len(s) > max {
985 return s[:max]
986 }
987 return s
988}
989
990// auctionSync gets information from the Auction Contract
991func (s *Synchronizer) auctionSync(ethBlock *common.Block) (*common.AuctionData, error) {
992 blockNum := ethBlock.Num
993 var auctionData = common.NewAuctionData()
994
995 // Get auction events in the block
996 auctionEvents, blockHash, err := s.ethClient.AuctionEventsByBlock(blockNum)
997 if err != nil {
998 return nil, tracerr.Wrap(err)
999 }
1000 // No events in this block
1001 if blockHash == nil {
1002 return &auctionData, nil
1003 }
1004 if *blockHash != ethBlock.Hash {
1005 log.Errorw("Block hash mismatch in Auction events", "expected", ethBlock.Hash.String(),
1006 "got", blockHash.String())
1007 return nil, tracerr.Wrap(eth.ErrBlockHashMismatchEvent)
1008 }
1009
1010 // Get bids
1011 for _, evt := range auctionEvents.NewBid {
1012 bid := common.Bid{
1013 SlotNum: evt.Slot,
1014 BidValue: evt.BidAmount,
1015 Bidder: evt.Bidder,
1016 EthBlockNum: blockNum,
1017 }
1018 auctionData.Bids = append(auctionData.Bids, bid)
1019 }
1020
1021 // Get Coordinators
1022 for _, evt := range auctionEvents.SetCoordinator {
1023 coordinator := common.Coordinator{
1024 Bidder: evt.BidderAddress,
1025 Forger: evt.ForgerAddress,
1026 URL: evt.CoordinatorURL,
1027 EthBlockNum: blockNum,
1028 }
1029 auctionData.Coordinators = append(auctionData.Coordinators, coordinator)
1030 }
1031
1032 varsUpdate := false
1033
1034 for _, evt := range auctionEvents.NewSlotDeadline {
1035 s.vars.Auction.SlotDeadline = evt.NewSlotDeadline
1036 varsUpdate = true
1037 }
1038 for _, evt := range auctionEvents.NewClosedAuctionSlots {
1039 s.vars.Auction.ClosedAuctionSlots = evt.NewClosedAuctionSlots
1040 varsUpdate = true
1041 }
1042 for _, evt := range auctionEvents.NewOutbidding {
1043 s.vars.Auction.Outbidding = evt.NewOutbidding
1044 varsUpdate = true
1045 }
1046 for _, evt := range auctionEvents.NewDonationAddress {
1047 s.vars.Auction.DonationAddress = evt.NewDonationAddress
1048 varsUpdate = true
1049 }
1050 for _, evt := range auctionEvents.NewBootCoordinator {
1051 s.vars.Auction.BootCoordinator = evt.NewBootCoordinator
1052 s.vars.Auction.BootCoordinatorURL = evt.NewBootCoordinatorURL
1053 varsUpdate = true
1054 // Add new boot coordinator
1055 auctionData.Coordinators = append(auctionData.Coordinators, common.Coordinator{
1056 Forger: evt.NewBootCoordinator,
1057 URL: evt.NewBootCoordinatorURL,
1058 EthBlockNum: blockNum,
1059 })
1060 }
1061 for _, evt := range auctionEvents.NewOpenAuctionSlots {
1062 s.vars.Auction.OpenAuctionSlots = evt.NewOpenAuctionSlots
1063 varsUpdate = true
1064 }
1065 for _, evt := range auctionEvents.NewAllocationRatio {
1066 s.vars.Auction.AllocationRatio = evt.NewAllocationRatio
1067 varsUpdate = true
1068 }
1069 for _, evt := range auctionEvents.NewDefaultSlotSetBid {
1070 if evt.SlotSet > 6 { //nolint:gomnd
1071 return nil, tracerr.Wrap(fmt.Errorf("unexpected SlotSet in "+
1072 "auctionEvents.NewDefaultSlotSetBid: %v", evt.SlotSet))
1073 }
1074 s.vars.Auction.DefaultSlotSetBid[evt.SlotSet] = evt.NewInitialMinBid
1075 s.vars.Auction.DefaultSlotSetBidSlotNum = s.consts.Auction.SlotNum(blockNum) +
1076 int64(s.vars.Auction.ClosedAuctionSlots) + 1
1077 varsUpdate = true
1078 }
1079
1080 // NOTE: We ignore NewForgeAllocated
1081 // NOTE: We ignore NewForge because we're already tracking ForgeBatch event from Rollup
1082 // NOTE: We ignore HEZClaimed
1083
1084 if varsUpdate {
1085 s.vars.Auction.EthBlockNum = blockNum
1086 auctionData.Vars = s.vars.Auction.Copy()
1087 }
1088
1089 return &auctionData, nil
1090}
1091
1092// wdelayerSync gets information from the Withdrawal Delayer Contract
1093func (s *Synchronizer) wdelayerSync(ethBlock *common.Block) (*common.WDelayerData, error) {
1094 blockNum := ethBlock.Num
1095 wDelayerData := common.NewWDelayerData()
1096
1097 // Get wDelayer events in the block
1098 wDelayerEvents, blockHash, err := s.ethClient.WDelayerEventsByBlock(blockNum)
1099 if err != nil {
1100 return nil, tracerr.Wrap(err)
1101 }
1102 // No events in this block
1103 if blockHash == nil {
1104 return &wDelayerData, nil
1105 }
1106 if *blockHash != ethBlock.Hash {
1107 log.Errorw("Block hash mismatch in WDelayer events", "expected", ethBlock.Hash.String(),
1108 "got", blockHash.String())
1109 return nil, tracerr.Wrap(eth.ErrBlockHashMismatchEvent)
1110 }
1111
1112 for _, evt := range wDelayerEvents.Deposit {
1113 wDelayerData.Deposits = append(wDelayerData.Deposits, common.WDelayerTransfer{
1114 Owner: evt.Owner,
1115 Token: evt.Token,
1116 Amount: evt.Amount,
1117 })
1118 wDelayerData.DepositsByTxHash[evt.TxHash] =
1119 append(wDelayerData.DepositsByTxHash[evt.TxHash],
1120 &wDelayerData.Deposits[len(wDelayerData.Deposits)-1])
1121 }
1122 for _, evt := range wDelayerEvents.Withdraw {
1123 wDelayerData.Withdrawals = append(wDelayerData.Withdrawals, common.WDelayerTransfer{
1124 Owner: evt.Owner,
1125 Token: evt.Token,
1126 Amount: evt.Amount,
1127 })
1128 }
1129 for _, evt := range wDelayerEvents.EscapeHatchWithdrawal {
1130 wDelayerData.EscapeHatchWithdrawals = append(wDelayerData.EscapeHatchWithdrawals,
1131 common.WDelayerEscapeHatchWithdrawal{
1132 EthBlockNum: blockNum,
1133 Who: evt.Who,
1134 To: evt.To,
1135 TokenAddr: evt.Token,
1136 Amount: evt.Amount,
1137 })
1138 }
1139
1140 varsUpdate := false
1141
1142 for range wDelayerEvents.EmergencyModeEnabled {
1143 s.vars.WDelayer.EmergencyMode = true
1144 s.vars.WDelayer.EmergencyModeStartingBlock = blockNum
1145 varsUpdate = true
1146 }
1147 for _, evt := range wDelayerEvents.NewWithdrawalDelay {
1148 s.vars.WDelayer.WithdrawalDelay = evt.WithdrawalDelay
1149 varsUpdate = true
1150 }
1151 for _, evt := range wDelayerEvents.NewEmergencyCouncil {
1152 s.vars.WDelayer.EmergencyCouncilAddress = evt.NewEmergencyCouncil
1153 varsUpdate = true
1154 }
1155 for _, evt := range wDelayerEvents.NewHermezGovernanceAddress {
1156 s.vars.WDelayer.HermezGovernanceAddress = evt.NewHermezGovernanceAddress
1157 varsUpdate = true
1158 }
1159
1160 if varsUpdate {
1161 s.vars.WDelayer.EthBlockNum = blockNum
1162 wDelayerData.Vars = s.vars.WDelayer.Copy()
1163 }
1164
1165 return &wDelayerData, nil
1166}
1167
1168func getL1UserTx(eventsL1UserTx []eth.RollupEventL1UserTx, blockNum int64) ([]common.L1Tx, error) {
1169 l1Txs := make([]common.L1Tx, len(eventsL1UserTx))
1170 for i := range eventsL1UserTx {
1171 eventsL1UserTx[i].L1UserTx.EthBlockNum = blockNum
1172 // Check validity of L1UserTx
1173 l1Tx, err := common.NewL1Tx(&eventsL1UserTx[i].L1UserTx)
1174 if err != nil {
1175 return nil, tracerr.Wrap(err)
1176 }
1177 l1Txs[i] = *l1Tx
1178 }
1179 return l1Txs, nil
1180}