mirror of https://github.com/ethereum/go-ethereum
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
5.6 KiB
189 lines
5.6 KiB
// 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/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
)
|
|
|
|
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),
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
|