github.com/codysnider/go-ethereum@v1.10.18-0.20220420071915-14f4ae99222a/core/rawdb/freezer.go (about) 1 // Copyright 2019 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 rawdb 18 19 import ( 20 "errors" 21 "fmt" 22 "io/ioutil" 23 "math" 24 "os" 25 "path/filepath" 26 "sync" 27 "sync/atomic" 28 "time" 29 30 "github.com/ethereum/go-ethereum/common" 31 "github.com/ethereum/go-ethereum/ethdb" 32 "github.com/ethereum/go-ethereum/log" 33 "github.com/ethereum/go-ethereum/metrics" 34 "github.com/ethereum/go-ethereum/params" 35 "github.com/prometheus/tsdb/fileutil" 36 ) 37 38 var ( 39 // errReadOnly is returned if the freezer is opened in read only mode. All the 40 // mutations are disallowed. 41 errReadOnly = errors.New("read only") 42 43 // errUnknownTable is returned if the user attempts to read from a table that is 44 // not tracked by the freezer. 45 errUnknownTable = errors.New("unknown table") 46 47 // errOutOrderInsertion is returned if the user attempts to inject out-of-order 48 // binary blobs into the freezer. 49 errOutOrderInsertion = errors.New("the append operation is out-order") 50 51 // errSymlinkDatadir is returned if the ancient directory specified by user 52 // is a symbolic link. 53 errSymlinkDatadir = errors.New("symbolic link datadir is not supported") 54 ) 55 56 const ( 57 // freezerRecheckInterval is the frequency to check the key-value database for 58 // chain progression that might permit new blocks to be frozen into immutable 59 // storage. 60 freezerRecheckInterval = time.Minute 61 62 // freezerBatchLimit is the maximum number of blocks to freeze in one batch 63 // before doing an fsync and deleting it from the key-value store. 64 freezerBatchLimit = 30000 65 66 // freezerTableSize defines the maximum size of freezer data files. 67 freezerTableSize = 2 * 1000 * 1000 * 1000 68 ) 69 70 // freezer is a memory mapped append-only database to store immutable chain data 71 // into flat files: 72 // 73 // - The append only nature ensures that disk writes are minimized. 74 // - The memory mapping ensures we can max out system memory for caching without 75 // reserving it for go-ethereum. This would also reduce the memory requirements 76 // of Geth, and thus also GC overhead. 77 type freezer struct { 78 // WARNING: The `frozen` field is accessed atomically. On 32 bit platforms, only 79 // 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned, 80 // so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG). 81 frozen uint64 // Number of blocks already frozen 82 tail uint64 // Number of the first stored item in the freezer 83 threshold uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests) 84 85 // This lock synchronizes writers and the truncate operation, as well as 86 // the "atomic" (batched) read operations. 87 writeLock sync.RWMutex 88 writeBatch *freezerBatch 89 90 readonly bool 91 tables map[string]*freezerTable // Data tables for storing everything 92 instanceLock fileutil.Releaser // File-system lock to prevent double opens 93 94 trigger chan chan struct{} // Manual blocking freeze trigger, test determinism 95 96 quit chan struct{} 97 wg sync.WaitGroup 98 closeOnce sync.Once 99 } 100 101 // newFreezer creates a chain freezer that moves ancient chain data into 102 // append-only flat file containers. 103 // 104 // The 'tables' argument defines the data tables. If the value of a map 105 // entry is true, snappy compression is disabled for the table. 106 func newFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*freezer, error) { 107 // Create the initial freezer object 108 var ( 109 readMeter = metrics.NewRegisteredMeter(namespace+"ancient/read", nil) 110 writeMeter = metrics.NewRegisteredMeter(namespace+"ancient/write", nil) 111 sizeGauge = metrics.NewRegisteredGauge(namespace+"ancient/size", nil) 112 ) 113 // Ensure the datadir is not a symbolic link if it exists. 114 if info, err := os.Lstat(datadir); !os.IsNotExist(err) { 115 if info.Mode()&os.ModeSymlink != 0 { 116 log.Warn("Symbolic link ancient database is not supported", "path", datadir) 117 return nil, errSymlinkDatadir 118 } 119 } 120 // Leveldb uses LOCK as the filelock filename. To prevent the 121 // name collision, we use FLOCK as the lock name. 122 lock, _, err := fileutil.Flock(filepath.Join(datadir, "FLOCK")) 123 if err != nil { 124 return nil, err 125 } 126 // Open all the supported data tables 127 freezer := &freezer{ 128 readonly: readonly, 129 threshold: params.FullImmutabilityThreshold, 130 tables: make(map[string]*freezerTable), 131 instanceLock: lock, 132 trigger: make(chan chan struct{}), 133 quit: make(chan struct{}), 134 } 135 136 // Create the tables. 137 for name, disableSnappy := range tables { 138 table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, maxTableSize, disableSnappy, readonly) 139 if err != nil { 140 for _, table := range freezer.tables { 141 table.Close() 142 } 143 lock.Release() 144 return nil, err 145 } 146 freezer.tables[name] = table 147 } 148 149 if freezer.readonly { 150 // In readonly mode only validate, don't truncate. 151 // validate also sets `freezer.frozen`. 152 err = freezer.validate() 153 } else { 154 // Truncate all tables to common length. 155 err = freezer.repair() 156 } 157 if err != nil { 158 for _, table := range freezer.tables { 159 table.Close() 160 } 161 lock.Release() 162 return nil, err 163 } 164 165 // Create the write batch. 166 freezer.writeBatch = newFreezerBatch(freezer) 167 168 log.Info("Opened ancient database", "database", datadir, "readonly", readonly) 169 return freezer, nil 170 } 171 172 // Close terminates the chain freezer, unmapping all the data files. 173 func (f *freezer) Close() error { 174 f.writeLock.Lock() 175 defer f.writeLock.Unlock() 176 177 var errs []error 178 f.closeOnce.Do(func() { 179 close(f.quit) 180 // Wait for any background freezing to stop 181 f.wg.Wait() 182 for _, table := range f.tables { 183 if err := table.Close(); err != nil { 184 errs = append(errs, err) 185 } 186 } 187 if err := f.instanceLock.Release(); err != nil { 188 errs = append(errs, err) 189 } 190 }) 191 if errs != nil { 192 return fmt.Errorf("%v", errs) 193 } 194 return nil 195 } 196 197 // HasAncient returns an indicator whether the specified ancient data exists 198 // in the freezer. 199 func (f *freezer) HasAncient(kind string, number uint64) (bool, error) { 200 if table := f.tables[kind]; table != nil { 201 return table.has(number), nil 202 } 203 return false, nil 204 } 205 206 // Ancient retrieves an ancient binary blob from the append-only immutable files. 207 func (f *freezer) Ancient(kind string, number uint64) ([]byte, error) { 208 if table := f.tables[kind]; table != nil { 209 return table.Retrieve(number) 210 } 211 return nil, errUnknownTable 212 } 213 214 // AncientRange retrieves multiple items in sequence, starting from the index 'start'. 215 // It will return 216 // - at most 'max' items, 217 // - at least 1 item (even if exceeding the maxByteSize), but will otherwise 218 // return as many items as fit into maxByteSize. 219 func (f *freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { 220 if table := f.tables[kind]; table != nil { 221 return table.RetrieveItems(start, count, maxBytes) 222 } 223 return nil, errUnknownTable 224 } 225 226 // Ancients returns the length of the frozen items. 227 func (f *freezer) Ancients() (uint64, error) { 228 return atomic.LoadUint64(&f.frozen), nil 229 } 230 231 // Tail returns the number of first stored item in the freezer. 232 func (f *freezer) Tail() (uint64, error) { 233 return atomic.LoadUint64(&f.tail), nil 234 } 235 236 // AncientSize returns the ancient size of the specified category. 237 func (f *freezer) AncientSize(kind string) (uint64, error) { 238 // This needs the write lock to avoid data races on table fields. 239 // Speed doesn't matter here, AncientSize is for debugging. 240 f.writeLock.RLock() 241 defer f.writeLock.RUnlock() 242 243 if table := f.tables[kind]; table != nil { 244 return table.size() 245 } 246 return 0, errUnknownTable 247 } 248 249 // ReadAncients runs the given read operation while ensuring that no writes take place 250 // on the underlying freezer. 251 func (f *freezer) ReadAncients(fn func(ethdb.AncientReader) error) (err error) { 252 f.writeLock.RLock() 253 defer f.writeLock.RUnlock() 254 return fn(f) 255 } 256 257 // ModifyAncients runs the given write operation. 258 func (f *freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) { 259 if f.readonly { 260 return 0, errReadOnly 261 } 262 f.writeLock.Lock() 263 defer f.writeLock.Unlock() 264 265 // Roll back all tables to the starting position in case of error. 266 prevItem := f.frozen 267 defer func() { 268 if err != nil { 269 // The write operation has failed. Go back to the previous item position. 270 for name, table := range f.tables { 271 err := table.truncateHead(prevItem) 272 if err != nil { 273 log.Error("Freezer table roll-back failed", "table", name, "index", prevItem, "err", err) 274 } 275 } 276 } 277 }() 278 279 f.writeBatch.reset() 280 if err := fn(f.writeBatch); err != nil { 281 return 0, err 282 } 283 item, writeSize, err := f.writeBatch.commit() 284 if err != nil { 285 return 0, err 286 } 287 atomic.StoreUint64(&f.frozen, item) 288 return writeSize, nil 289 } 290 291 // TruncateHead discards any recent data above the provided threshold number. 292 func (f *freezer) TruncateHead(items uint64) error { 293 if f.readonly { 294 return errReadOnly 295 } 296 f.writeLock.Lock() 297 defer f.writeLock.Unlock() 298 299 if atomic.LoadUint64(&f.frozen) <= items { 300 return nil 301 } 302 for _, table := range f.tables { 303 if err := table.truncateHead(items); err != nil { 304 return err 305 } 306 } 307 atomic.StoreUint64(&f.frozen, items) 308 return nil 309 } 310 311 // TruncateTail discards any recent data below the provided threshold number. 312 func (f *freezer) TruncateTail(tail uint64) error { 313 if f.readonly { 314 return errReadOnly 315 } 316 f.writeLock.Lock() 317 defer f.writeLock.Unlock() 318 319 if atomic.LoadUint64(&f.tail) >= tail { 320 return nil 321 } 322 for _, table := range f.tables { 323 if err := table.truncateTail(tail); err != nil { 324 return err 325 } 326 } 327 atomic.StoreUint64(&f.tail, tail) 328 return nil 329 } 330 331 // Sync flushes all data tables to disk. 332 func (f *freezer) Sync() error { 333 var errs []error 334 for _, table := range f.tables { 335 if err := table.Sync(); err != nil { 336 errs = append(errs, err) 337 } 338 } 339 if errs != nil { 340 return fmt.Errorf("%v", errs) 341 } 342 return nil 343 } 344 345 // validate checks that every table has the same length. 346 // Used instead of `repair` in readonly mode. 347 func (f *freezer) validate() error { 348 if len(f.tables) == 0 { 349 return nil 350 } 351 var ( 352 length uint64 353 name string 354 ) 355 // Hack to get length of any table 356 for kind, table := range f.tables { 357 length = atomic.LoadUint64(&table.items) 358 name = kind 359 break 360 } 361 // Now check every table against that length 362 for kind, table := range f.tables { 363 items := atomic.LoadUint64(&table.items) 364 if length != items { 365 return fmt.Errorf("freezer tables %s and %s have differing lengths: %d != %d", kind, name, items, length) 366 } 367 } 368 atomic.StoreUint64(&f.frozen, length) 369 return nil 370 } 371 372 // repair truncates all data tables to the same length. 373 func (f *freezer) repair() error { 374 var ( 375 head = uint64(math.MaxUint64) 376 tail = uint64(0) 377 ) 378 for _, table := range f.tables { 379 items := atomic.LoadUint64(&table.items) 380 if head > items { 381 head = items 382 } 383 hidden := atomic.LoadUint64(&table.itemHidden) 384 if hidden > tail { 385 tail = hidden 386 } 387 } 388 for _, table := range f.tables { 389 if err := table.truncateHead(head); err != nil { 390 return err 391 } 392 if err := table.truncateTail(tail); err != nil { 393 return err 394 } 395 } 396 atomic.StoreUint64(&f.frozen, head) 397 atomic.StoreUint64(&f.tail, tail) 398 return nil 399 } 400 401 // freeze is a background thread that periodically checks the blockchain for any 402 // import progress and moves ancient data from the fast database into the freezer. 403 // 404 // This functionality is deliberately broken off from block importing to avoid 405 // incurring additional data shuffling delays on block propagation. 406 func (f *freezer) freeze(db ethdb.KeyValueStore) { 407 nfdb := &nofreezedb{KeyValueStore: db} 408 409 var ( 410 backoff bool 411 triggered chan struct{} // Used in tests 412 ) 413 for { 414 select { 415 case <-f.quit: 416 log.Info("Freezer shutting down") 417 return 418 default: 419 } 420 if backoff { 421 // If we were doing a manual trigger, notify it 422 if triggered != nil { 423 triggered <- struct{}{} 424 triggered = nil 425 } 426 select { 427 case <-time.NewTimer(freezerRecheckInterval).C: 428 backoff = false 429 case triggered = <-f.trigger: 430 backoff = false 431 case <-f.quit: 432 return 433 } 434 } 435 // Retrieve the freezing threshold. 436 hash := ReadHeadBlockHash(nfdb) 437 if hash == (common.Hash{}) { 438 log.Debug("Current full block hash unavailable") // new chain, empty database 439 backoff = true 440 continue 441 } 442 number := ReadHeaderNumber(nfdb, hash) 443 threshold := atomic.LoadUint64(&f.threshold) 444 445 switch { 446 case number == nil: 447 log.Error("Current full block number unavailable", "hash", hash) 448 backoff = true 449 continue 450 451 case *number < threshold: 452 log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold) 453 backoff = true 454 continue 455 456 case *number-threshold <= f.frozen: 457 log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen) 458 backoff = true 459 continue 460 } 461 head := ReadHeader(nfdb, hash, *number) 462 if head == nil { 463 log.Error("Current full block unavailable", "number", *number, "hash", hash) 464 backoff = true 465 continue 466 } 467 468 // Seems we have data ready to be frozen, process in usable batches 469 var ( 470 start = time.Now() 471 first, _ = f.Ancients() 472 limit = *number - threshold 473 ) 474 if limit-first > freezerBatchLimit { 475 limit = first + freezerBatchLimit 476 } 477 ancients, err := f.freezeRange(nfdb, first, limit) 478 if err != nil { 479 log.Error("Error in block freeze operation", "err", err) 480 backoff = true 481 continue 482 } 483 484 // Batch of blocks have been frozen, flush them before wiping from leveldb 485 if err := f.Sync(); err != nil { 486 log.Crit("Failed to flush frozen tables", "err", err) 487 } 488 489 // Wipe out all data from the active database 490 batch := db.NewBatch() 491 for i := 0; i < len(ancients); i++ { 492 // Always keep the genesis block in active database 493 if first+uint64(i) != 0 { 494 DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i)) 495 DeleteCanonicalHash(batch, first+uint64(i)) 496 } 497 } 498 if err := batch.Write(); err != nil { 499 log.Crit("Failed to delete frozen canonical blocks", "err", err) 500 } 501 batch.Reset() 502 503 // Wipe out side chains also and track dangling side chains 504 var dangling []common.Hash 505 for number := first; number < f.frozen; number++ { 506 // Always keep the genesis block in active database 507 if number != 0 { 508 dangling = ReadAllHashes(db, number) 509 for _, hash := range dangling { 510 log.Trace("Deleting side chain", "number", number, "hash", hash) 511 DeleteBlock(batch, hash, number) 512 } 513 } 514 } 515 if err := batch.Write(); err != nil { 516 log.Crit("Failed to delete frozen side blocks", "err", err) 517 } 518 batch.Reset() 519 520 // Step into the future and delete and dangling side chains 521 if f.frozen > 0 { 522 tip := f.frozen 523 for len(dangling) > 0 { 524 drop := make(map[common.Hash]struct{}) 525 for _, hash := range dangling { 526 log.Debug("Dangling parent from freezer", "number", tip-1, "hash", hash) 527 drop[hash] = struct{}{} 528 } 529 children := ReadAllHashes(db, tip) 530 for i := 0; i < len(children); i++ { 531 // Dig up the child and ensure it's dangling 532 child := ReadHeader(nfdb, children[i], tip) 533 if child == nil { 534 log.Error("Missing dangling header", "number", tip, "hash", children[i]) 535 continue 536 } 537 if _, ok := drop[child.ParentHash]; !ok { 538 children = append(children[:i], children[i+1:]...) 539 i-- 540 continue 541 } 542 // Delete all block data associated with the child 543 log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash) 544 DeleteBlock(batch, children[i], tip) 545 } 546 dangling = children 547 tip++ 548 } 549 if err := batch.Write(); err != nil { 550 log.Crit("Failed to delete dangling side blocks", "err", err) 551 } 552 } 553 554 // Log something friendly for the user 555 context := []interface{}{ 556 "blocks", f.frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen - 1, 557 } 558 if n := len(ancients); n > 0 { 559 context = append(context, []interface{}{"hash", ancients[n-1]}...) 560 } 561 log.Info("Deep froze chain segment", context...) 562 563 // Avoid database thrashing with tiny writes 564 if f.frozen-first < freezerBatchLimit { 565 backoff = true 566 } 567 } 568 } 569 570 func (f *freezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hashes []common.Hash, err error) { 571 hashes = make([]common.Hash, 0, limit-number) 572 573 _, err = f.ModifyAncients(func(op ethdb.AncientWriteOp) error { 574 for ; number <= limit; number++ { 575 // Retrieve all the components of the canonical block. 576 hash := ReadCanonicalHash(nfdb, number) 577 if hash == (common.Hash{}) { 578 return fmt.Errorf("canonical hash missing, can't freeze block %d", number) 579 } 580 header := ReadHeaderRLP(nfdb, hash, number) 581 if len(header) == 0 { 582 return fmt.Errorf("block header missing, can't freeze block %d", number) 583 } 584 body := ReadBodyRLP(nfdb, hash, number) 585 if len(body) == 0 { 586 return fmt.Errorf("block body missing, can't freeze block %d", number) 587 } 588 receipts := ReadReceiptsRLP(nfdb, hash, number) 589 if len(receipts) == 0 { 590 return fmt.Errorf("block receipts missing, can't freeze block %d", number) 591 } 592 td := ReadTdRLP(nfdb, hash, number) 593 if len(td) == 0 { 594 return fmt.Errorf("total difficulty missing, can't freeze block %d", number) 595 } 596 597 // Write to the batch. 598 if err := op.AppendRaw(freezerHashTable, number, hash[:]); err != nil { 599 return fmt.Errorf("can't write hash to freezer: %v", err) 600 } 601 if err := op.AppendRaw(freezerHeaderTable, number, header); err != nil { 602 return fmt.Errorf("can't write header to freezer: %v", err) 603 } 604 if err := op.AppendRaw(freezerBodiesTable, number, body); err != nil { 605 return fmt.Errorf("can't write body to freezer: %v", err) 606 } 607 if err := op.AppendRaw(freezerReceiptTable, number, receipts); err != nil { 608 return fmt.Errorf("can't write receipts to freezer: %v", err) 609 } 610 if err := op.AppendRaw(freezerDifficultyTable, number, td); err != nil { 611 return fmt.Errorf("can't write td to freezer: %v", err) 612 } 613 614 hashes = append(hashes, hash) 615 } 616 return nil 617 }) 618 619 return hashes, err 620 } 621 622 // convertLegacyFn takes a raw freezer entry in an older format and 623 // returns it in the new format. 624 type convertLegacyFn = func([]byte) ([]byte, error) 625 626 // MigrateTable processes the entries in a given table in sequence 627 // converting them to a new format if they're of an old format. 628 func (f *freezer) MigrateTable(kind string, convert convertLegacyFn) error { 629 if f.readonly { 630 return errReadOnly 631 } 632 f.writeLock.Lock() 633 defer f.writeLock.Unlock() 634 635 table, ok := f.tables[kind] 636 if !ok { 637 return errUnknownTable 638 } 639 // forEach iterates every entry in the table serially and in order, calling `fn` 640 // with the item as argument. If `fn` returns an error the iteration stops 641 // and that error will be returned. 642 forEach := func(t *freezerTable, offset uint64, fn func(uint64, []byte) error) error { 643 var ( 644 items = atomic.LoadUint64(&t.items) 645 batchSize = uint64(1024) 646 maxBytes = uint64(1024 * 1024) 647 ) 648 for i := offset; i < items; { 649 if i+batchSize > items { 650 batchSize = items - i 651 } 652 data, err := t.RetrieveItems(i, batchSize, maxBytes) 653 if err != nil { 654 return err 655 } 656 for j, item := range data { 657 if err := fn(i+uint64(j), item); err != nil { 658 return err 659 } 660 } 661 i += uint64(len(data)) 662 } 663 return nil 664 } 665 // TODO(s1na): This is a sanity-check since as of now no process does tail-deletion. But the migration 666 // process assumes no deletion at tail and needs to be modified to account for that. 667 if table.itemOffset > 0 || table.itemHidden > 0 { 668 return fmt.Errorf("migration not supported for tail-deleted freezers") 669 } 670 ancientsPath := filepath.Dir(table.index.Name()) 671 // Set up new dir for the migrated table, the content of which 672 // we'll at the end move over to the ancients dir. 673 migrationPath := filepath.Join(ancientsPath, "migration") 674 newTable, err := NewFreezerTable(migrationPath, kind, FreezerNoSnappy[kind], false) 675 if err != nil { 676 return err 677 } 678 var ( 679 batch = newTable.newBatch() 680 out []byte 681 start = time.Now() 682 logged = time.Now() 683 offset = newTable.items 684 ) 685 if offset > 0 { 686 log.Info("found previous migration attempt", "migrated", offset) 687 } 688 // Iterate through entries and transform them 689 if err := forEach(table, offset, func(i uint64, blob []byte) error { 690 if i%10000 == 0 && time.Since(logged) > 16*time.Second { 691 log.Info("Processing legacy elements", "count", i, "elapsed", common.PrettyDuration(time.Since(start))) 692 logged = time.Now() 693 } 694 out, err = convert(blob) 695 if err != nil { 696 return err 697 } 698 if err := batch.AppendRaw(i, out); err != nil { 699 return err 700 } 701 return nil 702 }); err != nil { 703 return err 704 } 705 if err := batch.commit(); err != nil { 706 return err 707 } 708 log.Info("Replacing old table files with migrated ones", "elapsed", common.PrettyDuration(time.Since(start))) 709 // Release and delete old table files. Note this won't 710 // delete the index file. 711 table.releaseFilesAfter(0, true) 712 713 if err := newTable.Close(); err != nil { 714 return err 715 } 716 files, err := ioutil.ReadDir(migrationPath) 717 if err != nil { 718 return err 719 } 720 // Move migrated files to ancients dir. 721 for _, f := range files { 722 // This will replace the old index file as a side-effect. 723 if err := os.Rename(filepath.Join(migrationPath, f.Name()), filepath.Join(ancientsPath, f.Name())); err != nil { 724 return err 725 } 726 } 727 // Delete by now empty dir. 728 if err := os.Remove(migrationPath); err != nil { 729 return err 730 } 731 732 return nil 733 }