gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/skynetblocklist/persist_compat.go (about)

     1  package skynetblocklist
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"time"
    11  
    12  	"gitlab.com/NebulousLabs/encoding"
    13  	"gitlab.com/NebulousLabs/errors"
    14  	"go.sia.tech/siad/crypto"
    15  	"go.sia.tech/siad/persist"
    16  	"go.sia.tech/siad/types"
    17  )
    18  
    19  const (
    20  	blacklistPersistFile string = "skynetblacklist"
    21  
    22  	// marshaledVersionLength is the length of the byte slice generated from
    23  	// marshalling the version
    24  	marshaledVersionLength = types.SpecifierLen
    25  
    26  	// persistSizeV151 is the size of a persisted merkleroot in the
    27  	// blocklist up through compat version v1.5.1. It is the length of
    28  	// `merkleroot` plus the `listed` flag (32 + 1).
    29  	persistSizeV151 uint64 = crypto.HashSize + 1
    30  )
    31  
    32  var (
    33  	blacklistMetadataHeader = types.NewSpecifier("SkynetBlacklist\n")
    34  	metadataVersionV143     = types.NewSpecifier("v1.4.3\n")
    35  	// NOTE: There is a MetadataVersionV150 in the persist package
    36  	metadataVersionV151  = types.NewSpecifier("v1.5.1\n")
    37  	metadataVersionV1510 = types.NewSpecifier("v1.5.10\n")
    38  )
    39  
    40  type (
    41  	// persistEntryV151 contains the information persisted up through compat
    42  	// version v1.5.1. The data contained is a hash and whether it should be
    43  	// listed as being in the current blocklist.
    44  	persistEntryV151 struct {
    45  		Hash   crypto.Hash
    46  		Listed bool
    47  	}
    48  )
    49  
    50  // tempPersistFileName is a helper for creating the file name for a temporary
    51  // persist file during conversion
    52  func tempPersistFileName(persistFileName string) string {
    53  	return persistFileName + "_temp"
    54  }
    55  
    56  // convertPersistence will try and convert the persistence from the oldest
    57  // persist version to the newest.
    58  //
    59  // NOTE: Errors from earlier versions will only be returned if there is an error
    60  // with the newest version
    61  func convertPersistence(persistDir string) error {
    62  	// Try converting persistence from v1.4.3 to v1.5.0
    63  	errv143Tov150 := convertPersistVersionFromv143Tov150(persistDir)
    64  
    65  	// Try converting persistence from v1.5.0 to v1.5.1
    66  	errv150TOv151 := convertPersistVersionFromv150Tov151(persistDir)
    67  
    68  	// Try converting persistence from v1.5.1 to v1.5.10
    69  	errv151TOv1510 := convertPersistVersionFromv151Tov1510(persistDir)
    70  	if errv151TOv1510 != nil {
    71  		return errors.Compose(errv143Tov150, errv150TOv151, errv151TOv1510)
    72  	}
    73  	return nil
    74  }
    75  
    76  // convertPersistVersionFromv143Tov150 handles the compatibility code for
    77  // upgrading the persistence from v1.4.3 to v1.5.0. The change in persistence is
    78  // that the hash of the merkleroot is now persisted instead of the merkleroot
    79  // itself.
    80  func convertPersistVersionFromv143Tov150(persistDir string) (err error) {
    81  	// Identify the filepath for the persist file and the temp persist file that
    82  	// will be created during the conversion of the persistence from v1.4.3 to
    83  	// v1.5.0
    84  	persistFilePath := filepath.Join(persistDir, blacklistPersistFile)
    85  	tempFilePath := filepath.Join(persistDir, tempPersistFileName(blacklistPersistFile))
    86  
    87  	// Create a temporary file from v1.4.3 persist file
    88  	readerv143, err := createTempFileFromPersistFile(persistDir, blacklistPersistFile, blacklistMetadataHeader, metadataVersionV143)
    89  	if err != nil {
    90  		return errors.AddContext(err, "unable to create temp file")
    91  	}
    92  
    93  	// Delete the v1.4.3 persist file
    94  	err = os.RemoveAll(persistFilePath)
    95  	if err != nil {
    96  		return errors.AddContext(err, "unable to remove v1.4.3 persist file from disk")
    97  	}
    98  
    99  	// Unmarshal the persistence. We can still use the same unmarshalObjects
   100  	// function since merkleroots are a crypto.Hash this code did not change
   101  	merkleroots, err := unmarshalObjectsCompat(readerv143, metadataVersionV143)
   102  	if err != nil {
   103  		return errors.AddContext(err, "unable to unmarshal persist objects")
   104  	}
   105  
   106  	// Convert merkleroots to hashes and marshal again
   107  	var buf bytes.Buffer
   108  	for mr := range merkleroots {
   109  		hash := crypto.HashObject(mr)
   110  		pe := persistEntryV151{hash, true}
   111  		bytes := encoding.Marshal(pe)
   112  		_, err = buf.Write(bytes)
   113  		if err != nil {
   114  			return errors.AddContext(err, "unable to write merkleroot to buffer")
   115  		}
   116  	}
   117  
   118  	// Initialize new v1.5.0 persistence
   119  	aopV150, _, err := persist.NewAppendOnlyPersist(persistDir, blacklistPersistFile, blacklistMetadataHeader, persist.MetadataVersionv150)
   120  	if err != nil {
   121  		return errors.AddContext(err, "unable to initialize v1.5.0 persist file")
   122  	}
   123  	defer func() {
   124  		err = errors.Compose(err, aopV150.Close())
   125  	}()
   126  
   127  	// Write the hashes to the v1.5.0 persist file
   128  	_, err = aopV150.Write(buf.Bytes())
   129  	if err != nil {
   130  		return errors.AddContext(err, "unable to write to v150 persist file")
   131  	}
   132  
   133  	// Delete the temporary file
   134  	err = os.Remove(tempFilePath)
   135  	if err != nil {
   136  		return errors.AddContext(err, "unable to remove temp file from disk")
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  // convertPersistVersionFromv150Tov151 handles the compatibility code for
   143  // upgrading the persistence from v1.5.0 to v1.5.1. The change in persistence is
   144  // in the name of the header and the version.
   145  func convertPersistVersionFromv150Tov151(persistDir string) error {
   146  	// Identify the filepath for the persist file and the temp persist file that
   147  	// will be created during the conversion of the persistence from
   148  	// v1.5.0 to v1.5.1
   149  	persistFilePath := filepath.Join(persistDir, blacklistPersistFile)
   150  	tempFilePath := filepath.Join(persistDir, tempPersistFileName(blacklistPersistFile))
   151  
   152  	// Create a temporary file from v1.5.0 persist file
   153  	readerv150, err := createTempFileFromPersistFile(persistDir, blacklistPersistFile, blacklistMetadataHeader, persist.MetadataVersionv150)
   154  	if err != nil {
   155  		return errors.AddContext(err, "unable to create temp file")
   156  	}
   157  
   158  	// Delete the v1.5.0 persist file
   159  	err = os.RemoveAll(persistFilePath)
   160  	if err != nil {
   161  		return errors.AddContext(err, "unable to remove v1.5.0 persist file from disk")
   162  	}
   163  
   164  	// Initialize new blocklist persistence
   165  	aopBlocklist, _, err := persist.NewAppendOnlyPersist(persistDir, persistFile, metadataHeader, metadataVersionV151)
   166  	if err != nil {
   167  		return errors.AddContext(err, "unable to initialize blocklist persist file")
   168  	}
   169  	defer aopBlocklist.Close()
   170  
   171  	// Write the persist data to the blocklist persist file
   172  	data, err := ioutil.ReadAll(readerv150)
   173  	if err != nil {
   174  		return errors.AddContext(err, "unable to read data from v150 reader")
   175  	}
   176  	_, err = aopBlocklist.Write(data)
   177  	if err != nil {
   178  		return errors.AddContext(err, "unable to write to blocklist persist file")
   179  	}
   180  
   181  	// Delete the temporary file
   182  	err = os.Remove(tempFilePath)
   183  	if err != nil {
   184  		return errors.AddContext(err, "unable to remove temp file from disk")
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  // convertPersistVersionFromv151Tov1510 handles the compatibility code for
   191  // upgrading the persistence from v1.5.1 to v1.5.10. The change in persistence
   192  // is the addition of the probationaryPeriodEnd field.
   193  func convertPersistVersionFromv151Tov1510(persistDir string) error {
   194  	// Identify the filepath for the persist file and the temp persist file that
   195  	// will be created during the conversion of the persistence from v1.5.1 to
   196  	// v1.5.10
   197  	persistFilePath := filepath.Join(persistDir, persistFile)
   198  	tempFilePath := filepath.Join(persistDir, tempPersistFileName(persistFile))
   199  
   200  	// Create a temporary file from v1.5.1 persist file
   201  	readerv151, err := createTempFileFromPersistFile(persistDir, persistFile, metadataHeader, metadataVersionV151)
   202  	if err != nil {
   203  		return errors.AddContext(err, "unable to create temp file")
   204  	}
   205  
   206  	// Delete the v1.5.1 persist file
   207  	err = os.RemoveAll(persistFilePath)
   208  	if err != nil {
   209  		return errors.AddContext(err, "unable to remove v1.5.1 persist file from disk")
   210  	}
   211  
   212  	// Unmarshal the persistence.
   213  	blocklist, err := unmarshalObjectsCompat(readerv151, metadataVersionV151)
   214  	if err != nil {
   215  		return errors.AddContext(err, "unable to unmarshal persist objects")
   216  	}
   217  
   218  	// Add probationaryPeriodEnd values and marshal
   219  	var buf bytes.Buffer
   220  	for hash := range blocklist {
   221  		// Initialize all entries with the default probationary period
   222  		ppe := time.Now().Unix() + DefaultProbationaryPeriod
   223  		pe := persistEntry{hash, ppe, true}
   224  		bytes := encoding.Marshal(pe)
   225  		_, err = buf.Write(bytes)
   226  		if err != nil {
   227  			return errors.AddContext(err, "unable to write to buffer")
   228  		}
   229  	}
   230  
   231  	// Initialize new v1.5.10 persistence
   232  	aopV1510, _, err := persist.NewAppendOnlyPersist(persistDir, persistFile, metadataHeader, metadataVersionV1510)
   233  	if err != nil {
   234  		return errors.AddContext(err, "unable to initialize v1.5.10 persist file")
   235  	}
   236  	defer func() {
   237  		err = errors.Compose(err, aopV1510.Close())
   238  	}()
   239  
   240  	// Write the updated blocklist to the v1.5.10 persist file
   241  	_, err = aopV1510.Write(buf.Bytes())
   242  	if err != nil {
   243  		return errors.AddContext(err, "unable to write to v150 persist file")
   244  	}
   245  
   246  	// Delete the temporary file
   247  	err = os.Remove(tempFilePath)
   248  	if err != nil {
   249  		return errors.AddContext(err, "unable to remove temp file from disk")
   250  	}
   251  
   252  	return nil
   253  }
   254  
   255  // createTempFileFromPersistFile copies the data from the persist file into
   256  // a temporary file and returns a reader for the data. This function checks for
   257  // the existence of a temp file first and will return a reader for the temporary
   258  // file if the temporary file contains a valid checksum.
   259  func createTempFileFromPersistFile(persistDir, fileName string, header, version types.Specifier) (_ io.Reader, err error) {
   260  	// Try and load the temporary file first. This is done first because an
   261  	// unclean shutdown could result in a valid temporary file existing but no
   262  	// persist file existing. In this case we do not want a call to
   263  	// NewAppendOnlyPersist to create a new persist file resulting in a loss of
   264  	// the data in the temporary file
   265  	tempFilePath := filepath.Join(persistDir, tempPersistFileName(fileName))
   266  	reader, err := loadTempFile(tempFilePath, version)
   267  	if err == nil {
   268  		// Temporary file is valid, return the reader
   269  		return reader, nil
   270  	}
   271  
   272  	// Clear any old temp file and read the persist data
   273  	data, err := removeTempFileAndReadPersistData(tempFilePath, persistDir, fileName, header, version)
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  	// Version Check
   278  	switch version {
   279  	case metadataVersionV1510:
   280  	case metadataVersionV143, persist.MetadataVersionv150, metadataVersionV151:
   281  		// write data to file
   282  		return bytes.NewReader(data), writeDataAndChecksumToFile(tempFilePath, data)
   283  	default:
   284  		return nil, errors.New("bad version")
   285  	}
   286  
   287  	// Prepend the version
   288  	versionBytes := encoding.Marshal(version)
   289  	data = append(versionBytes, data...)
   290  
   291  	// Write the checksum and data to the temp file
   292  	return bytes.NewReader(data[marshaledVersionLength:]), writeDataAndChecksumToFile(tempFilePath, data)
   293  }
   294  
   295  // loadTempFile will load a temporary file and verifies the checksum that was
   296  // prefixed. If the checksum is valid a reader will be returned.
   297  func loadTempFile(tempFilePath string, expectedVersion types.Specifier) (_ io.Reader, err error) {
   298  	// Load and verify the checksum
   299  	fileBytes, err := loadTempFileAndVerifyChecksum(tempFilePath)
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  
   304  	// Version Check
   305  	switch expectedVersion {
   306  	case metadataVersionV1510:
   307  	case metadataVersionV143, persist.MetadataVersionv150, metadataVersionV151:
   308  		// Return the data after the checksum as a reader
   309  		return bytes.NewReader(fileBytes), nil
   310  	default:
   311  		return nil, errors.New("bad version")
   312  	}
   313  
   314  	// Verify version
   315  	var version types.Specifier
   316  	versionBytes := fileBytes[:marshaledVersionLength]
   317  	err = encoding.Unmarshal(versionBytes, &version)
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  	if version != expectedVersion {
   322  		return nil, errors.New("wrong version")
   323  	}
   324  
   325  	// Return the data after the checksum as a reader
   326  	return bytes.NewReader(fileBytes[marshaledVersionLength:]), nil
   327  }
   328  
   329  // loadTempFileAndVerifyChecksum loads the temp file and verifies the checksum
   330  // then returns the remaining file bytes.
   331  func loadTempFileAndVerifyChecksum(tempFilePath string) ([]byte, error) {
   332  	// Open the temporary file
   333  	f, err := os.Open(tempFilePath)
   334  	if err != nil {
   335  		return nil, errors.AddContext(err, "unable to open temp file")
   336  	}
   337  	defer func() {
   338  		err = errors.Compose(err, f.Close())
   339  	}()
   340  
   341  	// Read file
   342  	fileBytes, err := ioutil.ReadAll(f)
   343  	if err != nil {
   344  		return nil, errors.AddContext(err, "unable to read file")
   345  	}
   346  
   347  	// Verify there is enough data for a checksum
   348  	if len(fileBytes) < crypto.HashSize {
   349  		return nil, errors.New("temp file does not contain enough bytes for a checksum")
   350  	}
   351  
   352  	// Verify checksum
   353  	checksum := fileBytes[:crypto.HashSize]
   354  	fileChecksum := crypto.HashBytes(fileBytes[crypto.HashSize:])
   355  	if !bytes.Equal(checksum, fileChecksum[:]) {
   356  		return nil, errors.New("checksum invalid")
   357  	}
   358  
   359  	// Return the valid file bytes
   360  	return fileBytes[crypto.HashSize:], nil
   361  }
   362  
   363  // loadPersist will load the persistence from the persist file in a way that
   364  // takes into account any previous persistence updates
   365  func loadPersist(persistDir string) (*persist.AppendOnlyPersist, io.Reader, error) {
   366  	// Check for any temp files or old file indicating that a persistence
   367  	// update was interrupted
   368  	//
   369  	// We check for a temp file first because in the event of an unclean shutdown
   370  	// there is the potential for a temp file to exist but no persist file. In
   371  	// this case a call to NewAppendOnlyPersist would create a new persist file
   372  	// and we would lose the information in the temp file.
   373  	//
   374  	// Check for old blacklist file
   375  	_, errBlacklist := os.Stat(filepath.Join(persistDir, blacklistPersistFile))
   376  	blacklistFileExists := !os.IsNotExist(errBlacklist)
   377  	// Check for old temp blacklist file
   378  	tempFilePathBlacklist := filepath.Join(persistDir, tempPersistFileName(blacklistPersistFile))
   379  	_, errOld := os.Stat(tempFilePathBlacklist)
   380  	blacklistTempFileExists := !os.IsNotExist(errOld)
   381  	// Check for temp blocklist file
   382  	tempFilePathBlocklist := filepath.Join(persistDir, tempPersistFileName(persistFile))
   383  	_, errBlocklist := os.Stat(tempFilePathBlocklist)
   384  	blocklistTempFileExists := !os.IsNotExist(errBlocklist)
   385  	// If any of these files exist, there was an unclean shutdown, try and
   386  	// convert the persistence.
   387  	if blacklistFileExists || blacklistTempFileExists || blocklistTempFileExists {
   388  		// Either a temp file exists or a blacklist file exists. Try and
   389  		// update the persistence.
   390  		err := convertPersistence(persistDir)
   391  		if err != nil {
   392  			return nil, nil, errors.AddContext(err, "unable to convert persistence with the existence of an old or temp file")
   393  		}
   394  	}
   395  
   396  	// Load Persistence
   397  	aop, reader, err := persist.NewAppendOnlyPersist(persistDir, persistFile, metadataHeader, metadataVersion)
   398  	if errors.Contains(err, persist.ErrWrongVersion) {
   399  		// Wrong version, try and convert persistence
   400  		err = convertPersistence(persistDir)
   401  		if err != nil {
   402  			return nil, nil, errors.AddContext(err, "unable to convert persistence after wrong version error")
   403  		}
   404  		// Load the current persistence
   405  		aop, reader, err = persist.NewAppendOnlyPersist(persistDir, persistFile, metadataHeader, metadataVersion)
   406  	}
   407  	if err != nil {
   408  		return nil, nil, errors.AddContext(err, fmt.Sprintf("unable to initialize the skynet blocklist persistence at '%v'", aop.FilePath()))
   409  	}
   410  
   411  	return aop, reader, nil
   412  }
   413  
   414  // removeTempFileAndReadPersistData removes a temp file if it exists and reads
   415  // the persist data from the persistence file.
   416  func removeTempFileAndReadPersistData(tempFilePath, persistDir, fileName string, header, version types.Specifier) ([]byte, error) {
   417  	// If there was an error loading the temporary file then we want to remove any
   418  	// file in that location.
   419  	err := os.RemoveAll(tempFilePath)
   420  	if err != nil {
   421  		return nil, err
   422  	}
   423  
   424  	// Open the persist file
   425  	aop, reader, err := persist.NewAppendOnlyPersist(persistDir, fileName, header, version)
   426  	if err != nil {
   427  		return nil, errors.AddContext(err, "unable to load persistence")
   428  	}
   429  	defer func() {
   430  		err = errors.Compose(err, aop.Close())
   431  	}()
   432  
   433  	// Read the persist file
   434  	data, err := ioutil.ReadAll(reader)
   435  	if err != nil {
   436  		return nil, errors.AddContext(err, "unable to read persist file")
   437  	}
   438  	return data, nil
   439  }
   440  
   441  // unmarshalObjectsCompat unmarshals the sia encoded objects up through compat
   442  // version v1.5.1.
   443  func unmarshalObjectsCompat(reader io.Reader, version types.Specifier) (map[crypto.Hash]struct{}, error) {
   444  	// Version Check
   445  	switch version {
   446  	case metadataVersionV143, persist.MetadataVersionv150, metadataVersionV151:
   447  	default:
   448  		return nil, errors.New("bad version")
   449  	}
   450  
   451  	blocklist := make(map[crypto.Hash]struct{})
   452  	// Unmarshal blocked links one by one until EOF.
   453  	var offset uint64
   454  	for {
   455  		buf := make([]byte, persistSizeV151)
   456  		_, err := io.ReadFull(reader, buf)
   457  		if errors.Contains(err, io.EOF) {
   458  			break
   459  		}
   460  		if err != nil {
   461  			return nil, err
   462  		}
   463  		var pe persistEntryV151
   464  		err = encoding.Unmarshal(buf, &pe)
   465  		if err != nil {
   466  			return nil, err
   467  		}
   468  		offset += persistSizeV151
   469  
   470  		if !pe.Listed {
   471  			delete(blocklist, pe.Hash)
   472  			continue
   473  		}
   474  		blocklist[pe.Hash] = struct{}{}
   475  	}
   476  	return blocklist, nil
   477  }
   478  
   479  // writeDataAndChecksumToFile generates the checksum and writes it and the data
   480  // to the temp file on disk.
   481  func writeDataAndChecksumToFile(tempFilePath string, data []byte) error {
   482  	// Create the checksum for the persist file
   483  	checksum := crypto.HashBytes(data)
   484  
   485  	// Create the temporary file
   486  	f, err := os.Create(tempFilePath)
   487  	if err != nil {
   488  		return errors.AddContext(err, "unable to open temp file")
   489  	}
   490  	defer func() {
   491  		err = errors.Compose(err, f.Close())
   492  	}()
   493  
   494  	// Write the data to the temp file, leaving space for the checksum at the
   495  	// beginning of the file
   496  	//
   497  	// We write the checksum second to protect against unclean shut downs. If we
   498  	// wrote the checksum first and then there was an unclean shut down, we would
   499  	// not be able to simply check for a checksum at the beginning of the file.
   500  	offset := int64(len(checksum))
   501  	_, err = f.WriteAt(data, offset)
   502  	if err != nil {
   503  		return errors.AddContext(err, "unable to write persist data to temp file")
   504  	}
   505  
   506  	// Write the checksum to the beginning of the file
   507  	_, err = f.WriteAt(checksum[:], 0)
   508  	if err != nil {
   509  		return errors.AddContext(err, "unable to write persist checksum to temp file")
   510  	}
   511  
   512  	// Sync writes
   513  	return f.Sync()
   514  }