github.com/ethereum/go-ethereum@v1.16.1/core/rawdb/freezer_meta.go (about)

     1  // Copyright 2022 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  	"io"
    22  	"math"
    23  	"os"
    24  
    25  	"github.com/ethereum/go-ethereum/log"
    26  	"github.com/ethereum/go-ethereum/rlp"
    27  )
    28  
    29  const (
    30  	freezerTableV1 = 1              // Initial version of metadata struct
    31  	freezerTableV2 = 2              // Add field: 'flushOffset'
    32  	freezerVersion = freezerTableV2 // The current used version
    33  )
    34  
    35  // freezerTableMeta is a collection of additional properties that describe the
    36  // freezer table. These properties are designed with error resilience, allowing
    37  // them to be automatically corrected after an error occurs without significantly
    38  // impacting overall correctness.
    39  type freezerTableMeta struct {
    40  	file    *os.File // file handler of metadata
    41  	version uint16   // version descriptor of the freezer table
    42  
    43  	// virtualTail represents the number of items marked as deleted. It is
    44  	// calculated as the sum of items removed from the table and the items
    45  	// hidden within the table, and should never be less than the "actual
    46  	// tail".
    47  	//
    48  	// If lost due to a crash or other reasons, it will be reset to the number
    49  	// of items deleted from the table, causing the previously hidden items
    50  	// to become visible, which is an acceptable consequence.
    51  	virtualTail uint64
    52  
    53  	// flushOffset represents the offset in the index file up to which the index
    54  	// items along with the corresponding data items in data files has been flushed
    55  	// (fsync’d) to disk. Beyond this offset, data integrity is not guaranteed,
    56  	// the extra index items along with the associated data items should be removed
    57  	// during the startup.
    58  	//
    59  	// The principle is that all data items above the flush offset are considered
    60  	// volatile and should be recoverable if they are discarded after the unclean
    61  	// shutdown. If data integrity is required, manually force a sync of the
    62  	// freezer before proceeding with further operations (e.g. do freezer.Sync()
    63  	// first and then write data to key value store in some circumstances).
    64  	//
    65  	// The offset could be moved forward by applying sync operation, or be moved
    66  	// backward in cases of head/tail truncation, etc.
    67  	flushOffset int64
    68  }
    69  
    70  // decodeV1 attempts to decode the metadata structure in v1 format. If fails or
    71  // the result is incompatible, nil is returned.
    72  func decodeV1(file *os.File) *freezerTableMeta {
    73  	_, err := file.Seek(0, io.SeekStart)
    74  	if err != nil {
    75  		return nil
    76  	}
    77  	type obj struct {
    78  		Version uint16
    79  		Tail    uint64
    80  	}
    81  	var o obj
    82  	if err := rlp.Decode(file, &o); err != nil {
    83  		return nil
    84  	}
    85  	if o.Version != freezerTableV1 {
    86  		return nil
    87  	}
    88  	return &freezerTableMeta{
    89  		file:        file,
    90  		version:     o.Version,
    91  		virtualTail: o.Tail,
    92  	}
    93  }
    94  
    95  // decodeV2 attempts to decode the metadata structure in v2 format. If fails or
    96  // the result is incompatible, nil is returned.
    97  func decodeV2(file *os.File) *freezerTableMeta {
    98  	_, err := file.Seek(0, io.SeekStart)
    99  	if err != nil {
   100  		return nil
   101  	}
   102  	type obj struct {
   103  		Version uint16
   104  		Tail    uint64
   105  		Offset  uint64
   106  	}
   107  	var o obj
   108  	if err := rlp.Decode(file, &o); err != nil {
   109  		return nil
   110  	}
   111  	if o.Version != freezerTableV2 {
   112  		return nil
   113  	}
   114  	if o.Offset > math.MaxInt64 {
   115  		log.Error("Invalid flushOffset %d in freezer metadata", o.Offset, "file", file.Name())
   116  		return nil
   117  	}
   118  	return &freezerTableMeta{
   119  		file:        file,
   120  		version:     freezerTableV2,
   121  		virtualTail: o.Tail,
   122  		flushOffset: int64(o.Offset),
   123  	}
   124  }
   125  
   126  // newMetadata initializes the metadata object, either by loading it from the file
   127  // or by constructing a new one from scratch.
   128  func newMetadata(file *os.File) (*freezerTableMeta, error) {
   129  	stat, err := file.Stat()
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	if stat.Size() == 0 {
   134  		m := &freezerTableMeta{
   135  			file:        file,
   136  			version:     freezerTableV2,
   137  			virtualTail: 0,
   138  			flushOffset: 0,
   139  		}
   140  		if err := m.write(true); err != nil {
   141  			return nil, err
   142  		}
   143  		return m, nil
   144  	}
   145  	if m := decodeV2(file); m != nil {
   146  		return m, nil
   147  	}
   148  	if m := decodeV1(file); m != nil {
   149  		return m, nil // legacy metadata
   150  	}
   151  	return nil, errors.New("failed to decode metadata")
   152  }
   153  
   154  // setVirtualTail sets the virtual tail and flushes the metadata if sync is true.
   155  func (m *freezerTableMeta) setVirtualTail(tail uint64, sync bool) error {
   156  	m.virtualTail = tail
   157  	return m.write(sync)
   158  }
   159  
   160  // setFlushOffset sets the flush offset and flushes the metadata if sync is true.
   161  func (m *freezerTableMeta) setFlushOffset(offset int64, sync bool) error {
   162  	m.flushOffset = offset
   163  	return m.write(sync)
   164  }
   165  
   166  // write flushes the content of metadata into file and performs a fsync if required.
   167  func (m *freezerTableMeta) write(sync bool) error {
   168  	type obj struct {
   169  		Version uint16
   170  		Tail    uint64
   171  		Offset  uint64
   172  	}
   173  	var o obj
   174  	o.Version = freezerVersion // forcibly use the current version
   175  	o.Tail = m.virtualTail
   176  	o.Offset = uint64(m.flushOffset)
   177  
   178  	_, err := m.file.Seek(0, io.SeekStart)
   179  	if err != nil {
   180  		return err
   181  	}
   182  	if err := rlp.Encode(m.file, &o); err != nil {
   183  		return err
   184  	}
   185  	if !sync {
   186  		return nil
   187  	}
   188  	return m.file.Sync()
   189  }