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 }