github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/integration/legacy_migrate.go (about) 1 package integration 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path" 8 "strings" 9 10 "github.com/syndtr/goleveldb/leveldb/opt" 11 "github.com/unicornultrafoundation/go-helios/u2udb" 12 "github.com/unicornultrafoundation/go-helios/u2udb/batched" 13 "github.com/unicornultrafoundation/go-helios/u2udb/pebble" 14 "github.com/unicornultrafoundation/go-helios/u2udb/skipkeys" 15 "github.com/unicornultrafoundation/go-helios/u2udb/table" 16 17 "github.com/unicornultrafoundation/go-u2u/cmd/utils" 18 "github.com/unicornultrafoundation/go-u2u/common" 19 "github.com/unicornultrafoundation/go-u2u/log" 20 "github.com/unicornultrafoundation/go-u2u/utils/dbutil/autocompact" 21 "github.com/unicornultrafoundation/go-u2u/utils/dbutil/compactdb" 22 ) 23 24 func lastKey(db u2udb.Store) []byte { 25 var start []byte 26 for { 27 for b := 0xff; b >= 0; b-- { 28 if !isEmptyDB(table.New(db, append(start, byte(b)))) { 29 start = append(start, byte(b)) 30 break 31 } 32 if b == 0 { 33 return start 34 } 35 } 36 } 37 } 38 39 type transformTask struct { 40 openSrc func() u2udb.Store 41 openDst func() u2udb.Store 42 name string 43 dir string 44 dropSrc bool 45 } 46 47 func transform(m transformTask) error { 48 openDst := func() *batched.Store { 49 return batched.Wrap(autocompact.Wrap2M(m.openDst(), opt.GiB, 16*opt.GiB, true, "")) 50 } 51 openSrc := func() *batched.Store { 52 return batched.Wrap(m.openSrc()) 53 } 54 src := openSrc() 55 defer func() { 56 _ = src.Close() 57 if m.dropSrc { 58 src.Drop() 59 } 60 }() 61 if isEmptyDB(src) { 62 return nil 63 } 64 dst := openDst() 65 66 const batchKeys = 5000000 67 keys := make([][]byte, 0, batchKeys) 68 // start from previously written data, if any 69 it := src.NewIterator(nil, lastKey(dst)) 70 defer func() { 71 // wrap with func because DBs may be reopened below 72 it.Release() 73 _ = dst.Close() 74 }() 75 log.Info("Transforming DB layout", "db", m.name) 76 for next := true; next; { 77 for len(keys) < batchKeys { 78 next = it.Next() 79 if !next { 80 break 81 } 82 err := dst.Put(it.Key(), it.Value()) 83 if err != nil { 84 utils.Fatalf("Failed to put: %v", err) 85 } 86 keys = append(keys, common.CopyBytes(it.Key())) 87 } 88 err := dst.Flush() 89 if err != nil { 90 utils.Fatalf("Failed to flush: %v", err) 91 } 92 freeSpace, err := utils.GetFreeDiskSpace(m.dir) 93 if err != nil { 94 log.Error("Failed to retrieve free disk space", "err", err) 95 } else if freeSpace < 20*opt.GiB { 96 return errors.New("not enough disk space") 97 } else if len(keys) > 0 && freeSpace < 100*opt.GiB { 98 log.Warn("Running out of disk space. Trimming source DB records", "space_GB", freeSpace/opt.GiB) 99 _, _ = dst.Stat("async_flush") 100 // release iterator so that DB could release data 101 it.Release() 102 // erase data from src 103 for _, k := range keys { 104 _ = src.Delete(k) 105 } 106 _ = src.Compact(keys[0], keys[len(keys)-1]) 107 // reopen source DB too if it doesn't release data 108 if freeSpace < 50*opt.GiB { 109 _ = src.Close() 110 src = openSrc() 111 } 112 it = src.NewIterator(nil, keys[len(keys)-1]) 113 } 114 keys = keys[:0] 115 } 116 // compact the new DB 117 if err := compactdb.Compact(dst, m.name, 16*opt.GiB); err != nil { 118 return err 119 } 120 return nil 121 } 122 123 func mustTransform(m transformTask) { 124 err := transform(m) 125 if err != nil { 126 utils.Fatalf(err.Error()) 127 } 128 } 129 130 func isEmptyDB(db u2udb.Iteratee) bool { 131 it := db.NewIterator(nil, nil) 132 defer it.Release() 133 return !it.Next() 134 } 135 136 type closebaleTable struct { 137 *table.Table 138 backend u2udb.Store 139 } 140 141 func (s *closebaleTable) Close() error { 142 return s.backend.Close() 143 } 144 145 func (s *closebaleTable) Drop() { 146 s.backend.Drop() 147 } 148 149 func newClosableTable(db u2udb.Store, prefix []byte) *closebaleTable { 150 return &closebaleTable{ 151 Table: table.New(db, prefix), 152 backend: db, 153 } 154 } 155 156 func translateGossipPrefix(p byte) byte { 157 if p == byte('!') { 158 return byte('S') 159 } 160 if p == byte('@') { 161 return byte('R') 162 } 163 if p == byte('#') { 164 return byte('Q') 165 } 166 if p == byte('$') { 167 return byte('T') 168 } 169 if p == byte('%') { 170 return byte('J') 171 } 172 if p == byte('^') { 173 return byte('E') 174 } 175 if p == byte('&') { 176 return byte('I') 177 } 178 if p == byte('*') { 179 return byte('G') 180 } 181 if p == byte('(') { 182 return byte('F') 183 } 184 return p 185 } 186 187 func migrateLegacyDBs(chaindataDir string, dbs u2udb.FlushableDBProducer, mode string, layout RoutingConfig) error { 188 { // didn't erase the brackets to avoid massive code changes 189 // migrate DB layout 190 cacheFn, err := DbCacheFdlimit(DBsCacheConfig{ 191 Table: map[string]DBCacheConfig{ 192 "": { 193 Cache: 1024 * opt.MiB, 194 Fdlimit: uint64(utils.MakeDatabaseHandles() / 2), 195 }, 196 }, 197 }) 198 if err != nil { 199 return err 200 } 201 oldDBs := pebble.NewProducer(chaindataDir, cacheFn) 202 openOldDB := func(name string) u2udb.Store { 203 db, err := oldDBs.OpenDB(name) 204 if err != nil { 205 utils.Fatalf("Failed to open %s old DB: %v", name, err) 206 } 207 return db 208 } 209 openNewDB := func(name string) u2udb.Store { 210 db, err := dbs.OpenDB(name) 211 if err != nil { 212 utils.Fatalf("Failed to open %s DB: %v", name, err) 213 } 214 return db 215 } 216 217 switch mode { 218 case "rebuild": 219 // move hashgraph, hashgraph-%d and gossip-%d DBs 220 for _, name := range oldDBs.Names() { 221 if strings.HasPrefix(name, "hashgraph") || strings.HasPrefix(name, "gossip-") { 222 mustTransform(transformTask{ 223 openSrc: func() u2udb.Store { 224 return skipkeys.Wrap(openOldDB(name), MetadataPrefix) 225 }, 226 openDst: func() u2udb.Store { 227 return openNewDB(name) 228 }, 229 name: name, 230 dir: chaindataDir, 231 }) 232 } 233 } 234 235 // move gossip DB 236 237 // move logs 238 mustTransform(transformTask{ 239 openSrc: func() u2udb.Store { 240 return newClosableTable(openOldDB("gossip"), []byte("Lr")) 241 }, 242 openDst: func() u2udb.Store { 243 return openNewDB("evm-logs/r") 244 }, 245 name: "gossip/Lr", 246 dir: chaindataDir, 247 }) 248 mustTransform(transformTask{ 249 openSrc: func() u2udb.Store { 250 return newClosableTable(openOldDB("gossip"), []byte("Lt")) 251 }, 252 openDst: func() u2udb.Store { 253 return openNewDB("evm-logs/t") 254 }, 255 name: "gossip/Lt", 256 dir: chaindataDir, 257 }) 258 259 // skip 0 prefix, as it contains flushID 260 for b := 1; b <= 0xff; b++ { 261 if b == int('L') { 262 // logs are already moved above 263 continue 264 } 265 mustTransform(transformTask{ 266 openSrc: func() u2udb.Store { 267 return newClosableTable(openOldDB("gossip"), []byte{byte(b)}) 268 }, 269 openDst: func() u2udb.Store { 270 if b == int('M') || b == int('r') || b == int('x') || b == int('X') { 271 return openNewDB("evm/" + string([]byte{byte(b)})) 272 } else { 273 return openNewDB("gossip/" + string([]byte{translateGossipPrefix(byte(b))})) 274 } 275 }, 276 name: fmt.Sprintf("gossip/%c", rune(b)), 277 dir: chaindataDir, 278 dropSrc: b == 0xff, 279 }) 280 } 281 case "reformat": 282 if !layout.Equal(PblLegacyRoutingConfig()) { 283 return errors.New("reformatting DBs: missing --db.preset=legacy-pbl flag") 284 } 285 err = os.Rename(path.Join(chaindataDir, "gossip"), path.Join(chaindataDir, "pebble-fsh", "main")) 286 if err != nil { 287 return err 288 } 289 for _, name := range oldDBs.Names() { 290 if strings.HasPrefix(name, "hashgraph") || strings.HasPrefix(name, "gossip-") { 291 err = os.Rename(path.Join(chaindataDir, name), path.Join(chaindataDir, "pebble-fsh", name)) 292 if err != nil { 293 return err 294 } 295 } 296 } 297 default: 298 return errors.New("missing --db.migration.mode flag") 299 } 300 } 301 302 return nil 303 }