github.com/ethereum/go-ethereum@v1.14.3/core/rawdb/freezer_memory.go (about)

     1  // Copyright 2024 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  	"fmt"
    22  	"sync"
    23  
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/common/math"
    26  	"github.com/ethereum/go-ethereum/ethdb"
    27  	"github.com/ethereum/go-ethereum/log"
    28  	"github.com/ethereum/go-ethereum/rlp"
    29  )
    30  
    31  // memoryTable is used to store a list of sequential items in memory.
    32  type memoryTable struct {
    33  	name   string   // Table name
    34  	items  uint64   // Number of stored items in the table, including the deleted ones
    35  	offset uint64   // Number of deleted items from the table
    36  	data   [][]byte // List of rlp-encoded items, sort in order
    37  	size   uint64   // Total memory size occupied by the table
    38  	lock   sync.RWMutex
    39  }
    40  
    41  // newMemoryTable initializes the memory table.
    42  func newMemoryTable(name string) *memoryTable {
    43  	return &memoryTable{name: name}
    44  }
    45  
    46  // has returns an indicator whether the specified data exists.
    47  func (t *memoryTable) has(number uint64) bool {
    48  	t.lock.RLock()
    49  	defer t.lock.RUnlock()
    50  
    51  	return number >= t.offset && number < t.items
    52  }
    53  
    54  // retrieve retrieves multiple items in sequence, starting from the index 'start'.
    55  // It will return:
    56  //   - at most 'count' items,
    57  //   - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize),
    58  //     but will otherwise return as many items as fit into maxByteSize.
    59  //   - if maxBytes is not specified, 'count' items will be returned if they are present
    60  func (t *memoryTable) retrieve(start uint64, count, maxBytes uint64) ([][]byte, error) {
    61  	t.lock.RLock()
    62  	defer t.lock.RUnlock()
    63  
    64  	var (
    65  		size  uint64
    66  		batch [][]byte
    67  	)
    68  	// Ensure the start is written, not deleted from the tail, and that the
    69  	// caller actually wants something.
    70  	if t.items <= start || t.offset > start || count == 0 {
    71  		return nil, errOutOfBounds
    72  	}
    73  	// Cap the item count if the retrieval is out of bound.
    74  	if start+count > t.items {
    75  		count = t.items - start
    76  	}
    77  	for n := start; n < start+count; n++ {
    78  		index := n - t.offset
    79  		if len(batch) != 0 && maxBytes != 0 && size+uint64(len(t.data[index])) > maxBytes {
    80  			return batch, nil
    81  		}
    82  		batch = append(batch, t.data[index])
    83  		size += uint64(len(t.data[index]))
    84  	}
    85  	return batch, nil
    86  }
    87  
    88  // truncateHead discards any recent data above the provided threshold number.
    89  func (t *memoryTable) truncateHead(items uint64) error {
    90  	t.lock.Lock()
    91  	defer t.lock.Unlock()
    92  
    93  	// Short circuit if nothing to delete.
    94  	if t.items <= items {
    95  		return nil
    96  	}
    97  	if items < t.offset {
    98  		return errors.New("truncation below tail")
    99  	}
   100  	t.data = t.data[:items-t.offset]
   101  	t.items = items
   102  	return nil
   103  }
   104  
   105  // truncateTail discards any recent data before the provided threshold number.
   106  func (t *memoryTable) truncateTail(items uint64) error {
   107  	t.lock.Lock()
   108  	defer t.lock.Unlock()
   109  
   110  	// Short circuit if nothing to delete.
   111  	if t.offset >= items {
   112  		return nil
   113  	}
   114  	if t.items < items {
   115  		return errors.New("truncation above head")
   116  	}
   117  	t.data = t.data[items-t.offset:]
   118  	t.offset = items
   119  	return nil
   120  }
   121  
   122  // commit merges the given item batch into table. It's presumed that the
   123  // batch is ordered and continuous with table.
   124  func (t *memoryTable) commit(batch [][]byte) error {
   125  	t.lock.Lock()
   126  	defer t.lock.Unlock()
   127  
   128  	for _, item := range batch {
   129  		t.size += uint64(len(item))
   130  	}
   131  	t.data = append(t.data, batch...)
   132  	t.items += uint64(len(batch))
   133  	return nil
   134  }
   135  
   136  // memoryBatch is the singleton batch used for ancient write.
   137  type memoryBatch struct {
   138  	data map[string][][]byte
   139  	next map[string]uint64
   140  	size map[string]int64
   141  }
   142  
   143  func newMemoryBatch() *memoryBatch {
   144  	return &memoryBatch{
   145  		data: make(map[string][][]byte),
   146  		next: make(map[string]uint64),
   147  		size: make(map[string]int64),
   148  	}
   149  }
   150  
   151  func (b *memoryBatch) reset(freezer *MemoryFreezer) {
   152  	b.data = make(map[string][][]byte)
   153  	b.next = make(map[string]uint64)
   154  	b.size = make(map[string]int64)
   155  
   156  	for name, table := range freezer.tables {
   157  		b.next[name] = table.items
   158  	}
   159  }
   160  
   161  // Append adds an RLP-encoded item.
   162  func (b *memoryBatch) Append(kind string, number uint64, item interface{}) error {
   163  	if b.next[kind] != number {
   164  		return errOutOrderInsertion
   165  	}
   166  	blob, err := rlp.EncodeToBytes(item)
   167  	if err != nil {
   168  		return err
   169  	}
   170  	b.data[kind] = append(b.data[kind], blob)
   171  	b.next[kind]++
   172  	b.size[kind] += int64(len(blob))
   173  	return nil
   174  }
   175  
   176  // AppendRaw adds an item without RLP-encoding it.
   177  func (b *memoryBatch) AppendRaw(kind string, number uint64, blob []byte) error {
   178  	if b.next[kind] != number {
   179  		return errOutOrderInsertion
   180  	}
   181  	b.data[kind] = append(b.data[kind], common.CopyBytes(blob))
   182  	b.next[kind]++
   183  	b.size[kind] += int64(len(blob))
   184  	return nil
   185  }
   186  
   187  // commit is called at the end of a write operation and writes all remaining
   188  // data to tables.
   189  func (b *memoryBatch) commit(freezer *MemoryFreezer) (items uint64, writeSize int64, err error) {
   190  	// Check that count agrees on all batches.
   191  	items = math.MaxUint64
   192  	for name, next := range b.next {
   193  		if items < math.MaxUint64 && next != items {
   194  			return 0, 0, fmt.Errorf("table %s is at item %d, want %d", name, next, items)
   195  		}
   196  		items = next
   197  	}
   198  	// Commit all table batches.
   199  	for name, batch := range b.data {
   200  		table := freezer.tables[name]
   201  		if err := table.commit(batch); err != nil {
   202  			return 0, 0, err
   203  		}
   204  		writeSize += b.size[name]
   205  	}
   206  	return items, writeSize, nil
   207  }
   208  
   209  // MemoryFreezer is an ephemeral ancient store. It implements the ethdb.AncientStore
   210  // interface and can be used along with ephemeral key-value store.
   211  type MemoryFreezer struct {
   212  	items      uint64                  // Number of items stored
   213  	tail       uint64                  // Number of the first stored item in the freezer
   214  	readonly   bool                    // Flag if the freezer is only for reading
   215  	lock       sync.RWMutex            // Lock to protect fields
   216  	tables     map[string]*memoryTable // Tables for storing everything
   217  	writeBatch *memoryBatch            // Pre-allocated write batch
   218  }
   219  
   220  // NewMemoryFreezer initializes an in-memory freezer instance.
   221  func NewMemoryFreezer(readonly bool, tableName map[string]bool) *MemoryFreezer {
   222  	tables := make(map[string]*memoryTable)
   223  	for name := range tableName {
   224  		tables[name] = newMemoryTable(name)
   225  	}
   226  	return &MemoryFreezer{
   227  		writeBatch: newMemoryBatch(),
   228  		readonly:   readonly,
   229  		tables:     tables,
   230  	}
   231  }
   232  
   233  // HasAncient returns an indicator whether the specified data exists.
   234  func (f *MemoryFreezer) HasAncient(kind string, number uint64) (bool, error) {
   235  	f.lock.RLock()
   236  	defer f.lock.RUnlock()
   237  
   238  	if table := f.tables[kind]; table != nil {
   239  		return table.has(number), nil
   240  	}
   241  	return false, nil
   242  }
   243  
   244  // Ancient retrieves an ancient binary blob from the in-memory freezer.
   245  func (f *MemoryFreezer) Ancient(kind string, number uint64) ([]byte, error) {
   246  	f.lock.RLock()
   247  	defer f.lock.RUnlock()
   248  
   249  	t := f.tables[kind]
   250  	if t == nil {
   251  		return nil, errUnknownTable
   252  	}
   253  	data, err := t.retrieve(number, 1, 0)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  	return data[0], nil
   258  }
   259  
   260  // AncientRange retrieves multiple items in sequence, starting from the index 'start'.
   261  // It will return
   262  //   - at most 'count' items,
   263  //   - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize),
   264  //     but will otherwise return as many items as fit into maxByteSize.
   265  //   - if maxBytes is not specified, 'count' items will be returned if they are present
   266  func (f *MemoryFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
   267  	f.lock.RLock()
   268  	defer f.lock.RUnlock()
   269  
   270  	t := f.tables[kind]
   271  	if t == nil {
   272  		return nil, errUnknownTable
   273  	}
   274  	return t.retrieve(start, count, maxBytes)
   275  }
   276  
   277  // Ancients returns the ancient item numbers in the freezer.
   278  func (f *MemoryFreezer) Ancients() (uint64, error) {
   279  	f.lock.RLock()
   280  	defer f.lock.RUnlock()
   281  
   282  	return f.items, nil
   283  }
   284  
   285  // Tail returns the number of first stored item in the freezer.
   286  // This number can also be interpreted as the total deleted item numbers.
   287  func (f *MemoryFreezer) Tail() (uint64, error) {
   288  	f.lock.RLock()
   289  	defer f.lock.RUnlock()
   290  
   291  	return f.tail, nil
   292  }
   293  
   294  // AncientSize returns the ancient size of the specified category.
   295  func (f *MemoryFreezer) AncientSize(kind string) (uint64, error) {
   296  	f.lock.RLock()
   297  	defer f.lock.RUnlock()
   298  
   299  	if table := f.tables[kind]; table != nil {
   300  		return table.size, nil
   301  	}
   302  	return 0, errUnknownTable
   303  }
   304  
   305  // ReadAncients runs the given read operation while ensuring that no writes take place
   306  // on the underlying freezer.
   307  func (f *MemoryFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
   308  	f.lock.RLock()
   309  	defer f.lock.RUnlock()
   310  
   311  	return fn(f)
   312  }
   313  
   314  // ModifyAncients runs the given write operation.
   315  func (f *MemoryFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) {
   316  	f.lock.Lock()
   317  	defer f.lock.Unlock()
   318  
   319  	if f.readonly {
   320  		return 0, errReadOnly
   321  	}
   322  	// Roll back all tables to the starting position in case of error.
   323  	defer func(old uint64) {
   324  		if err == nil {
   325  			return
   326  		}
   327  		// The write operation has failed. Go back to the previous item position.
   328  		for name, table := range f.tables {
   329  			err := table.truncateHead(old)
   330  			if err != nil {
   331  				log.Error("Freezer table roll-back failed", "table", name, "index", old, "err", err)
   332  			}
   333  		}
   334  	}(f.items)
   335  
   336  	// Modify the ancients in batch.
   337  	f.writeBatch.reset(f)
   338  	if err := fn(f.writeBatch); err != nil {
   339  		return 0, err
   340  	}
   341  	item, writeSize, err := f.writeBatch.commit(f)
   342  	if err != nil {
   343  		return 0, err
   344  	}
   345  	f.items = item
   346  	return writeSize, nil
   347  }
   348  
   349  // TruncateHead discards any recent data above the provided threshold number.
   350  // It returns the previous head number.
   351  func (f *MemoryFreezer) TruncateHead(items uint64) (uint64, error) {
   352  	f.lock.Lock()
   353  	defer f.lock.Unlock()
   354  
   355  	if f.readonly {
   356  		return 0, errReadOnly
   357  	}
   358  	old := f.items
   359  	if old <= items {
   360  		return old, nil
   361  	}
   362  	for _, table := range f.tables {
   363  		if err := table.truncateHead(items); err != nil {
   364  			return 0, err
   365  		}
   366  	}
   367  	f.items = items
   368  	return old, nil
   369  }
   370  
   371  // TruncateTail discards any recent data below the provided threshold number.
   372  func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) {
   373  	f.lock.Lock()
   374  	defer f.lock.Unlock()
   375  
   376  	if f.readonly {
   377  		return 0, errReadOnly
   378  	}
   379  	old := f.tail
   380  	if old >= tail {
   381  		return old, nil
   382  	}
   383  	for _, table := range f.tables {
   384  		if err := table.truncateTail(tail); err != nil {
   385  			return 0, err
   386  		}
   387  	}
   388  	f.tail = tail
   389  	return old, nil
   390  }
   391  
   392  // Sync flushes all data tables to disk.
   393  func (f *MemoryFreezer) Sync() error {
   394  	return nil
   395  }
   396  
   397  // MigrateTable processes and migrates entries of a given table to a new format.
   398  // The second argument is a function that takes a raw entry and returns it
   399  // in the newest format.
   400  func (f *MemoryFreezer) MigrateTable(string, func([]byte) ([]byte, error)) error {
   401  	return errors.New("not implemented")
   402  }
   403  
   404  // Close releases all the sources held by the memory freezer. It will panic if
   405  // any following invocation is made to a closed freezer.
   406  func (f *MemoryFreezer) Close() error {
   407  	f.lock.Lock()
   408  	defer f.lock.Unlock()
   409  
   410  	f.tables = nil
   411  	f.writeBatch = nil
   412  	return nil
   413  }
   414  
   415  // Reset drops all the data cached in the memory freezer and reset itself
   416  // back to default state.
   417  func (f *MemoryFreezer) Reset() error {
   418  	f.lock.Lock()
   419  	defer f.lock.Unlock()
   420  
   421  	tables := make(map[string]*memoryTable)
   422  	for name := range f.tables {
   423  		tables[name] = newMemoryTable(name)
   424  	}
   425  	f.tables = tables
   426  	f.items, f.tail = 0, 0
   427  	return nil
   428  }