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