Skip to content

Commit 47b5bde

Browse files
joanestebanrclaude
andauthored
feat: migrate bridgesync claim data into claimsync DB on startup (#1554)
## 🔄 Changes Summary - Add `ImportDataFromBridgesyncer` which copies `block`, `claim`, `set_claim` and `unset_claim` rows from a bridgesync SQLite DB into the claimsync DB. Handles old schema versions (missing `block.hash`, `claim.tx_hash`, `claim.block_timestamp`, `claim.type`) gracefully via column detection. - Add `ImportKeyValueFromBridgesyncer` which copies the single `key_value` row, replacing the `owner` field with the claimsync syncer ID. - Both functions are idempotent (`INSERT OR IGNORE`) and no-ops when the source DB has no relevant data (claimDB is not created unless data exists). - A migration guard validates that `bridgesync0012` (the last migration touching the imported tables) has been applied before proceeding. - In `cmd/run.go`, wire the migration via `runImportFromBridgeSyncerIfNeeded` at the call site, before both the bridge and claim syncers start: - L1: triggered when `BRIDGE` or `L1BRIDGESYNC` are active - L2: triggered when any of `BRIDGE`, `L2BRIDGESYNC`, `L2CLAIMSYNC`, `AGGSENDER`, `AGGSENDERVALIDATOR` or `AGGCHAINPROOFGEN` are active - It removes the old tables from bridgesyncer Cases: | # | Condition | Shuld Migrate | Outcome | -- | ---------------|----------------------|---------------- | 0 | Componentsdoesn't imply run Bridge syncer neither Claim sycner | ❌ | Skip | 1 | BridgeDB doesn't exist | ❌ | Skip | 2 | ClaimDB exists | ❌| Skip. ClaimDB has data so no migration required | 3 | BridgeDB doesn't have claims tables or they are empty |❌ | Skip. No data to migrate | 4 | BridgeDB migrations below `bridgesync0012` | ⚠️| Abort. It's not possible to migrate it, update to an intermediate version | 5 | All conditions met | ✅| Migration runs ## ⚠️ Breaking Changes None ## 📋 Config Updates None ## ✅ Testing - Unit tests - Manually upgrade v0.8.2 to this version ## 🐞 Issues - Closes #1546 --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c92507f commit 47b5bde

File tree

11 files changed

+1185
-138
lines changed

11 files changed

+1185
-138
lines changed

bridgesync/backfill_tx_sender_test.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -79,23 +79,6 @@ func TestBackfillTxnSender(t *testing.T) {
7979
require.NoError(t, meddler.Insert(tx, bridgeTableName, newTestBridge(1, 0,
8080
"0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890")))
8181

82-
// Insert test claim record
83-
_, err = tx.Exec(`
84-
INSERT INTO claim (
85-
block_num, block_pos, global_index, origin_network, origin_address,
86-
destination_address, amount, proof_local_exit_root, proof_rollup_exit_root,
87-
mainnet_exit_root, rollup_exit_root, global_exit_root, destination_network,
88-
metadata, is_message, block_timestamp, tx_hash
89-
) VALUES (
90-
1, 1, '1', 1, '0x1234567890123456789012345678901234567890',
91-
'0x0987654321098765432109876543210987654321', '1000000000000000000',
92-
'', '', '', '', '', 2,
93-
'', false, 1234567890,
94-
'0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890'
95-
)
96-
`)
97-
require.NoError(t, err)
98-
9982
err = tx.Commit()
10083
require.NoError(t, err)
10184

bridgesync/migrations/bridgesync0013.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ DROP TABLE _bridge_to_address_backup;
77

88
-- +migrate Up
99
CREATE TABLE _bridge_to_address_backup AS SELECT block_num, block_pos, to_address FROM bridge;
10+
CREATE INDEX _bridge_to_address_backup_idx ON _bridge_to_address_backup(block_num, block_pos);
1011
ALTER TABLE bridge DROP COLUMN to_address;
1112
ALTER TABLE bridge ADD COLUMN to_address VARCHAR DEFAULT '';
1213
-- Only set to_address for rows != null
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
-- +migrate Down
2+
CREATE TABLE IF NOT EXISTS claim (
3+
block_num INTEGER NOT NULL REFERENCES block(num) ON DELETE CASCADE,
4+
block_pos INTEGER NOT NULL,
5+
global_index TEXT NOT NULL,
6+
origin_network INTEGER NOT NULL,
7+
origin_address VARCHAR NOT NULL,
8+
destination_address VARCHAR NOT NULL,
9+
amount TEXT NOT NULL,
10+
proof_local_exit_root VARCHAR,
11+
proof_rollup_exit_root VARCHAR,
12+
mainnet_exit_root VARCHAR,
13+
rollup_exit_root VARCHAR,
14+
global_exit_root VARCHAR,
15+
destination_network INTEGER NOT NULL,
16+
metadata BLOB,
17+
is_message BOOLEAN,
18+
tx_hash VARCHAR,
19+
block_timestamp INTEGER,
20+
type TEXT NOT NULL DEFAULT '',
21+
PRIMARY KEY (block_num, block_pos)
22+
);
23+
24+
CREATE INDEX IF NOT EXISTS idx_claim_block_num_block_pos_desc ON claim (block_num DESC, block_pos DESC);
25+
CREATE INDEX IF NOT EXISTS idx_claim_block_num_block_pos_asc ON claim (block_num ASC, block_pos ASC);
26+
CREATE INDEX IF NOT EXISTS idx_claim_type_block ON claim (type, block_num);
27+
28+
CREATE TABLE IF NOT EXISTS unset_claim (
29+
block_num INTEGER NOT NULL REFERENCES block(num) ON DELETE CASCADE,
30+
block_pos INTEGER NOT NULL,
31+
tx_hash VARCHAR NOT NULL,
32+
global_index TEXT NOT NULL,
33+
unset_global_index_hash_chain VARCHAR NOT NULL,
34+
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
35+
PRIMARY KEY (block_num, block_pos)
36+
);
37+
38+
CREATE TABLE IF NOT EXISTS set_claim (
39+
block_num INTEGER NOT NULL REFERENCES block(num) ON DELETE CASCADE,
40+
block_pos INTEGER NOT NULL,
41+
tx_hash VARCHAR NOT NULL,
42+
global_index TEXT NOT NULL,
43+
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
44+
PRIMARY KEY (block_num, block_pos)
45+
);
46+
47+
-- +migrate Up
48+
DROP TABLE IF EXISTS set_claim;
49+
DROP TABLE IF EXISTS unset_claim;
50+
DROP TABLE IF EXISTS claim;

bridgesync/migrations/migrations.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"database/sql"
55
"embed"
66
"fmt"
7+
"slices"
78
"sort"
89
"strings"
910

@@ -54,7 +55,7 @@ func addSourceField(database *sql.DB) error {
5455
return fmt.Errorf("error getting applied migrations: %w", err)
5556
}
5657
// This code is to undo the change where bridgesync0014 drops the field
57-
if !contains(migrations, "bridgesync0014") {
58+
if !slices.Contains(migrations, "bridgesync0014") {
5859
log.Warn("migration 'bridgesync0014' not applied, skipping addSourceField." +
5960
" This means that the 'source' column on 'bridge' table will not be added.")
6061
return nil
@@ -74,15 +75,6 @@ func addSourceField(database *sql.DB) error {
7475
return nil
7576
}
7677

77-
func contains(slice []string, item string) bool {
78-
for _, s := range slice {
79-
if s == item {
80-
return true
81-
}
82-
}
83-
return false
84-
}
85-
8678
func GetFullMigrations() []types.Migration {
8779
baseMigrations := dbmigrations.GetBaseMigrations()
8880
total := len(baseMigrations) + len(migrations) + len(treemigrations.Migrations)

bridgesync/migrations/migrations_test.go

Lines changed: 69 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ import (
2121
"github.com/stretchr/testify/require"
2222
)
2323

24+
func TestRunMigrationsExploratory(t *testing.T) {
25+
t.Skip("This test is for exploratory testing of migrations during development. It is not meant to be run as part of automated tests.")
26+
dbPath := "/tmp/bridgel1sync.sqlite"
27+
err := RunMigrations(dbPath)
28+
require.NoError(t, err)
29+
}
30+
2431
func TestMigration0001(t *testing.T) {
2532
dbPath := path.Join(t.TempDir(), "bridgesyncTest001.sqlite")
2633

@@ -49,24 +56,6 @@ func TestMigration0001(t *testing.T) {
4956
metadata,
5057
deposit_count
5158
) VALUES (1, 0, 0, 0, '0x0000', 0, '0x0000', 0, NULL, 0);
52-
53-
INSERT INTO claim (
54-
block_num,
55-
block_pos,
56-
global_index,
57-
origin_network,
58-
origin_address,
59-
destination_address,
60-
amount,
61-
proof_local_exit_root,
62-
proof_rollup_exit_root,
63-
mainnet_exit_root,
64-
rollup_exit_root,
65-
global_exit_root,
66-
destination_network,
67-
metadata,
68-
is_message
69-
) VALUES (1, 0, 0, 0, '0x0000', '0x0000', 0, '0x000,0x000', '0x000,0x000', '0x000', '0x000', '0x0', 0, NULL, FALSE);
7059
`)
7160
require.NoError(t, err)
7261
err = tx.Commit()
@@ -118,20 +107,6 @@ func TestMigration0002(t *testing.T) {
118107
from_address
119108
) VALUES (1, 0, 0, 0, '0x3', 0, '0x0000', 0, NULL, 0, 1739270804, '0xabcd', '0x123');
120109
121-
INSERT INTO claim (
122-
block_num,
123-
block_pos,
124-
global_index,
125-
origin_network,
126-
origin_address,
127-
destination_address,
128-
amount,
129-
destination_network,
130-
metadata,
131-
is_message,
132-
block_timestamp,
133-
tx_hash
134-
) VALUES (1, 0, 0, 0, '0x3', '0x0000', 0, 0, NULL, FALSE, 1739270804, '0xabcd');
135110
`)
136111
require.NoError(t, err)
137112
err = tx.Commit()
@@ -185,27 +160,6 @@ func TestMigration0002(t *testing.T) {
185160
require.NoError(t, err)
186161
require.NotNil(t, bridge)
187162
require.Equal(t, uint64(1739270804), bridge.BlockTimestamp)
188-
189-
var claim struct {
190-
BlockNum uint64 `meddler:"block_num"`
191-
BlockPos uint64 `meddler:"block_pos"`
192-
GlobalIndex *big.Int `meddler:"global_index,bigint"`
193-
OriginNetwork uint32 `meddler:"origin_network"`
194-
OriginAddress string `meddler:"origin_address"`
195-
DestinationAddress string `meddler:"destination_address"`
196-
Amount *big.Int `meddler:"amount,bigint"`
197-
DestinationNetwork uint32 `meddler:"destination_network"`
198-
Metadata []byte `meddler:"metadata"`
199-
IsMessage bool `meddler:"is_message"`
200-
BlockTimestamp uint64 `meddler:"block_timestamp"`
201-
TxHash string `meddler:"tx_hash"`
202-
}
203-
204-
err = meddler.QueryRow(db, &claim,
205-
`SELECT * FROM claim`)
206-
require.NoError(t, err)
207-
require.NotNil(t, claim)
208-
require.Equal(t, uint64(1739270804), claim.BlockTimestamp)
209163
}
210164

211165
func TestMigrations0003(t *testing.T) {
@@ -763,6 +717,40 @@ func TestMigration0013(t *testing.T) {
763717
err = tx.Commit()
764718
require.NoError(t, err)
765719
}
720+
func TestMigration0015(t *testing.T) {
721+
dbPath := path.Join(t.TempDir(), "bridgesyncTest0015.sqlite")
722+
723+
database, err := db.NewSQLiteDB(dbPath)
724+
require.NoError(t, err)
725+
defer database.Close()
726+
727+
// Run migrations up to 0014 — claim, set_claim and unset_claim still exist.
728+
err = db.RunMigrationsDBExtended(log.GetDefaultLogger(),
729+
database, GetUpTo("bridgesync0014"), nil, migrate.Up, db.NoLimitMigrations)
730+
require.NoError(t, err)
731+
732+
tableExists := func(name string) bool {
733+
var count int
734+
err := database.QueryRow(
735+
`SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?`, name).Scan(&count)
736+
require.NoError(t, err)
737+
return count > 0
738+
}
739+
740+
require.True(t, tableExists("claim"), "claim table should exist before migration 0015")
741+
require.True(t, tableExists("set_claim"), "set_claim table should exist before migration 0015")
742+
require.True(t, tableExists("unset_claim"), "unset_claim table should exist before migration 0015")
743+
744+
// Apply migration 0015.
745+
err = db.RunMigrationsDBExtended(log.GetDefaultLogger(),
746+
database, GetUpTo("bridgesync0015"), nil, migrate.Up, db.NoLimitMigrations)
747+
require.NoError(t, err)
748+
749+
require.False(t, tableExists("claim"), "claim table should be dropped by migration 0015")
750+
require.False(t, tableExists("set_claim"), "set_claim table should be dropped by migration 0015")
751+
require.False(t, tableExists("unset_claim"), "unset_claim table should be dropped by migration 0015")
752+
}
753+
766754
func TestMigrationsDown(t *testing.T) {
767755
dbPath := path.Join(t.TempDir(), "bridgesyncTestDown.sqlite")
768756
err := RunMigrations(dbPath)
@@ -788,8 +776,23 @@ func TestMigrationFromPreviousVersion(t *testing.T) {
788776

789777
referenceHash := schemaHash(t, freshDB)
790778

779+
// Build the expected set of migration IDs from the full migration list.
780+
expectedIDs := make(map[string]struct{})
781+
for _, m := range GetFullMigrations() {
782+
expectedIDs[m.ID] = struct{}{}
783+
}
784+
785+
// Verify the fresh DB itself has all expected migrations applied.
786+
appliedIDs, err := db.GetMigrationsIDsApplied(freshDB)
787+
require.NoError(t, err)
788+
for _, id := range appliedIDs {
789+
delete(expectedIDs, id)
790+
}
791+
require.Empty(t, expectedIDs, "fresh DB is missing migrations: %v", expectedIDs)
792+
791793
// For each testdata/*.sqlite, copy it, apply all remaining migrations, and
792-
// verify that the resulting schema hash matches the reference.
794+
// verify that the resulting schema hash matches the reference and that all
795+
// expected migrations are recorded in gorp_migrations.
793796
testdataEntries, err := os.ReadDir("testdata")
794797
require.NoError(t, err)
795798

@@ -810,6 +813,18 @@ func TestMigrationFromPreviousVersion(t *testing.T) {
810813

811814
require.Equal(t, referenceHash, schemaHash(t, migratedDB),
812815
"schema mismatch for %s after applying all migrations", entry.Name())
816+
817+
// Verify all expected migrations are recorded in gorp_migrations.
818+
applied, err := db.GetMigrationsIDsApplied(migratedDB)
819+
require.NoError(t, err)
820+
appliedSet := make(map[string]struct{}, len(applied))
821+
for _, id := range applied {
822+
appliedSet[id] = struct{}{}
823+
}
824+
for _, m := range GetFullMigrations() {
825+
require.Contains(t, appliedSet, m.ID,
826+
"migration %q missing from gorp_migrations after migrating %s", m.ID, entry.Name())
827+
}
813828
})
814829
}
815830
}
4 KB
Binary file not shown.

bridgesync/processor_test.go

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -45,62 +45,6 @@ func newTestProcessor(dbPath string, syncerID string, logger *log.Logger, dbQuer
4545
return newProcessor(database, syncerID, logger, dbQueryTimeout)
4646
}
4747

48-
func TestBigIntString(t *testing.T) {
49-
globalIndex := GenerateGlobalIndex(true, 0, 1093)
50-
fmt.Println(globalIndex.String())
51-
52-
_, ok := new(big.Int).SetString(globalIndex.String(), 10)
53-
require.True(t, ok)
54-
55-
dbPath := filepath.Join(t.TempDir(), "bridgesyncTestBigIntString.sqlite")
56-
57-
err := migrations.RunMigrations(dbPath)
58-
require.NoError(t, err)
59-
db, err := db.NewSQLiteDB(dbPath)
60-
require.NoError(t, err)
61-
62-
ctx := context.Background()
63-
tx, err := db.BeginTx(ctx, nil)
64-
require.NoError(t, err)
65-
66-
claim := &claimsynctypes.Claim{
67-
BlockNum: 1,
68-
BlockPos: 0,
69-
GlobalIndex: GenerateGlobalIndex(true, 0, 1093),
70-
OriginNetwork: 11,
71-
Amount: big.NewInt(11),
72-
OriginAddress: common.HexToAddress("0x11"),
73-
DestinationAddress: common.HexToAddress("0x11"),
74-
ProofLocalExitRoot: types.Proof{},
75-
ProofRollupExitRoot: types.Proof{},
76-
MainnetExitRoot: common.Hash{},
77-
RollupExitRoot: common.Hash{},
78-
GlobalExitRoot: common.Hash{},
79-
DestinationNetwork: 12,
80-
Type: claimsynctypes.ClaimEvent,
81-
}
82-
83-
_, err = tx.Exec(`INSERT INTO block (num) VALUES ($1)`, claim.BlockNum)
84-
require.NoError(t, err)
85-
require.NoError(t, meddler.Insert(tx, "claim", claim))
86-
87-
require.NoError(t, tx.Commit())
88-
89-
tx, err = db.BeginTx(ctx, nil)
90-
require.NoError(t, err)
91-
92-
rows, err := tx.Query(`
93-
SELECT * FROM claim
94-
WHERE block_num >= $1 AND block_num <= $2;
95-
`, claim.BlockNum, claim.BlockNum)
96-
require.NoError(t, err)
97-
98-
claimsFromDB := []*claimsynctypes.Claim{}
99-
require.NoError(t, meddler.ScanAll(rows, &claimsFromDB))
100-
require.Len(t, claimsFromDB, 1)
101-
require.Equal(t, claim, claimsFromDB[0])
102-
}
103-
10448
func TestProcessor(t *testing.T) {
10549
path := path.Join(t.TempDir(), "bridgeSyncerProcessor.db")
10650
logger := log.WithFields("module", "bridge-syncer")

0 commit comments

Comments
 (0)