github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/index/chunk_write.go (about)

     1  package index
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  
    11  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    12  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/colors"
    13  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config"
    14  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file"
    15  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger"
    16  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    17  )
    18  
    19  type writeReport struct {
    20  	Range        base.FileRange
    21  	nAddresses   int
    22  	nAppearances int
    23  	FileSize     int64
    24  	Snapped      bool
    25  }
    26  
    27  func (c *writeReport) Report() {
    28  	report := `Wrote {%d} address and {%d} appearance records to {$INDEX/%s.bin}`
    29  	if c.Snapped {
    30  		report += ` @(snapped to grid)}`
    31  	}
    32  	report += " (size: {%d} , span: {%d})"
    33  	logger.Info(colors.ColoredWith(fmt.Sprintf(report, c.nAddresses, c.nAppearances, c.Range, c.FileSize, c.Range.Span()), colors.BrightBlue))
    34  }
    35  
    36  func (chunk *Chunk) Write(chain string, publisher base.Address, fileName string, addrAppearanceMap map[string][]types.AppRecord, nApps int) (*writeReport, error) {
    37  	// We're going to build two tables. An addressTable and an appearanceTable. We do this as we spin
    38  	// through the map
    39  
    40  	// Create space for the two tables...
    41  	addressTable := make([]types.AddrRecord, 0, len(addrAppearanceMap))
    42  	appearanceTable := make([]types.AppRecord, 0, nApps)
    43  
    44  	// We want to sort the items in the map by address (maps in GoLang are not sorted)
    45  	sorted := []string{}
    46  	for address := range addrAppearanceMap {
    47  		sorted = append(sorted, address)
    48  	}
    49  	sort.Slice(sorted, func(i, j int) bool {
    50  		return sorted[i] < sorted[j]
    51  	})
    52  
    53  	// We need somewhere to store our progress...
    54  	offset := uint32(0)
    55  	bl := Bloom{}
    56  
    57  	// For each address in the sorted list...
    58  	for _, addrStr := range sorted {
    59  		// ...get its appearances and append them to the appearanceTable....
    60  		apps := addrAppearanceMap[addrStr]
    61  		appearanceTable = append(appearanceTable, apps...)
    62  
    63  		// ...add the address to the bloom filter...
    64  		address := base.HexToAddress(addrStr)
    65  		bl.InsertAddress(address)
    66  
    67  		// ...and append the record to the addressTable.
    68  		addressTable = append(addressTable, types.AddrRecord{
    69  			Address: address,
    70  			Offset:  offset,
    71  			Count:   uint32(len(apps)),
    72  		})
    73  
    74  		// Finally, note the next offset
    75  		offset += uint32(len(apps))
    76  	}
    77  
    78  	// At this point, the two tables and the bloom filter are fully populated. We're ready to write to disc...
    79  
    80  	// First, we backup the existing chunk if there is one...
    81  	indexFn := ToIndexPath(fileName)
    82  	tmpPath := filepath.Join(config.PathToCache(chain), "tmp")
    83  	if backup, err := file.MakeBackup(tmpPath, indexFn); err == nil {
    84  		defer func() {
    85  			backup.Restore()
    86  		}()
    87  
    88  		if fp, err := os.OpenFile(indexFn, os.O_WRONLY|os.O_CREATE, 0644); err == nil {
    89  			// defer fp.Close() // Note -- we don't defer because we want to close the file and possibly pin it below...
    90  
    91  			_, _ = fp.Seek(0, io.SeekStart) // already true, but can't hurt
    92  			header := indexHeader{
    93  				Magic:           file.MagicNumber,
    94  				Hash:            base.BytesToHash(config.HeaderHash(config.ExpectedVersion())),
    95  				AddressCount:    uint32(len(addressTable)),
    96  				AppearanceCount: uint32(len(appearanceTable)),
    97  			}
    98  			if err = binary.Write(fp, binary.LittleEndian, header); err != nil {
    99  				return nil, err
   100  			}
   101  
   102  			if err = binary.Write(fp, binary.LittleEndian, addressTable); err != nil {
   103  				return nil, err
   104  			}
   105  
   106  			if err = binary.Write(fp, binary.LittleEndian, appearanceTable); err != nil {
   107  				return nil, err
   108  			}
   109  
   110  			if err := fp.Sync(); err != nil {
   111  				return nil, err
   112  			}
   113  
   114  			if err := fp.Close(); err != nil { // Close the file so we can pin it
   115  				return nil, err
   116  			}
   117  
   118  			if _, err = bl.writeBloom(ToBloomPath(indexFn)); err != nil {
   119  				// Cleanup possibly corrupted bloom file, index gets restored by backup mechanism
   120  				_ = os.Remove(ToBloomPath(indexFn))
   121  				return nil, err
   122  			}
   123  
   124  			// We're sucessfully written the chunk, so we don't need this any more. If the pin
   125  			// fails we don't want to have to re-do this chunk, so remove this here.
   126  			backup.Clear()
   127  			return &writeReport{
   128  				Range:        base.RangeFromFilename(indexFn),
   129  				nAddresses:   len(addressTable),
   130  				nAppearances: len(appearanceTable),
   131  			}, nil
   132  
   133  		} else {
   134  			return nil, err
   135  		}
   136  
   137  	} else {
   138  		return nil, err
   139  	}
   140  }
   141  
   142  // Tag updates the manifest version in the chunk's header
   143  func (chunk *Chunk) Tag(tag, fileName string) (err error) {
   144  	blVers, idxVers, err := versions(fileName)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	if blVers == tag && idxVers == tag {
   149  		return nil
   150  	}
   151  
   152  	bloomFn := ToBloomPath(fileName)
   153  	indexFn := ToIndexPath(fileName)
   154  	indexBackup := indexFn + ".backup"
   155  	bloomBackup := bloomFn + ".backup"
   156  
   157  	defer func() {
   158  		// If the backup files still exist when the function ends, something went wrong, reset everything
   159  		if file.FileExists(indexBackup) || file.FileExists(bloomBackup) {
   160  			_, _ = file.Copy(bloomFn, bloomBackup)
   161  			_, _ = file.Copy(indexFn, indexBackup)
   162  			_ = os.Remove(bloomBackup)
   163  			_ = os.Remove(indexBackup)
   164  		}
   165  	}()
   166  
   167  	if _, err = file.Copy(indexBackup, indexFn); err != nil {
   168  		return err
   169  	} else if _, err = file.Copy(bloomBackup, bloomFn); err != nil {
   170  		return err
   171  	}
   172  
   173  	if err = chunk.Bloom.updateTag(tag, bloomFn); err != nil {
   174  		return err
   175  	}
   176  
   177  	if err = chunk.Index.updateTag(tag, indexFn); err != nil {
   178  		return err
   179  	}
   180  
   181  	_ = os.Remove(indexBackup)
   182  	_ = os.Remove(bloomBackup)
   183  
   184  	return nil
   185  }