github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/migration/step_06.go (about) 1 // Copyright 2024 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package migration 6 7 import ( 8 "bytes" 9 "context" 10 "errors" 11 "fmt" 12 "os" 13 "runtime" 14 "sync/atomic" 15 "time" 16 17 "github.com/ethersphere/bee/v2/pkg/log" 18 "github.com/ethersphere/bee/v2/pkg/storage" 19 "github.com/ethersphere/bee/v2/pkg/storer/internal/chunkstamp" 20 "github.com/ethersphere/bee/v2/pkg/storer/internal/reserve" 21 "github.com/ethersphere/bee/v2/pkg/storer/internal/stampindex" 22 "github.com/ethersphere/bee/v2/pkg/storer/internal/transaction" 23 "golang.org/x/sync/errgroup" 24 ) 25 26 // step_06 is a migration step that adds a stampHash to all BatchRadiusItems, ChunkBinItems and StampIndexItems. 27 func step_06(st transaction.Storage) func() error { 28 return func() error { 29 logger := log.NewLogger("migration-step-06", log.WithSink(os.Stdout)) 30 logger.Info("start adding stampHash to BatchRadiusItems, ChunkBinItems and StampIndexItems") 31 32 seenCount, doneCount, err := addStampHash(logger, st) 33 if err != nil { 34 return fmt.Errorf("add stamp hash migration: %w", err) 35 } 36 logger.Info("finished migrating items", "seen", seenCount, "migrated", doneCount) 37 return nil 38 } 39 } 40 41 func addStampHash(logger log.Logger, st transaction.Storage) (int64, int64, error) { 42 43 preBatchRadiusCnt, err := st.IndexStore().Count(&reserve.BatchRadiusItemV1{}) 44 if err != nil { 45 return 0, 0, err 46 } 47 48 preChunkBinCnt, err := st.IndexStore().Count(&reserve.ChunkBinItemV1{}) 49 if err != nil { 50 return 0, 0, err 51 } 52 53 if preBatchRadiusCnt != preChunkBinCnt { 54 return 0, 0, fmt.Errorf("pre-migration check: index counts do not match, %d vs %d. It's recommended that the repair-reserve cmd is run first", preBatchRadiusCnt, preChunkBinCnt) 55 } 56 57 // Delete epoch timestamp 58 err = st.Run(context.Background(), func(s transaction.Store) error { 59 return s.IndexStore().Delete(&reserve.EpochItem{}) 60 }) 61 if err != nil { 62 return 0, 0, err 63 } 64 65 itemC := make(chan *reserve.BatchRadiusItemV1) 66 67 errC := make(chan error, 1) 68 doneC := make(chan any) 69 defer close(doneC) 70 defer close(errC) 71 72 var eg errgroup.Group 73 eg.SetLimit(runtime.NumCPU()) 74 75 var doneCount atomic.Int64 76 var seenCount int64 77 78 go func() { 79 ticker := time.NewTicker(1 * time.Minute) 80 defer ticker.Stop() 81 for { 82 select { 83 case <-ticker.C: 84 logger.Info("still migrating items...") 85 case <-doneC: 86 return 87 } 88 } 89 }() 90 91 go func() { 92 _ = st.IndexStore().Iterate(storage.Query{ 93 Factory: func() storage.Item { return new(reserve.BatchRadiusItemV1) }, 94 }, func(result storage.Result) (bool, error) { 95 seenCount++ 96 item := result.Entry.(*reserve.BatchRadiusItemV1) 97 select { 98 case itemC <- item: 99 case err := <-errC: 100 return true, err 101 } 102 return false, nil 103 }) 104 close(itemC) 105 }() 106 107 for item := range itemC { 108 batchRadiusItemV1 := item 109 eg.Go(func() error { 110 err := st.Run(context.Background(), func(s transaction.Store) error { 111 idxStore := s.IndexStore() 112 stamp, err := chunkstamp.LoadWithBatchID(idxStore, "reserve", batchRadiusItemV1.Address, batchRadiusItemV1.BatchID) 113 if err != nil { 114 return err 115 } 116 stampHash, err := stamp.Hash() 117 if err != nil { 118 return err 119 } 120 121 // Since the ID format has changed, we should delete the old item and put a new one with the new ID format. 122 err = idxStore.Delete(batchRadiusItemV1) 123 if err != nil { 124 return err 125 } 126 err = idxStore.Put(&reserve.BatchRadiusItem{ 127 StampHash: stampHash, 128 Bin: batchRadiusItemV1.Bin, 129 BatchID: batchRadiusItemV1.BatchID, 130 Address: batchRadiusItemV1.Address, 131 BinID: batchRadiusItemV1.BinID, 132 }) 133 if err != nil { 134 return err 135 } 136 137 chunkBinItemV1 := &reserve.ChunkBinItemV1{ 138 Bin: batchRadiusItemV1.Bin, 139 BinID: batchRadiusItemV1.BinID, 140 } 141 err = idxStore.Get(chunkBinItemV1) 142 if err != nil { 143 return err 144 } 145 146 // same id. Will replace. 147 err = idxStore.Put(&reserve.ChunkBinItem{ 148 StampHash: stampHash, 149 Bin: chunkBinItemV1.Bin, 150 BinID: chunkBinItemV1.BinID, 151 Address: chunkBinItemV1.Address, 152 BatchID: chunkBinItemV1.BatchID, 153 ChunkType: chunkBinItemV1.ChunkType, 154 }) 155 if err != nil { 156 return err 157 } 158 159 // same id. Will replace. 160 stampIndexItem := &stampindex.Item{ 161 StampHash: stampHash, 162 BatchID: chunkBinItemV1.BatchID, 163 StampIndex: stamp.Index(), 164 StampTimestamp: stamp.Timestamp(), 165 ChunkAddress: chunkBinItemV1.Address, 166 } 167 stampIndexItem.SetScope([]byte("reserve")) 168 err = idxStore.Put(stampIndexItem) 169 if err != nil { 170 return err 171 } 172 doneCount.Add(1) 173 return nil 174 }) 175 if err != nil { 176 errC <- err 177 return err 178 } 179 return nil 180 }) 181 } 182 183 err = eg.Wait() 184 if err != nil { 185 return 0, 0, err 186 } 187 188 postBatchRadiusCnt, err := st.IndexStore().Count(&reserve.BatchRadiusItem{}) 189 if err != nil { 190 return 0, 0, err 191 } 192 193 postChunkBinCnt, err := st.IndexStore().Count(&reserve.ChunkBinItem{}) 194 if err != nil { 195 return 0, 0, err 196 } 197 198 if postBatchRadiusCnt != postChunkBinCnt || preBatchRadiusCnt != postBatchRadiusCnt || preChunkBinCnt != postChunkBinCnt { 199 return 0, 0, fmt.Errorf("post-migration check: index counts do not match, %d vs %d. It's recommended that the nuke cmd is run to reset the node", postBatchRadiusCnt, postChunkBinCnt) 200 } 201 202 err = st.IndexStore().Iterate(storage.Query{ 203 Factory: func() storage.Item { return new(reserve.ChunkBinItem) }, 204 }, func(result storage.Result) (bool, error) { 205 item := result.Entry.(*reserve.ChunkBinItem) 206 207 batchRadiusItem := &reserve.BatchRadiusItem{BatchID: item.BatchID, Bin: item.Bin, Address: item.Address, StampHash: item.StampHash} 208 if err := st.IndexStore().Get(batchRadiusItem); err != nil { 209 return false, fmt.Errorf("batch radius item get: %w", err) 210 } 211 212 stamp, err := chunkstamp.LoadWithBatchID(st.IndexStore(), "reserve", item.Address, item.BatchID) 213 if err != nil { 214 return false, fmt.Errorf("stamp item get: %w", err) 215 } 216 217 stampIndex, err := stampindex.Load(st.IndexStore(), "reserve", stamp) 218 if err != nil { 219 return false, fmt.Errorf("stamp index get: %w", err) 220 } 221 222 if !bytes.Equal(item.StampHash, batchRadiusItem.StampHash) { 223 return false, fmt.Errorf("batch radius item stamp hash, %x vs %x", item.StampHash, batchRadiusItem.StampHash) 224 } 225 226 if !bytes.Equal(item.StampHash, stampIndex.StampHash) { 227 return false, fmt.Errorf("stamp index item stamp hash, %x vs %x", item.StampHash, stampIndex.StampHash) 228 } 229 230 return false, nil 231 }) 232 233 if err != nil { 234 return 0, 0, errors.New("post-migration check: items fields not match. It's recommended that the nuke cmd is run to reset the node") 235 } 236 237 return seenCount, doneCount.Load(), nil 238 }