@@ -26,6 +26,31 @@ func newClassHashIngestor(
2626 return & classHashIngestor {baseIngestor : newBaseIngestor (ctx , sem , database )}
2727}
2828
29+ // Run migrates the class-hash history of a single contract.
30+ //
31+ // Legend: Bₙ = block at which the n-th class-hash *replacement* happened.
32+ // Vₙ = the class hash active *after* Bₙ; V₀ is the deploy-time hash. The
33+ // deprecated layout writes nothing at deploy: each entry is written only
34+ // on a *Replace*, and the value stored is the hash that was active before
35+ // that replace. So deprecated[B₁] = V₀ even though no replace happened at
36+ // deploy_h itself. The new layout adds an explicit deploy entry and shifts
37+ // everything else by one slot:
38+ //
39+ // block │ deprecated │ new
40+ // ─────────┼────────────────┼──────
41+ // deploy_h │ — │ V₀ ← inserted from first deprecated entry
42+ // B₁ │ V₀ │ V₁
43+ // B₂ │ V₁ │ V₂
44+ // B₃ │ V₂ │ V₃
45+ // ─────────┼────────────────┼──────
46+ // > B₃ │ contract │ V₃ (last entry — self-contained)
47+ // .ClassHash ← deprecated must reach into the Contract
48+ // record for any block past the last replace
49+ //
50+ // If the deprecated history is empty (no replaces ever), the single deploy
51+ // entry is written with contract.ClassHash directly. Deprecated rows are
52+ // deleted at the end of the run. Resume-safe: empty-deprecated + existing
53+ // deploy entry → no-op.
2954func (i * classHashIngestor ) Run (index int , addr * felt.Felt , outputs chan <- task ) error {
3055 t := & i .tasks [index ]
3156
@@ -35,64 +60,81 @@ func (i *classHashIngestor) Run(index int, addr *felt.Felt, outputs chan<- task)
3560 return fmt .Errorf ("class-hash: GetContract(%s): %w" , addr , err )
3661 }
3762
38- deployKey := db .ContractClassHashHistoryAtBlockKey (addr , contract .DeployedHeight )
39- deployEntryExists , err := i .database .Has (deployKey )
40- if err != nil {
41- return fmt .Errorf ("class-hash: Has(deploy entry): %w" , err )
42- }
43-
4463 depIt , err := i .database .NewIterator (deprecatedPrefix , true )
4564 if err != nil {
4665 return fmt .Errorf ("class-hash: open deprecated iter(%s): %w" , addr , err )
4766 }
4867 defer depIt .Close ()
4968
5069 if ! depIt .First () {
51- if deployEntryExists {
52- return nil
53- }
54- err = state .WriteClassHashHistory (
55- t .batch ,
56- addr ,
57- contract .DeployedHeight ,
58- & contract .ClassHash ,
59- )
60- if err != nil {
61- return err
62- }
63- t .completedAddrs ++
64- t .entryCount ++
65- return i .flush (t , outputs )
70+ return i .writeDeployOnly (t , outputs , addr , contract .DeployedHeight , & contract .ClassHash )
71+ }
72+ return i .writeShiftedHistory (
73+ t , outputs , depIt , deprecatedPrefix , addr ,
74+ contract .DeployedHeight , & contract .ClassHash ,
75+ )
76+ }
77+
78+ // writeDeployOnly handles the "no deprecated history" branch: write the
79+ // deploy-time entry from contract.ClassHash, unless a previous run already
80+ // wrote it.
81+ func (i * classHashIngestor ) writeDeployOnly (
82+ t * task ,
83+ outputs chan <- task ,
84+ addr * felt.Felt ,
85+ deployHeight uint64 ,
86+ classHash * felt.Felt ,
87+ ) error {
88+ deployKey := db .ContractClassHashHistoryAtBlockKey (addr , deployHeight )
89+ deployEntryExists , err := i .database .Has (deployKey )
90+ if err != nil {
91+ return fmt .Errorf ("class-hash: Has(deploy entry): %w" , err )
92+ }
93+ if deployEntryExists {
94+ return nil
6695 }
96+ if err := state .WriteClassHashHistory (t .batch , addr , deployHeight , classHash ); err != nil {
97+ return err
98+ }
99+ t .completedAddrs ++
100+ t .entryCount ++
101+ return i .flush (t , outputs )
102+ }
67103
104+ // writeShiftedHistory handles the "non-empty deprecated history" branch:
105+ // writes the deploy entry from the first deprecated value, shifts each
106+ // deprecated entry into the new layout using the next entry's pre-value
107+ // (or contract.ClassHash for the last), and deletes the deprecated rows.
108+ // depIt must be positioned at the first deprecated entry.
109+ func (i * classHashIngestor ) writeShiftedHistory (
110+ t * task ,
111+ outputs chan <- task ,
112+ depIt db.Iterator ,
113+ prefix []byte ,
114+ addr * felt.Felt ,
115+ deployHeight uint64 ,
116+ headClassHash * felt.Felt ,
117+ ) error {
68118 rawValue , err := depIt .Value ()
69119 if err != nil {
70120 return fmt .Errorf ("class-hash: read first value(%s): %w" , addr , err )
71121 }
72122 deployClassHash := felt.FromBytes [felt.Felt ](rawValue )
73- if err := state .WriteClassHashHistory (
74- t .batch ,
75- addr ,
76- contract .DeployedHeight ,
77- & deployClassHash ,
78- ); err != nil {
123+ if err := state .WriteClassHashHistory (t .batch , addr , deployHeight , & deployClassHash ); err != nil {
79124 return err
80125 }
81126 t .entryCount ++
82127 if err := i .flush (t , outputs ); err != nil {
83128 return err
84129 }
85130
86- // Shift-up loop: each block in the deprecated history gets the *next*
87- // entry's value (since in the old layout the value at block B was the
88- // value before B's write). The final block gets the head class hash.
89131 for {
90- block , err := parseBlockKey (depIt .Key (), deprecatedPrefix )
132+ block , err := parseBlockKey (depIt .Key (), prefix )
91133 if err != nil {
92134 return fmt .Errorf ("class-hash(%s): %w" , addr , err )
93135 }
94136 hasNext := depIt .Next ()
95- historyValue := contract . ClassHash
137+ historyValue := * headClassHash
96138 if hasNext {
97139 rawValue , err := depIt .Value ()
98140 if err != nil {
@@ -112,7 +154,7 @@ func (i *classHashIngestor) Run(index int, addr *felt.Felt, outputs chan<- task)
112154 }
113155 }
114156
115- if err := t .batch .DeleteRange (deprecatedPrefix , dbutils .UpperBound (deprecatedPrefix )); err != nil {
157+ if err := t .batch .DeleteRange (prefix , dbutils .UpperBound (prefix )); err != nil {
116158 return fmt .Errorf ("class-hash: DeleteRange deprecated(%s): %w" , addr , err )
117159 }
118160 t .completedAddrs ++
0 commit comments