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 }