Files
geth/core/rawdb/freezer_meta.go
zeekayandHanzo Dev a7a07e8c5a rawdb: add the metadata reader and the Open wiring the shared store calls
freezer_shared.go opens each table with loadMetadata and refreshes it with
metadata.reload, and chain_freezer.go's constructor takes a shared flag and a
freeze threshold. Neither the metadata functions nor Open's side of that
constructor were committed, so the package did not build.

loadMetadata reads a table's metadata without ever writing to it. An empty file
means the writer has created the table but not yet described it, which a reader
waits out rather than repairs from another process. reload re-reads the record
and keeps the values it already has if it catches the writer mid-rewrite: the
previous tail is a better answer than an error, and the next pass picks up the
new one.

Open gains AncientShared and FreezeThreshold. A node reading someone else's
store freezes nothing, so it runs prune in place of freeze, dropping from its own
key-value store whatever the shared store already holds.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-25 11:38:31 -07:00

225 lines
6.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rawdb
import (
"errors"
"io"
"math"
"os"
"github.com/luxfi/geth/rlp"
log "github.com/luxfi/log"
)
const (
freezerTableV1 = 1 // Initial version of metadata struct
freezerTableV2 = 2 // Add field: 'flushOffset'
freezerVersion = freezerTableV2 // The current used version
)
// freezerTableMeta is a collection of additional properties that describe the
// freezer table. These properties are designed with error resilience, allowing
// them to be automatically corrected after an error occurs without significantly
// impacting overall correctness.
type freezerTableMeta struct {
file *os.File // file handler of metadata
version uint16 // version descriptor of the freezer table
// virtualTail represents the number of items marked as deleted. It is
// calculated as the sum of items removed from the table and the items
// hidden within the table, and should never be less than the "actual
// tail".
//
// If lost due to a crash or other reasons, it will be reset to the number
// of items deleted from the table, causing the previously hidden items
// to become visible, which is an acceptable consequence.
virtualTail uint64
// flushOffset represents the offset in the index file up to which the index
// items along with the corresponding data items in data files has been flushed
// (fsync’d) to disk. Beyond this offset, data integrity is not guaranteed,
// the extra index items along with the associated data items should be removed
// during the startup.
//
// The principle is that all data items above the flush offset are considered
// volatile and should be recoverable if they are discarded after the unclean
// shutdown. If data integrity is required, manually force a sync of the
// freezer before proceeding with further operations (e.g. do freezer.Sync()
// first and then write data to key value store in some circumstances).
//
// The offset could be moved forward by applying sync operation, or be moved
// backward in cases of head/tail truncation, etc.
flushOffset int64
}
// decodeV1 attempts to decode the metadata structure in v1 format. If fails or
// the result is incompatible, nil is returned.
func decodeV1(file *os.File) *freezerTableMeta {
_, err := file.Seek(0, io.SeekStart)
if err != nil {
return nil
}
type obj struct {
Version uint16
Tail uint64
}
var o obj
if err := rlp.Decode(file, &o); err != nil {
return nil
}
if o.Version != freezerTableV1 {
return nil
}
return &freezerTableMeta{
file: file,
version: o.Version,
virtualTail: o.Tail,
}
}
// decodeV2 attempts to decode the metadata structure in v2 format. If fails or
// the result is incompatible, nil is returned.
func decodeV2(file *os.File) *freezerTableMeta {
_, err := file.Seek(0, io.SeekStart)
if err != nil {
return nil
}
type obj struct {
Version uint16
Tail uint64
Offset uint64
}
var o obj
if err := rlp.Decode(file, &o); err != nil {
return nil
}
if o.Version != freezerTableV2 {
return nil
}
if o.Offset > math.MaxInt64 {
log.Error("Invalid flushOffset %d in freezer metadata", o.Offset, "file", file.Name())
return nil
}
return &freezerTableMeta{
file: file,
version: freezerTableV2,
virtualTail: o.Tail,
flushOffset: int64(o.Offset),
}
}
// loadMetadata reads the metadata without ever writing to the file. A shared
// reader uses it: an empty metadata file means the writer has created the table
// but not yet described it, which is a state to read again later rather than to
// repair from another process.
func loadMetadata(file *os.File) (*freezerTableMeta, error) {
stat, err := file.Stat()
if err != nil {
return nil, err
}
if stat.Size() == 0 {
return &freezerTableMeta{file: file, version: freezerVersion}, nil
}
if m := decodeV2(file); m != nil {
return m, nil
}
if m := decodeV1(file); m != nil {
return m, nil
}
return nil, errors.New("failed to decode metadata")
}
// reload re-reads the metadata from disk, keeping the values it already has if
// the file is momentarily undecodable. The writer rewrites the whole record in
// place, so a reader can catch it mid-rewrite; the previous tail is a better
// answer than an error, and the next pass picks up the new one.
func (m *freezerTableMeta) reload() {
if n := decodeV2(m.file); n != nil {
m.version, m.virtualTail, m.flushOffset = n.version, n.virtualTail, n.flushOffset
return
}
if n := decodeV1(m.file); n != nil {
m.version, m.virtualTail = n.version, n.virtualTail
}
}
// newMetadata initializes the metadata object, either by loading it from the file
// or by constructing a new one from scratch.
func newMetadata(file *os.File) (*freezerTableMeta, error) {
stat, err := file.Stat()
if err != nil {
return nil, err
}
if stat.Size() == 0 {
m := &freezerTableMeta{
file: file,
version: freezerTableV2,
virtualTail: 0,
flushOffset: 0,
}
if err := m.write(true); err != nil {
return nil, err
}
return m, nil
}
if m := decodeV2(file); m != nil {
return m, nil
}
if m := decodeV1(file); m != nil {
return m, nil // legacy metadata
}
return nil, errors.New("failed to decode metadata")
}
// setVirtualTail sets the virtual tail and flushes the metadata if sync is true.
func (m *freezerTableMeta) setVirtualTail(tail uint64, sync bool) error {
m.virtualTail = tail
return m.write(sync)
}
// setFlushOffset sets the flush offset and flushes the metadata if sync is true.
func (m *freezerTableMeta) setFlushOffset(offset int64, sync bool) error {
m.flushOffset = offset
return m.write(sync)
}
// write flushes the content of metadata into file and performs a fsync if required.
func (m *freezerTableMeta) write(sync bool) error {
type obj struct {
Version uint16
Tail uint64
Offset uint64
}
var o obj
o.Version = freezerVersion // forcibly use the current version
o.Tail = m.virtualTail
o.Offset = uint64(m.flushOffset)
_, err := m.file.Seek(0, io.SeekStart)
if err != nil {
return err
}
if err := rlp.Encode(m.file, &o); err != nil {
return err
}
if !sync {
return nil
}
return m.file.Sync()
}