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  }