github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/cmd/geth/migrate_datadir.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package main
    18  
    19  import (
    20  	"fmt"
    21  	"math/big"
    22  	"os"
    23  	"path/filepath"
    24  
    25  	"github.com/ethereumproject/go-ethereum/common"
    26  	"github.com/ethereumproject/go-ethereum/core"
    27  	"github.com/ethereumproject/go-ethereum/ethdb"
    28  	"github.com/ethereumproject/go-ethereum/logger"
    29  	"github.com/ethereumproject/go-ethereum/logger/glog"
    30  	"gopkg.in/urfave/cli.v1"
    31  )
    32  
    33  // handleIfDataDirSchemaMigrations is a handlers for the conditional logic around
    34  // data/chain dir migrations from geth versions < 3.4 and in consideration of EF geth schemas used for ETC.
    35  func handleIfDataDirSchemaMigrations(ctx *cli.Context) error {
    36  
    37  	origV := int(*glog.GetVerbosity())
    38  	if origV == 0 {
    39  		origV = glog.DefaultVerbosity
    40  	}
    41  
    42  	// Turn verbosity down for migration check. If migration happens, it will print to Warn.
    43  	// Otherwise logs are just debuggers.
    44  	glog.SetToStderr(true)
    45  	glog.SetV(3)
    46  
    47  	if shouldAttemptDirMigration(ctx) {
    48  		// Rename existing default datadir <home>/<Ethereum>/ to <home>/<EthereumClassic>.
    49  		// Only do this if --datadir flag is not specified AND <home>/<EthereumClassic> does NOT already exist (only migrate once and only for defaulty).
    50  		// If it finds an 'Ethereum' directory, it will check if it contains default ETC or ETHF chain data.
    51  		// If it contains ETC data, it will rename the dir. If ETHF data, if will do nothing.
    52  		if err := migrateExistingDirToClassicNamingScheme(ctx); err != nil {
    53  			return err
    54  		}
    55  
    56  		// Move existing mainnet data to pertinent chain-named subdir scheme (ie ethereum-classic/mainnet).
    57  		// This should only happen if the given (newly defined in this protocol) subdir doesn't exist,
    58  		// and the dirs&files (nodekey, dapp, keystore, chaindata, nodes) do exist,
    59  		if err := migrateToChainSubdirIfNecessary(ctx); err != nil {
    60  			return err
    61  		}
    62  	}
    63  	// (Re)set default debug verbosity level.
    64  	glog.SetV(origV)
    65  	return nil
    66  }
    67  
    68  // migrateExistingDirToClassicNamingScheme renames default base data directory ".../Ethereum" to ".../EthereumClassic", pending os customs, etc... ;-)
    69  ///
    70  // Check for preexisting **Un-classic** data directory, ie "/home/path/to/Ethereum".
    71  // If it exists, check if the data therein belongs to Classic blockchain (ie not configged as "ETF"),
    72  // and rename it to fit Classic naming convention ("/home/path/to/EthereumClassic") if that dir doesn't already exist.
    73  // This case only applies to Default, ie when a user **doesn't** provide a custom --datadir flag;
    74  // a user should be able to override a specified data dir if they want.
    75  func migrateExistingDirToClassicNamingScheme(ctx *cli.Context) error {
    76  
    77  	ethDataDirPath := common.DefaultUnclassicDataDir()
    78  	etcDataDirPath := common.DefaultDataDir()
    79  
    80  	// only if default <EthereumClassic>/ datadir doesn't already exist
    81  	if _, err := os.Stat(etcDataDirPath); err == nil {
    82  		// classic data dir already exists
    83  		glog.V(logger.Debug).Infof("Using existing ETClassic data directory at: %v\n", etcDataDirPath)
    84  		return nil
    85  	}
    86  
    87  	ethChainDBPath := filepath.Join(ethDataDirPath, "chaindata")
    88  	if chainIsMorden(ctx) {
    89  		ethChainDBPath = filepath.Join(ethDataDirPath, "testnet", "chaindata")
    90  	}
    91  
    92  	// only if ETHdatadir chaindb path DOES already exist, so return nil if it doesn't;
    93  	// otherwise NewLDBDatabase will create an empty one there.
    94  	// note that this uses the 'old' non-subdirectory way of holding default data.
    95  	// it must be called before migrating to subdirectories
    96  	// NOTE: Since ETH stores chaindata by default in Ethereum/geth/..., this path
    97  	// will not exist if the existing data belongs to ETH, so it works as a valid check for us as well.
    98  	if _, err := os.Stat(ethChainDBPath); os.IsNotExist(err) {
    99  		glog.V(logger.Debug).Warnf(`No existing default chaindata dir found at: %v
   100  		  	Using default data directory at: %v`,
   101  			ethChainDBPath, etcDataDirPath)
   102  		return nil
   103  	}
   104  
   105  	foundCorrectLookingFiles := []string{}
   106  	requiredFiles := []string{"LOG", "LOCK", "CURRENT"}
   107  	for _, f := range requiredFiles {
   108  		p := filepath.Join(ethChainDBPath, f)
   109  		if _, err := os.Stat(p); os.IsNotExist(err) {
   110  			glog.V(logger.Debug).Warnf(`No existing default file found at: %v
   111  		  	Using default data directory at: %v`,
   112  				p, etcDataDirPath)
   113  		} else {
   114  			foundCorrectLookingFiles = append(foundCorrectLookingFiles, f)
   115  		}
   116  	}
   117  	hasRequiredFiles := len(requiredFiles) == len(foundCorrectLookingFiles)
   118  	if !hasRequiredFiles {
   119  		return nil
   120  	}
   121  
   122  	// check if there is existing etf blockchain data in unclassic default dir (ie /<home>/Ethereum)
   123  	chainDB, err := ethdb.NewLDBDatabase(ethChainDBPath, 0, 0)
   124  	if err != nil {
   125  		glog.V(logger.Debug).Warnf(`Failed to check blockchain compatibility for existing Ethereum chaindata database at: %v
   126  		 	Using default data directory at: %v`,
   127  			err, etcDataDirPath)
   128  		return nil
   129  	}
   130  
   131  	defer chainDB.Close()
   132  
   133  	// Only move if defaulty ETC (mainnet or testnet).
   134  	// Get head block if testnet, fork block if mainnet.
   135  	hh := core.GetHeadBlockHash(chainDB) // get last block in fork
   136  	if ctx.GlobalBool(aliasableName(FastSyncFlag.Name, ctx)) {
   137  		hh = core.GetHeadFastBlockHash(chainDB)
   138  	}
   139  	if hh.IsEmpty() {
   140  		glog.V(logger.Debug).Warnln("There was no head block for the old database. It could be very young.")
   141  	}
   142  
   143  	hasRequiredForkIfSufficientHeight := true
   144  	if !hh.IsEmpty() {
   145  		// if head block < 1920000, then its compatible
   146  		// if head block >= 1920000, then it must have a hash matching required hash
   147  
   148  		// Use default configuration to check if known fork, if block 1920000 exists.
   149  		// If block1920000 doesn't exist, given above checks for directory structure expectations,
   150  		// I think it's safe to assume that the chaindata directory is just too 'young', where it hasn't
   151  		// synced until block 1920000, and therefore can be migrated.
   152  		conf := core.DefaultConfigMainnet.ChainConfig
   153  		if chainIsMorden(ctx) {
   154  			conf = core.DefaultConfigMorden.ChainConfig
   155  		}
   156  
   157  		hf := conf.ForkByName("The DAO Hard Fork")
   158  		if hf == nil || hf.Block == nil || new(big.Int).Cmp(hf.Block) == 0 || hf.RequiredHash.IsEmpty() {
   159  			glog.V(logger.Debug).Warnln("DAO Hard Fork required hash not configured for database chain. Not migrating.")
   160  			return nil
   161  		}
   162  
   163  		b := core.GetBlock(chainDB, hh)
   164  		if b == nil {
   165  			glog.V(logger.Debug).Warnf("There was a problem checking the head block of old-namespaced database. The head hash was: %v", hh.Hex())
   166  			return nil
   167  		}
   168  
   169  		// if head block >= 1920000
   170  		if b.Number().Cmp(hf.Block) >= 0 {
   171  			// now, since we know that the height is bigger than the hardfork, we have to check that the db contains the required hardfork hash
   172  			glog.V(logger.Debug).Infof("Existing head block in old data dir has sufficient height: %v", b.String())
   173  
   174  			hasRequiredForkIfSufficientHeight = false
   175  			bf := core.GetBlock(chainDB, hf.RequiredHash)
   176  			// does not have required block by hash
   177  			if bf != nil {
   178  				glog.V(logger.Debug).Infof("Head block has sufficient height AND required hash: %v", b.String())
   179  				hasRequiredForkIfSufficientHeight = true
   180  			} else {
   181  				glog.V(logger.Debug).Infof("Head block has sufficient height but not required hash: %v", b.String())
   182  			}
   183  			// head block < 1920000
   184  		} else {
   185  			glog.V(logger.Debug).Infof("Existing head block in old data dir has INSUFFICIENT height to differentiate ETC/ETF: %v", b.String())
   186  		}
   187  	}
   188  
   189  	if hasRequiredForkIfSufficientHeight {
   190  		// if any of the LOG, LOCK, or CURRENT files are missing from old chaindata/, don't migrate
   191  		glog.V(logger.Warn).Warnf(`Found existing data directory named 'Ethereum' with default ETC chaindata.
   192  		  	Moving it from: %v, to: %v
   193  		  	To specify a different data directory use the '--datadir' flag.`,
   194  			ethDataDirPath, etcDataDirPath)
   195  		return os.Rename(ethDataDirPath, etcDataDirPath)
   196  	}
   197  
   198  	glog.V(logger.Debug).Infof(`Existing default Ethereum database at: %v isn't an Ethereum Classic default blockchain.
   199  	  	Will not migrate.
   200  	  	Using ETC chaindata database at: %v`,
   201  		ethDataDirPath, etcDataDirPath)
   202  	return nil
   203  }
   204  
   205  // migrateToChainSubdirIfNecessary migrates ".../EthereumClassic/nodes|chaindata|...|nodekey" --> ".../EthereumClassic/mainnet/nodes|chaindata|...|nodekey"
   206  func migrateToChainSubdirIfNecessary(ctx *cli.Context) error {
   207  	chainIdentity := mustMakeChainIdentity(ctx) // "mainnet", "morden", "custom"
   208  
   209  	datapath := mustMakeDataDir(ctx) // ".../EthereumClassic/ | --datadir"
   210  
   211  	subdirPath := MustMakeChainDataDir(ctx) // ie, <EthereumClassic>/mainnet
   212  
   213  	// check if default subdir "mainnet" exits
   214  	// NOTE: this assumes that if the migration has been run once, the "mainnet" dir will exist and will have necessary datum inside it
   215  	subdirPathInfo, err := os.Stat(subdirPath)
   216  	if err == nil {
   217  		// dir already exists
   218  		return nil
   219  	}
   220  	if subdirPathInfo != nil && !subdirPathInfo.IsDir() {
   221  		return fmt.Errorf(`%v: found file named '%v' in EthereumClassic datadir,
   222  			which conflicts with default chain directory naming convention: %v`, ErrDirectoryStructure, chainIdentity, subdirPath)
   223  	}
   224  
   225  	// 3.3 testnet uses subdir '/testnet'
   226  	if core.ChainIdentitiesMorden[chainIdentity] {
   227  		exTestDir := filepath.Join(subdirPath, "../testnet")
   228  		exTestDirInfo, e := os.Stat(exTestDir)
   229  		if e != nil && os.IsNotExist(e) {
   230  			return nil // ex testnet dir doesn't exist
   231  		}
   232  		if !exTestDirInfo.IsDir() {
   233  			return nil // don't interfere with user *file* that won't be relevant for geth
   234  		}
   235  		return os.Rename(exTestDir, subdirPath) // /testnet -> /morden
   236  	}
   237  
   238  	// mkdir -p ".../mainnet"
   239  	if err := os.MkdirAll(subdirPath, 0755); err != nil {
   240  		return err
   241  	}
   242  
   243  	// move if existing (nodekey, dapp/, keystore/, chaindata/, nodes/) into new subdirectories
   244  	for _, dir := range []string{"dapp", "keystore", "chaindata", "nodes"} {
   245  
   246  		dirPath := filepath.Join(datapath, dir)
   247  
   248  		dirInfo, e := os.Stat(dirPath)
   249  		if e != nil && os.IsNotExist(e) {
   250  			continue // dir doesn't exist
   251  		}
   252  		if !dirInfo.IsDir() {
   253  			continue // don't interfere with user *file* that won't be relevant for geth
   254  		}
   255  
   256  		dirPathUnderSubdir := filepath.Join(subdirPath, dir)
   257  		if err := os.Rename(dirPath, dirPathUnderSubdir); err != nil {
   258  			return err
   259  		}
   260  	}
   261  
   262  	// ensure nodekey exists and is file (loop lets us stay consistent in form here, an keep options open for easy other files to include)
   263  	for _, file := range []string{"nodekey", "geth.ipc"} {
   264  		filePath := filepath.Join(datapath, file)
   265  
   266  		// ensure exists and is a file
   267  		fileInfo, e := os.Stat(filePath)
   268  		if e != nil && os.IsNotExist(e) {
   269  			continue
   270  		}
   271  		if fileInfo.IsDir() {
   272  			continue // don't interfere with user dirs that won't be relevant for geth
   273  		}
   274  
   275  		filePathUnderSubdir := filepath.Join(subdirPath, file)
   276  		if err := os.Rename(filePath, filePathUnderSubdir); err != nil {
   277  			return err
   278  		}
   279  	}
   280  	return nil
   281  }