package orm import ( "context" "fmt" "time" "gorm.io/gorm" "scroll-tech/common/types" ) // L1Block is structure of stored l1 block message type L1Block struct { db *gorm.DB `gorm:"column:-"` // block Number uint64 `json:"number" gorm:"column:number"` Hash string `json:"hash" gorm:"column:hash"` BaseFee uint64 `json:"base_fee" gorm:"column:base_fee"` // oracle GasOracleStatus int16 `json:"oracle_status" gorm:"column:oracle_status;default:1"` OracleTxHash string `json:"oracle_tx_hash" gorm:"column:oracle_tx_hash;default:NULL"` // metadata CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"column:deleted_at;default:NULL"` } // NewL1Block create an l1Block instance func NewL1Block(db *gorm.DB) *L1Block { return &L1Block{db: db} } // TableName define the L1Block table name func (*L1Block) TableName() string { return "l1_block" } // GetLatestL1BlockHeight get the latest l1 block height func (o *L1Block) GetLatestL1BlockHeight(ctx context.Context) (uint64, error) { db := o.db.WithContext(ctx) db = db.Model(&L1Block{}) db = db.Select("COALESCE(MAX(number), 0)") var maxNumber uint64 if err := db.Row().Scan(&maxNumber); err != nil { return 0, fmt.Errorf("L1Block.GetLatestL1BlockHeight error: %w", err) } return maxNumber, nil } // GetL1Blocks get the l1 blocks func (o *L1Block) GetL1Blocks(ctx context.Context, fields map[string]interface{}) ([]L1Block, error) { db := o.db.WithContext(ctx) db = db.Model(&L1Block{}) for key, value := range fields { db = db.Where(key, value) } db = db.Order("number ASC") var l1Blocks []L1Block if err := db.Find(&l1Blocks).Error; err != nil { return nil, fmt.Errorf("L1Block.GetL1Blocks error: %w, fields: %v", err, fields) } return l1Blocks, nil } // InsertL1Blocks batch inserts l1 blocks. // If there's a block number conflict (e.g., due to reorg), soft deletes the existing block and inserts the new one. func (o *L1Block) InsertL1Blocks(ctx context.Context, blocks []L1Block) error { if len(blocks) == 0 { return nil } return o.db.Transaction(func(tx *gorm.DB) error { minBlockNumber := blocks[0].Number for _, block := range blocks[1:] { if block.Number < minBlockNumber { minBlockNumber = block.Number } } db := tx.WithContext(ctx) db = db.Model(&L1Block{}) db = db.Where("number >= ?", minBlockNumber) result := db.Delete(&L1Block{}) if result.Error != nil { return fmt.Errorf("L1Block.InsertL1Blocks error: soft deleting blocks failed, block numbers starting from: %v, error: %w", minBlockNumber, result.Error) } // If the number of deleted blocks exceeds the limit (input length + 64), treat it as an anomaly. // Because reorg with >= 64 blocks is very unlikely to happen. if result.RowsAffected >= int64(len(blocks)+64) { return fmt.Errorf("L1Block.InsertL1Blocks error: too many blocks were deleted, count: %d", result.RowsAffected) } if err := db.Create(&blocks).Error; err != nil { return fmt.Errorf("L1Block.InsertL1Blocks error: %w", err) } return nil }) } // UpdateL1GasOracleStatusAndOracleTxHash update l1 gas oracle status and oracle tx hash func (o *L1Block) UpdateL1GasOracleStatusAndOracleTxHash(ctx context.Context, blockHash string, status types.GasOracleStatus, txHash string) error { updateFields := map[string]interface{}{ "oracle_status": int(status), "oracle_tx_hash": txHash, } db := o.db.WithContext(ctx) db = db.Model(&L1Block{}) db = db.Where("hash", blockHash) if err := db.Updates(updateFields).Error; err != nil { return fmt.Errorf("L1Block.UpdateL1GasOracleStatusAndOracleTxHash error: %w, block hash: %v, status: %v, tx hash: %v", err, blockHash, status.String(), txHash) } return nil }