mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-21 20:26:41 +00:00
triedb/pathdb: introduce extension to history index structure
This commit is contained in:
parent
01b39c96bf
commit
30202237de
11 changed files with 1076 additions and 503 deletions
|
|
@ -25,22 +25,28 @@ import (
|
|||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
// parseIndex parses the index data with the supplied byte stream. The index data
|
||||
// is a list of fixed-sized metadata. Empty metadata is regarded as invalid.
|
||||
func parseIndex(blob []byte) ([]*indexBlockDesc, error) {
|
||||
// parseIndex parses the index data from the provided byte stream. The index data
|
||||
// is a sequence of fixed-size metadata entries, and any empty metadata entry is
|
||||
// considered invalid.
|
||||
//
|
||||
// Each metadata entry consists of two components: the indexBlockDesc and an
|
||||
// optional extension bitmap. The bitmap length may vary across different categories,
|
||||
// but must remain consistent within the same category.
|
||||
func parseIndex(blob []byte, bitmapSize int) ([]*indexBlockDesc, error) {
|
||||
if len(blob) == 0 {
|
||||
return nil, errors.New("empty state history index")
|
||||
}
|
||||
if len(blob)%indexBlockDescSize != 0 {
|
||||
return nil, fmt.Errorf("corrupted state index, len: %d", len(blob))
|
||||
size := indexBlockDescSize + bitmapSize
|
||||
if len(blob)%size != 0 {
|
||||
return nil, fmt.Errorf("corrupted state index, len: %d, bitmap size: %d", len(blob), bitmapSize)
|
||||
}
|
||||
var (
|
||||
lastID uint32
|
||||
descList []*indexBlockDesc
|
||||
)
|
||||
for i := 0; i < len(blob)/indexBlockDescSize; i++ {
|
||||
for i := 0; i < len(blob)/size; i++ {
|
||||
var desc indexBlockDesc
|
||||
desc.decode(blob[i*indexBlockDescSize : (i+1)*indexBlockDescSize])
|
||||
desc.decode(blob[i*size : (i+1)*size])
|
||||
if desc.empty() {
|
||||
return nil, errors.New("empty state history index block")
|
||||
}
|
||||
|
|
@ -73,21 +79,22 @@ type indexReader struct {
|
|||
descList []*indexBlockDesc
|
||||
readers map[uint32]*blockReader
|
||||
state stateIdent
|
||||
bitmapSize int
|
||||
}
|
||||
|
||||
// loadIndexData loads the index data associated with the specified state.
|
||||
func loadIndexData(db ethdb.KeyValueReader, state stateIdent) ([]*indexBlockDesc, error) {
|
||||
func loadIndexData(db ethdb.KeyValueReader, state stateIdent, bitmapSize int) ([]*indexBlockDesc, error) {
|
||||
blob := readStateIndex(state, db)
|
||||
if len(blob) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return parseIndex(blob)
|
||||
return parseIndex(blob, bitmapSize)
|
||||
}
|
||||
|
||||
// newIndexReader constructs a index reader for the specified state. Reader with
|
||||
// empty data is allowed.
|
||||
func newIndexReader(db ethdb.KeyValueReader, state stateIdent) (*indexReader, error) {
|
||||
descList, err := loadIndexData(db, state)
|
||||
func newIndexReader(db ethdb.KeyValueReader, state stateIdent, bitmapSize int) (*indexReader, error) {
|
||||
descList, err := loadIndexData(db, state, bitmapSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -96,6 +103,7 @@ func newIndexReader(db ethdb.KeyValueReader, state stateIdent) (*indexReader, er
|
|||
readers: make(map[uint32]*blockReader),
|
||||
db: db,
|
||||
state: state,
|
||||
bitmapSize: bitmapSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -106,11 +114,9 @@ func (r *indexReader) refresh() error {
|
|||
// may have been modified by additional elements written to the disk.
|
||||
if len(r.descList) != 0 {
|
||||
last := r.descList[len(r.descList)-1]
|
||||
if !last.full() {
|
||||
delete(r.readers, last.id)
|
||||
}
|
||||
}
|
||||
descList, err := loadIndexData(r.db, r.state)
|
||||
descList, err := loadIndexData(r.db, r.state, r.bitmapSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -118,26 +124,10 @@ func (r *indexReader) refresh() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// newIterator creates an iterator for traversing the index entries.
|
||||
func (r *indexReader) newIterator() *indexIterator {
|
||||
return newIndexIterator(r.descList, func(id uint32) (*blockReader, error) {
|
||||
br, ok := r.readers[id]
|
||||
if !ok {
|
||||
var err error
|
||||
br, err = newBlockReader(readStateIndexBlock(r.state, r.db, id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.readers[id] = br
|
||||
}
|
||||
return br, nil
|
||||
})
|
||||
}
|
||||
|
||||
// readGreaterThan locates the first element that is greater than the specified
|
||||
// id. If no such element is found, MaxUint64 is returned.
|
||||
func (r *indexReader) readGreaterThan(id uint64) (uint64, error) {
|
||||
it := r.newIterator()
|
||||
it := r.newIterator(nil)
|
||||
found := it.SeekGT(id)
|
||||
if err := it.Error(); err != nil {
|
||||
return 0, err
|
||||
|
|
@ -159,7 +149,8 @@ type indexWriter struct {
|
|||
bw *blockWriter // The live index block writer
|
||||
frozen []*blockWriter // The finalized index block writers, waiting for flush
|
||||
lastID uint64 // The ID of the latest tracked history
|
||||
state stateIdent
|
||||
state stateIdent // The identifier of the state being indexed
|
||||
bitmapSize int // The size of optional extension bitmap
|
||||
db ethdb.KeyValueReader
|
||||
}
|
||||
|
||||
|
|
@ -167,19 +158,20 @@ type indexWriter struct {
|
|||
// it takes an integer as the limit and prunes all existing elements above that ID.
|
||||
// It's essential as the recovery mechanism after unclean shutdown during the history
|
||||
// indexing.
|
||||
func newIndexWriter(db ethdb.KeyValueReader, state stateIdent, limit uint64) (*indexWriter, error) {
|
||||
func newIndexWriter(db ethdb.KeyValueReader, state stateIdent, limit uint64, bitmapSize int) (*indexWriter, error) {
|
||||
blob := readStateIndex(state, db)
|
||||
if len(blob) == 0 {
|
||||
desc := newIndexBlockDesc(0)
|
||||
bw, _ := newBlockWriter(nil, desc, 0 /* useless if the block is empty */)
|
||||
desc := newIndexBlockDesc(0, bitmapSize)
|
||||
bw, _ := newBlockWriter(nil, desc, 0 /* useless if the block is empty */, bitmapSize != 0)
|
||||
return &indexWriter{
|
||||
descList: []*indexBlockDesc{desc},
|
||||
bw: bw,
|
||||
state: state,
|
||||
db: db,
|
||||
bitmapSize: bitmapSize,
|
||||
}, nil
|
||||
}
|
||||
descList, err := parseIndex(blob)
|
||||
descList, err := parseIndex(blob, bitmapSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -197,7 +189,7 @@ func newIndexWriter(db ethdb.KeyValueReader, state stateIdent, limit uint64) (*i
|
|||
|
||||
// Construct the writer for the last block. All elements in this block
|
||||
// that exceed the limit will be truncated.
|
||||
bw, err := newBlockWriter(indexBlock, lastDesc, limit)
|
||||
bw, err := newBlockWriter(indexBlock, lastDesc, limit, bitmapSize != 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -207,20 +199,21 @@ func newIndexWriter(db ethdb.KeyValueReader, state stateIdent, limit uint64) (*i
|
|||
bw: bw,
|
||||
state: state,
|
||||
db: db,
|
||||
bitmapSize: bitmapSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// append adds the new element into the index writer.
|
||||
func (w *indexWriter) append(id uint64) error {
|
||||
func (w *indexWriter) append(id uint64, ext []uint16) error {
|
||||
if id <= w.lastID {
|
||||
return fmt.Errorf("append element out of order, last: %d, this: %d", w.lastID, id)
|
||||
}
|
||||
if w.bw.full() {
|
||||
if w.bw.estimateFull(ext) {
|
||||
if err := w.rotate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := w.bw.append(id); err != nil {
|
||||
if err := w.bw.append(id, ext); err != nil {
|
||||
return err
|
||||
}
|
||||
w.lastID = id
|
||||
|
|
@ -233,10 +226,10 @@ func (w *indexWriter) append(id uint64) error {
|
|||
func (w *indexWriter) rotate() error {
|
||||
var (
|
||||
err error
|
||||
desc = newIndexBlockDesc(w.bw.desc.id + 1)
|
||||
desc = newIndexBlockDesc(w.bw.desc.id+1, w.bitmapSize)
|
||||
)
|
||||
w.frozen = append(w.frozen, w.bw)
|
||||
w.bw, err = newBlockWriter(nil, desc, 0 /* useless if the block is empty */)
|
||||
w.bw, err = newBlockWriter(nil, desc, 0 /* useless if the block is empty */, w.bitmapSize != 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -268,7 +261,8 @@ func (w *indexWriter) finish(batch ethdb.Batch) {
|
|||
}
|
||||
w.frozen = nil // release all the frozen writers
|
||||
|
||||
buf := make([]byte, 0, indexBlockDescSize*len(descList))
|
||||
size := indexBlockDescSize + w.bitmapSize
|
||||
buf := make([]byte, 0, size*len(descList))
|
||||
for _, desc := range descList {
|
||||
buf = append(buf, desc.encode()...)
|
||||
}
|
||||
|
|
@ -281,26 +275,28 @@ type indexDeleter struct {
|
|||
bw *blockWriter // The live index block writer
|
||||
dropped []uint32 // The list of index block id waiting for deleting
|
||||
lastID uint64 // The ID of the latest tracked history
|
||||
state stateIdent
|
||||
state stateIdent // The identifier of the state being indexed
|
||||
bitmapSize int // The size of optional extension bitmap
|
||||
db ethdb.KeyValueReader
|
||||
}
|
||||
|
||||
// newIndexDeleter constructs the index deleter for the specified state.
|
||||
func newIndexDeleter(db ethdb.KeyValueReader, state stateIdent, limit uint64) (*indexDeleter, error) {
|
||||
func newIndexDeleter(db ethdb.KeyValueReader, state stateIdent, limit uint64, bitmapSize int) (*indexDeleter, error) {
|
||||
blob := readStateIndex(state, db)
|
||||
if len(blob) == 0 {
|
||||
// TODO(rjl493456442) we can probably return an error here,
|
||||
// deleter with no data is meaningless.
|
||||
desc := newIndexBlockDesc(0)
|
||||
bw, _ := newBlockWriter(nil, desc, 0 /* useless if the block is empty */)
|
||||
desc := newIndexBlockDesc(0, bitmapSize)
|
||||
bw, _ := newBlockWriter(nil, desc, 0 /* useless if the block is empty */, bitmapSize != 0)
|
||||
return &indexDeleter{
|
||||
descList: []*indexBlockDesc{desc},
|
||||
bw: bw,
|
||||
state: state,
|
||||
bitmapSize: bitmapSize,
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
descList, err := parseIndex(blob)
|
||||
descList, err := parseIndex(blob, bitmapSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -318,7 +314,7 @@ func newIndexDeleter(db ethdb.KeyValueReader, state stateIdent, limit uint64) (*
|
|||
|
||||
// Construct the writer for the last block. All elements in this block
|
||||
// that exceed the limit will be truncated.
|
||||
bw, err := newBlockWriter(indexBlock, lastDesc, limit)
|
||||
bw, err := newBlockWriter(indexBlock, lastDesc, limit, bitmapSize != 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -327,6 +323,7 @@ func newIndexDeleter(db ethdb.KeyValueReader, state stateIdent, limit uint64) (*
|
|||
lastID: bw.last(),
|
||||
bw: bw,
|
||||
state: state,
|
||||
bitmapSize: bitmapSize,
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -364,7 +361,7 @@ func (d *indexDeleter) pop(id uint64) error {
|
|||
// Open the previous block writer for deleting
|
||||
lastDesc := d.descList[len(d.descList)-1]
|
||||
indexBlock := readStateIndexBlock(d.state, d.db, lastDesc.id)
|
||||
bw, err := newBlockWriter(indexBlock, lastDesc, lastDesc.max)
|
||||
bw, err := newBlockWriter(indexBlock, lastDesc, lastDesc.max, d.bitmapSize != 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -390,7 +387,8 @@ func (d *indexDeleter) finish(batch ethdb.Batch) {
|
|||
if d.empty() {
|
||||
deleteStateIndex(d.state, batch)
|
||||
} else {
|
||||
buf := make([]byte, 0, indexBlockDescSize*len(d.descList))
|
||||
size := indexBlockDescSize + d.bitmapSize
|
||||
buf := make([]byte, 0, size*len(d.descList))
|
||||
for _, desc := range d.descList {
|
||||
buf = append(buf, desc.encode()...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package pathdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -27,9 +28,8 @@ import (
|
|||
|
||||
const (
|
||||
indexBlockDescSize = 14 // The size of index block descriptor
|
||||
indexBlockEntriesCap = 4096 // The maximum number of entries can be grouped in a block
|
||||
indexBlockSizeCap = 4096 // The maximum size of a single index block
|
||||
indexBlockRestartLen = 256 // The restart interval length of index block
|
||||
historyIndexBatch = 8 * 1024 * 1024 // The number of state history indexes for constructing or deleting as batch
|
||||
)
|
||||
|
||||
// indexBlockDesc represents a descriptor for an index block, which contains a
|
||||
|
|
@ -39,10 +39,15 @@ type indexBlockDesc struct {
|
|||
max uint64 // The maximum state ID retained within the block
|
||||
entries uint16 // The number of state mutation records retained within the block
|
||||
id uint32 // The id of the index block
|
||||
extBitmap []byte // Optional fixed-size bitmap for the included extension elements
|
||||
}
|
||||
|
||||
func newIndexBlockDesc(id uint32) *indexBlockDesc {
|
||||
return &indexBlockDesc{id: id}
|
||||
func newIndexBlockDesc(id uint32, bitmapSize int) *indexBlockDesc {
|
||||
var bitmap []byte
|
||||
if bitmapSize > 0 {
|
||||
bitmap = make([]byte, bitmapSize)
|
||||
}
|
||||
return &indexBlockDesc{id: id, extBitmap: bitmap}
|
||||
}
|
||||
|
||||
// empty indicates whether the block is empty with no element retained.
|
||||
|
|
@ -50,26 +55,33 @@ func (d *indexBlockDesc) empty() bool {
|
|||
return d.entries == 0
|
||||
}
|
||||
|
||||
// full indicates whether the number of elements in the block exceeds the
|
||||
// preconfigured limit.
|
||||
func (d *indexBlockDesc) full() bool {
|
||||
return d.entries >= indexBlockEntriesCap
|
||||
}
|
||||
|
||||
// encode packs index block descriptor into byte stream.
|
||||
func (d *indexBlockDesc) encode() []byte {
|
||||
var buf [indexBlockDescSize]byte
|
||||
buf := make([]byte, indexBlockDescSize+len(d.extBitmap))
|
||||
binary.BigEndian.PutUint64(buf[0:8], d.max)
|
||||
binary.BigEndian.PutUint16(buf[8:10], d.entries)
|
||||
binary.BigEndian.PutUint32(buf[10:14], d.id)
|
||||
copy(buf[indexBlockDescSize:], d.extBitmap)
|
||||
return buf[:]
|
||||
}
|
||||
|
||||
// decode unpacks index block descriptor from byte stream.
|
||||
// decode unpacks index block descriptor from byte stream. It's safe to mutate
|
||||
// the provided byte stream after the function call.
|
||||
func (d *indexBlockDesc) decode(blob []byte) {
|
||||
d.max = binary.BigEndian.Uint64(blob[:8])
|
||||
d.entries = binary.BigEndian.Uint16(blob[8:10])
|
||||
d.id = binary.BigEndian.Uint32(blob[10:14])
|
||||
d.extBitmap = bytes.Clone(blob[indexBlockDescSize:])
|
||||
}
|
||||
|
||||
// copy returns a deep-copied object.
|
||||
func (d *indexBlockDesc) copy() *indexBlockDesc {
|
||||
return &indexBlockDesc{
|
||||
max: d.max,
|
||||
entries: d.entries,
|
||||
id: d.id,
|
||||
extBitmap: bytes.Clone(d.extBitmap),
|
||||
}
|
||||
}
|
||||
|
||||
// parseIndexBlock parses the index block with the supplied byte stream.
|
||||
|
|
@ -97,20 +109,38 @@ func (d *indexBlockDesc) decode(blob []byte) {
|
|||
// A uint16 can cover offsets in the range [0, 65536), which is more than enough
|
||||
// to store 4096 integers.
|
||||
//
|
||||
// Each chunk begins with the full value of the first integer, followed by
|
||||
// subsequent integers representing the differences between the current value
|
||||
// and the preceding one. Integers are encoded with variable-size for best
|
||||
// storage efficiency. Each chunk can be illustrated as below.
|
||||
// Each chunk begins with a full integer value for the first element, followed
|
||||
// by subsequent integers encoded as differences (deltas) from their preceding
|
||||
// values. All integers use variable-length encoding for optimal space efficiency.
|
||||
//
|
||||
// In the updated format, each element in the chunk may optionally include an
|
||||
// "extension" section. If an extension is present, it starts with a var-size
|
||||
// integer indicating the length of the remaining extension payload, followed by
|
||||
// that many bytes. If no extension is present, the element format is identical
|
||||
// to the original version (i.e., only the integer or delta value is encoded).
|
||||
//
|
||||
// In the trienode history index, the extension field contains the list of
|
||||
// trie node IDs that fall within this range. For the given state transition,
|
||||
// these IDs represent the specific nodes in this range that were mutated.
|
||||
//
|
||||
// Whether an element includes an extension is determined by the block reader
|
||||
// based on the specification. Conceptually, a chunk is structured as:
|
||||
//
|
||||
// Restart ---> +----------------+
|
||||
// | Full integer |
|
||||
// +----------------+
|
||||
// | (Extension?) |
|
||||
// +----------------+
|
||||
// | Diff with prev |
|
||||
// +----------------+
|
||||
// | (Extension?) |
|
||||
// +----------------+
|
||||
// | ... |
|
||||
// +----------------+
|
||||
// | Diff with prev |
|
||||
// +----------------+
|
||||
// | (Extension?) |
|
||||
// +----------------+
|
||||
//
|
||||
// Empty index block is regarded as invalid.
|
||||
func parseIndexBlock(blob []byte) ([]uint16, []byte, error) {
|
||||
|
|
@ -148,10 +178,11 @@ func parseIndexBlock(blob []byte) ([]uint16, []byte, error) {
|
|||
type blockReader struct {
|
||||
restarts []uint16
|
||||
data []byte
|
||||
hasExt bool
|
||||
}
|
||||
|
||||
// newBlockReader constructs the block reader with the supplied block data.
|
||||
func newBlockReader(blob []byte) (*blockReader, error) {
|
||||
func newBlockReader(blob []byte, hasExt bool) (*blockReader, error) {
|
||||
restarts, data, err := parseIndexBlock(blob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -159,13 +190,14 @@ func newBlockReader(blob []byte) (*blockReader, error) {
|
|||
return &blockReader{
|
||||
restarts: restarts,
|
||||
data: data, // safe to own the slice
|
||||
hasExt: hasExt, // flag whether extension should be resolved
|
||||
}, nil
|
||||
}
|
||||
|
||||
// readGreaterThan locates the first element in the block that is greater than
|
||||
// the specified value. If no such element is found, MaxUint64 is returned.
|
||||
func (br *blockReader) readGreaterThan(id uint64) (uint64, error) {
|
||||
it := newBlockIterator(br.data, br.restarts)
|
||||
it := br.newIterator(nil)
|
||||
found := it.SeekGT(id)
|
||||
if err := it.Error(); err != nil {
|
||||
return 0, err
|
||||
|
|
@ -180,17 +212,19 @@ type blockWriter struct {
|
|||
desc *indexBlockDesc // Descriptor of the block
|
||||
restarts []uint16 // Offsets into the data slice, marking the start of each section
|
||||
data []byte // Aggregated encoded data slice
|
||||
hasExt bool // Flag whether the extension field for each element exists
|
||||
}
|
||||
|
||||
// newBlockWriter constructs a block writer. In addition to the existing data
|
||||
// and block description, it takes an element ID and prunes all existing elements
|
||||
// above that ID. It's essential as the recovery mechanism after unclean shutdown
|
||||
// during the history indexing.
|
||||
func newBlockWriter(blob []byte, desc *indexBlockDesc, limit uint64) (*blockWriter, error) {
|
||||
func newBlockWriter(blob []byte, desc *indexBlockDesc, limit uint64, hasExt bool) (*blockWriter, error) {
|
||||
if len(blob) == 0 {
|
||||
return &blockWriter{
|
||||
desc: desc,
|
||||
data: make([]byte, 0, 1024),
|
||||
hasExt: hasExt,
|
||||
}, nil
|
||||
}
|
||||
restarts, data, err := parseIndexBlock(blob)
|
||||
|
|
@ -201,6 +235,7 @@ func newBlockWriter(blob []byte, desc *indexBlockDesc, limit uint64) (*blockWrit
|
|||
desc: desc,
|
||||
restarts: restarts,
|
||||
data: data, // safe to own the slice
|
||||
hasExt: hasExt,
|
||||
}
|
||||
var trimmed int
|
||||
for !writer.empty() && writer.last() > limit {
|
||||
|
|
@ -215,9 +250,26 @@ func newBlockWriter(blob []byte, desc *indexBlockDesc, limit uint64) (*blockWrit
|
|||
return writer, nil
|
||||
}
|
||||
|
||||
// setBitmap applies the given extension elements into the bitmap.
|
||||
func (b *blockWriter) setBitmap(ext []uint16) {
|
||||
for _, n := range ext {
|
||||
// Node ID zero is intentionally filtered out. Any element in this range
|
||||
// can indicate that the sub-tree's root node was mutated, so storing zero
|
||||
// is redundant and saves one byte for bitmap.
|
||||
if n != 0 {
|
||||
setBit(b.desc.extBitmap, int(n-1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// append adds a new element to the block. The new element must be greater than
|
||||
// the previous one. The provided ID is assumed to always be greater than 0.
|
||||
func (b *blockWriter) append(id uint64) error {
|
||||
//
|
||||
// ext refers to the optional extension field attached to the appended element.
|
||||
// This extension mechanism is used by trie-node history and represents a list of
|
||||
// trie node IDs that fall within the range covered by the index element
|
||||
// (typically corresponding to a sub-trie in trie-node history).
|
||||
func (b *blockWriter) append(id uint64, ext []uint16) error {
|
||||
if id == 0 {
|
||||
return errors.New("invalid zero id")
|
||||
}
|
||||
|
|
@ -244,13 +296,29 @@ func (b *blockWriter) append(id uint64) error {
|
|||
// element.
|
||||
b.data = binary.AppendUvarint(b.data, id-b.desc.max)
|
||||
}
|
||||
// Extension validation
|
||||
if (len(ext) == 0) != !b.hasExt {
|
||||
if len(ext) == 0 {
|
||||
return errors.New("missing extension")
|
||||
}
|
||||
return errors.New("unexpected extension")
|
||||
}
|
||||
// Append the extension if it is not nil. The extension is prefixed with a
|
||||
// length indicator, and the block reader MUST understand this scheme and
|
||||
// decode the extension accordingly.
|
||||
if len(ext) > 0 {
|
||||
b.setBitmap(ext)
|
||||
enc := encodeIDs(ext)
|
||||
b.data = binary.AppendUvarint(b.data, uint64(len(enc)))
|
||||
b.data = append(b.data, enc...)
|
||||
}
|
||||
b.desc.entries++
|
||||
b.desc.max = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// scanSection traverses the specified section and terminates if fn returns true.
|
||||
func (b *blockWriter) scanSection(section int, fn func(uint64, int) bool) {
|
||||
func (b *blockWriter) scanSection(section int, fn func(uint64, int, []uint16) bool) error {
|
||||
var (
|
||||
value uint64
|
||||
start = int(b.restarts[section])
|
||||
|
|
@ -269,28 +337,47 @@ func (b *blockWriter) scanSection(section int, fn func(uint64, int) bool) {
|
|||
} else {
|
||||
value += x
|
||||
}
|
||||
if fn(value, pos) {
|
||||
return
|
||||
// Resolve the extension if exists
|
||||
var (
|
||||
err error
|
||||
ext []uint16
|
||||
extLen int
|
||||
)
|
||||
if b.hasExt {
|
||||
l, ln := binary.Uvarint(b.data[pos+n:])
|
||||
extLen = ln + int(l)
|
||||
ext, err = decodeIDs(b.data[pos+n+ln : pos+n+extLen])
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fn(value, pos, ext) {
|
||||
return nil
|
||||
}
|
||||
// Shift to next position
|
||||
pos += n
|
||||
pos += extLen
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sectionLast returns the last element in the specified section.
|
||||
func (b *blockWriter) sectionLast(section int) uint64 {
|
||||
func (b *blockWriter) sectionLast(section int) (uint64, error) {
|
||||
var n uint64
|
||||
b.scanSection(section, func(v uint64, _ int) bool {
|
||||
if err := b.scanSection(section, func(v uint64, _ int, _ []uint16) bool {
|
||||
n = v
|
||||
return false
|
||||
})
|
||||
return n
|
||||
}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// sectionSearch looks up the specified value in the given section,
|
||||
// the position and the preceding value will be returned if found.
|
||||
// It assumes that the preceding element exists in the section.
|
||||
func (b *blockWriter) sectionSearch(section int, n uint64) (found bool, prev uint64, pos int) {
|
||||
b.scanSection(section, func(v uint64, p int) bool {
|
||||
func (b *blockWriter) sectionSearch(section int, n uint64) (found bool, prev uint64, pos int, err error) {
|
||||
if err := b.scanSection(section, func(v uint64, p int, _ []uint16) bool {
|
||||
if n == v {
|
||||
pos = p
|
||||
found = true
|
||||
|
|
@ -298,8 +385,24 @@ func (b *blockWriter) sectionSearch(section int, n uint64) (found bool, prev uin
|
|||
}
|
||||
prev = v
|
||||
return false // continue iteration
|
||||
})
|
||||
return found, prev, pos
|
||||
}); err != nil {
|
||||
return false, 0, 0, err
|
||||
}
|
||||
return found, prev, pos, nil
|
||||
}
|
||||
|
||||
// rebuildBitmap scans the entire block and rebuilds the bitmap.
|
||||
func (b *blockWriter) rebuildBitmap() error {
|
||||
clear(b.desc.extBitmap)
|
||||
for i := 0; i < len(b.restarts); i++ {
|
||||
if err := b.scanSection(i, func(v uint64, p int, ext []uint16) bool {
|
||||
b.setBitmap(ext)
|
||||
return false // continue iteration
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pop removes the last element from the block. The assumption is held that block
|
||||
|
|
@ -315,6 +418,7 @@ func (b *blockWriter) pop(id uint64) error {
|
|||
if b.desc.entries == 1 {
|
||||
b.desc.max = 0
|
||||
b.desc.entries = 0
|
||||
clear(b.desc.extBitmap)
|
||||
b.restarts = nil
|
||||
b.data = b.data[:0]
|
||||
return nil
|
||||
|
|
@ -324,28 +428,36 @@ func (b *blockWriter) pop(id uint64) error {
|
|||
if b.desc.entries%indexBlockRestartLen == 1 {
|
||||
b.data = b.data[:b.restarts[len(b.restarts)-1]]
|
||||
b.restarts = b.restarts[:len(b.restarts)-1]
|
||||
b.desc.max = b.sectionLast(len(b.restarts) - 1)
|
||||
last, err := b.sectionLast(len(b.restarts) - 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.desc.max = last
|
||||
b.desc.entries -= 1
|
||||
return nil
|
||||
return b.rebuildBitmap()
|
||||
}
|
||||
// Look up the element preceding the one to be popped, in order to update
|
||||
// the maximum element in the block.
|
||||
found, prev, pos := b.sectionSearch(len(b.restarts)-1, id)
|
||||
found, prev, pos, err := b.sectionSearch(len(b.restarts)-1, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("pop element is not found, last: %d, this: %d", b.desc.max, id)
|
||||
}
|
||||
b.desc.max = prev
|
||||
b.data = b.data[:pos]
|
||||
b.desc.entries -= 1
|
||||
return nil
|
||||
return b.rebuildBitmap()
|
||||
}
|
||||
|
||||
func (b *blockWriter) empty() bool {
|
||||
return b.desc.empty()
|
||||
}
|
||||
|
||||
func (b *blockWriter) full() bool {
|
||||
return b.desc.full()
|
||||
func (b *blockWriter) estimateFull(ext []uint16) bool {
|
||||
size := 8 + 2*len(ext)
|
||||
return len(b.data)+size > indexBlockSizeCap
|
||||
}
|
||||
|
||||
// last returns the last element in the block. It should only be called when
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package pathdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/rand"
|
||||
"slices"
|
||||
|
|
@ -24,16 +25,36 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func randomExt(bitmapSize int, n int) []uint16 {
|
||||
if bitmapSize == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
limit = bitmapSize * 8
|
||||
extList []uint16
|
||||
)
|
||||
for i := 0; i < n; i++ {
|
||||
extList = append(extList, uint16(rand.Intn(limit+1)))
|
||||
}
|
||||
return extList
|
||||
}
|
||||
|
||||
func TestBlockReaderBasic(t *testing.T) {
|
||||
testBlockReaderBasic(t, 0)
|
||||
testBlockReaderBasic(t, 2)
|
||||
testBlockReaderBasic(t, 34)
|
||||
}
|
||||
|
||||
func testBlockReaderBasic(t *testing.T, bitmapSize int) {
|
||||
elements := []uint64{
|
||||
1, 5, 10, 11, 20,
|
||||
}
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0, bitmapSize), 0, bitmapSize != 0)
|
||||
for i := 0; i < len(elements); i++ {
|
||||
bw.append(elements[i])
|
||||
bw.append(elements[i], randomExt(bitmapSize, 5))
|
||||
}
|
||||
|
||||
br, err := newBlockReader(bw.finish())
|
||||
br, err := newBlockReader(bw.finish(), bitmapSize != 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct the block reader, %v", err)
|
||||
}
|
||||
|
|
@ -60,18 +81,24 @@ func TestBlockReaderBasic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBlockReaderLarge(t *testing.T) {
|
||||
testBlockReaderLarge(t, 0)
|
||||
testBlockReaderLarge(t, 2)
|
||||
testBlockReaderLarge(t, 34)
|
||||
}
|
||||
|
||||
func testBlockReaderLarge(t *testing.T, bitmapSize int) {
|
||||
var elements []uint64
|
||||
for i := 0; i < 1000; i++ {
|
||||
elements = append(elements, rand.Uint64())
|
||||
}
|
||||
slices.Sort(elements)
|
||||
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0, bitmapSize), 0, bitmapSize != 0)
|
||||
for i := 0; i < len(elements); i++ {
|
||||
bw.append(elements[i])
|
||||
bw.append(elements[i], randomExt(bitmapSize, 5))
|
||||
}
|
||||
|
||||
br, err := newBlockReader(bw.finish())
|
||||
br, err := newBlockReader(bw.finish(), bitmapSize != 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct the block reader, %v", err)
|
||||
}
|
||||
|
|
@ -95,26 +122,32 @@ func TestBlockReaderLarge(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBlockWriterBasic(t *testing.T) {
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||
testBlockWriteBasic(t, 0)
|
||||
testBlockWriteBasic(t, 2)
|
||||
testBlockWriteBasic(t, 34)
|
||||
}
|
||||
|
||||
func testBlockWriteBasic(t *testing.T, bitmapSize int) {
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0, bitmapSize), 0, bitmapSize != 0)
|
||||
if !bw.empty() {
|
||||
t.Fatal("expected empty block")
|
||||
}
|
||||
bw.append(2)
|
||||
if err := bw.append(1); err == nil {
|
||||
bw.append(2, randomExt(bitmapSize, 5))
|
||||
if err := bw.append(1, randomExt(bitmapSize, 5)); err == nil {
|
||||
t.Fatal("out-of-order insertion is not expected")
|
||||
}
|
||||
var maxElem uint64
|
||||
for i := 0; i < 10; i++ {
|
||||
bw.append(uint64(i + 3))
|
||||
bw.append(uint64(i+3), randomExt(bitmapSize, 5))
|
||||
maxElem = uint64(i + 3)
|
||||
}
|
||||
|
||||
bw, err := newBlockWriter(bw.finish(), newIndexBlockDesc(0), maxElem)
|
||||
bw, err := newBlockWriter(bw.finish(), newIndexBlockDesc(0, bitmapSize), maxElem, bitmapSize != 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct the block writer, %v", err)
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := bw.append(uint64(i + 100)); err != nil {
|
||||
if err := bw.append(uint64(i+100), randomExt(bitmapSize, 5)); err != nil {
|
||||
t.Fatalf("Failed to append value %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -122,58 +155,38 @@ func TestBlockWriterBasic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBlockWriterWithLimit(t *testing.T) {
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||
testBlockWriterWithLimit(t, 0)
|
||||
testBlockWriterWithLimit(t, 2)
|
||||
testBlockWriterWithLimit(t, 34)
|
||||
}
|
||||
|
||||
var maxElem uint64
|
||||
for i := 0; i < indexBlockRestartLen*2; i++ {
|
||||
bw.append(uint64(i + 1))
|
||||
maxElem = uint64(i + 1)
|
||||
}
|
||||
func testBlockWriterWithLimit(t *testing.T, bitmapSize int) {
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0, bitmapSize), 0, bitmapSize != 0)
|
||||
|
||||
suites := []struct {
|
||||
limit uint64
|
||||
expMax uint64
|
||||
}{
|
||||
// nothing to truncate
|
||||
{
|
||||
maxElem, maxElem,
|
||||
},
|
||||
// truncate the last element
|
||||
{
|
||||
maxElem - 1, maxElem - 1,
|
||||
},
|
||||
// truncation around the restart boundary
|
||||
{
|
||||
uint64(indexBlockRestartLen + 1),
|
||||
uint64(indexBlockRestartLen + 1),
|
||||
},
|
||||
// truncation around the restart boundary
|
||||
{
|
||||
uint64(indexBlockRestartLen),
|
||||
uint64(indexBlockRestartLen),
|
||||
},
|
||||
{
|
||||
uint64(1), uint64(1),
|
||||
},
|
||||
// truncate the entire block, it's in theory invalid
|
||||
{
|
||||
uint64(0), uint64(0),
|
||||
},
|
||||
var bitmaps [][]byte
|
||||
for i := 0; i < indexBlockRestartLen+2; i++ {
|
||||
bw.append(uint64(i+1), randomExt(bitmapSize, 5))
|
||||
bitmaps = append(bitmaps, bytes.Clone(bw.desc.extBitmap))
|
||||
}
|
||||
for i, suite := range suites {
|
||||
desc := *bw.desc
|
||||
block, err := newBlockWriter(bw.finish(), &desc, suite.limit)
|
||||
for i := 0; i < indexBlockRestartLen+2; i++ {
|
||||
limit := uint64(i + 1)
|
||||
|
||||
desc := bw.desc.copy()
|
||||
block, err := newBlockWriter(bytes.Clone(bw.finish()), desc, limit, bitmapSize != 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct the block writer, %v", err)
|
||||
}
|
||||
if block.desc.max != suite.expMax {
|
||||
t.Fatalf("Test %d, unexpected max value, got %d, want %d", i, block.desc.max, suite.expMax)
|
||||
if block.desc.max != limit {
|
||||
t.Fatalf("Test %d, unexpected max value, got %d, want %d", i, block.desc.max, limit)
|
||||
}
|
||||
if !bytes.Equal(desc.extBitmap, bitmaps[i]) {
|
||||
t.Fatalf("Test %d, unexpected bitmap, got: %v, want: %v", i, block.desc.extBitmap, bitmaps[i])
|
||||
}
|
||||
|
||||
// Re-fill the elements
|
||||
var maxElem uint64
|
||||
for elem := suite.limit + 1; elem < indexBlockRestartLen*4; elem++ {
|
||||
if err := block.append(elem); err != nil {
|
||||
for elem := limit + 1; elem < indexBlockRestartLen+4; elem++ {
|
||||
if err := block.append(elem, randomExt(bitmapSize, 5)); err != nil {
|
||||
t.Fatalf("Failed to append value %d: %v", elem, err)
|
||||
}
|
||||
maxElem = elem
|
||||
|
|
@ -185,9 +198,15 @@ func TestBlockWriterWithLimit(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBlockWriterDelete(t *testing.T) {
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||
testBlockWriterDelete(t, 0)
|
||||
testBlockWriterDelete(t, 2)
|
||||
testBlockWriterDelete(t, 34)
|
||||
}
|
||||
|
||||
func testBlockWriterDelete(t *testing.T, bitmapSize int) {
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0, bitmapSize), 0, bitmapSize != 0)
|
||||
for i := 0; i < 10; i++ {
|
||||
bw.append(uint64(i + 1))
|
||||
bw.append(uint64(i+1), randomExt(bitmapSize, 5))
|
||||
}
|
||||
// Pop unknown id, the request should be rejected
|
||||
if err := bw.pop(100); err == nil {
|
||||
|
|
@ -209,12 +228,18 @@ func TestBlockWriterDelete(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBlcokWriterDeleteWithData(t *testing.T) {
|
||||
testBlcokWriterDeleteWithData(t, 0)
|
||||
testBlcokWriterDeleteWithData(t, 2)
|
||||
testBlcokWriterDeleteWithData(t, 34)
|
||||
}
|
||||
|
||||
func testBlcokWriterDeleteWithData(t *testing.T, bitmapSize int) {
|
||||
elements := []uint64{
|
||||
1, 5, 10, 11, 20,
|
||||
}
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0, bitmapSize), 0, bitmapSize != 0)
|
||||
for i := 0; i < len(elements); i++ {
|
||||
bw.append(elements[i])
|
||||
bw.append(elements[i], randomExt(bitmapSize, 5))
|
||||
}
|
||||
|
||||
// Re-construct the block writer with data
|
||||
|
|
@ -223,7 +248,10 @@ func TestBlcokWriterDeleteWithData(t *testing.T) {
|
|||
max: 20,
|
||||
entries: 5,
|
||||
}
|
||||
bw, err := newBlockWriter(bw.finish(), desc, elements[len(elements)-1])
|
||||
if bitmapSize > 0 {
|
||||
desc.extBitmap = make([]byte, bitmapSize)
|
||||
}
|
||||
bw, err := newBlockWriter(bw.finish(), desc, elements[len(elements)-1], bitmapSize != 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct block writer %v", err)
|
||||
}
|
||||
|
|
@ -234,7 +262,7 @@ func TestBlcokWriterDeleteWithData(t *testing.T) {
|
|||
newTail := elements[i-1]
|
||||
|
||||
// Ensure the element can still be queried with no issue
|
||||
br, err := newBlockReader(bw.finish())
|
||||
br, err := newBlockReader(bw.finish(), bitmapSize != 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct the block reader, %v", err)
|
||||
}
|
||||
|
|
@ -266,29 +294,60 @@ func TestBlcokWriterDeleteWithData(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCorruptedIndexBlock(t *testing.T) {
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0, 0), 0, false)
|
||||
|
||||
var maxElem uint64
|
||||
for i := 0; i < 10; i++ {
|
||||
bw.append(uint64(i + 1))
|
||||
bw.append(uint64(i+1), nil)
|
||||
maxElem = uint64(i + 1)
|
||||
}
|
||||
buf := bw.finish()
|
||||
|
||||
// Mutate the buffer manually
|
||||
buf[len(buf)-1]++
|
||||
_, err := newBlockWriter(buf, newIndexBlockDesc(0), maxElem)
|
||||
_, err := newBlockWriter(buf, newIndexBlockDesc(0, 0), maxElem, false)
|
||||
if err == nil {
|
||||
t.Fatal("Corrupted index block data is not detected")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParseIndexBlock benchmarks the performance of parseIndexBlock.
|
||||
//
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/ethereum/go-ethereum/triedb/pathdb
|
||||
// cpu: Apple M1 Pro
|
||||
// BenchmarkParseIndexBlock
|
||||
// BenchmarkParseIndexBlock-8 35829495 34.16 ns/op
|
||||
func BenchmarkParseIndexBlock(b *testing.B) {
|
||||
// Generate a realistic index block blob
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0, 0), 0, false)
|
||||
for i := 0; i < 4096; i++ {
|
||||
bw.append(uint64(i * 2))
|
||||
bw.append(uint64(i*2), nil)
|
||||
}
|
||||
blob := bw.finish()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, err := parseIndexBlock(blob)
|
||||
if err != nil {
|
||||
b.Fatalf("parseIndexBlock failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/ethereum/go-ethereum/triedb/pathdb
|
||||
// cpu: Apple M1 Pro
|
||||
// BenchmarkParseIndexBlockWithExt
|
||||
// BenchmarkParseIndexBlockWithExt-8 35773242 33.72 ns/op
|
||||
func BenchmarkParseIndexBlockWithExt(b *testing.B) {
|
||||
// Generate a realistic index block blob
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0, 34), 0, true)
|
||||
for i := 0; i < 4096; i++ {
|
||||
id, ext := uint64(i*2), randomExt(34, 3)
|
||||
bw.append(id, ext)
|
||||
}
|
||||
blob := bw.finish()
|
||||
|
||||
|
|
@ -302,21 +361,58 @@ func BenchmarkParseIndexBlock(b *testing.B) {
|
|||
}
|
||||
|
||||
// BenchmarkBlockWriterAppend benchmarks the performance of indexblock.writer
|
||||
//
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/ethereum/go-ethereum/triedb/pathdb
|
||||
// cpu: Apple M1 Pro
|
||||
// BenchmarkBlockWriterAppend
|
||||
// BenchmarkBlockWriterAppend-8 293611083 4.113 ns/op 3 B/op 0 allocs/op
|
||||
func BenchmarkBlockWriterAppend(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
var blockID uint32
|
||||
desc := newIndexBlockDesc(blockID)
|
||||
writer, _ := newBlockWriter(nil, desc, 0)
|
||||
desc := newIndexBlockDesc(blockID, 0)
|
||||
writer, _ := newBlockWriter(nil, desc, 0, false)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if writer.full() {
|
||||
if writer.estimateFull(nil) {
|
||||
blockID += 1
|
||||
desc = newIndexBlockDesc(blockID)
|
||||
writer, _ = newBlockWriter(nil, desc, 0)
|
||||
desc = newIndexBlockDesc(blockID, 0)
|
||||
writer, _ = newBlockWriter(nil, desc, 0, false)
|
||||
}
|
||||
if err := writer.append(writer.desc.max + 1); err != nil {
|
||||
if err := writer.append(writer.desc.max+1, nil); err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/ethereum/go-ethereum/triedb/pathdb
|
||||
// cpu: Apple M1 Pro
|
||||
// BenchmarkBlockWriterAppendWithExt
|
||||
// BenchmarkBlockWriterAppendWithExt-8 11123844 103.6 ns/op 42 B/op 2 allocs/op
|
||||
func BenchmarkBlockWriterAppendWithExt(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
var (
|
||||
bitmapSize = 34
|
||||
blockID uint32
|
||||
)
|
||||
desc := newIndexBlockDesc(blockID, bitmapSize)
|
||||
writer, _ := newBlockWriter(nil, desc, 0, true)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
ext := randomExt(bitmapSize, 3)
|
||||
if writer.estimateFull(ext) {
|
||||
blockID += 1
|
||||
desc = newIndexBlockDesc(blockID, bitmapSize)
|
||||
writer, _ = newBlockWriter(nil, desc, 0, true)
|
||||
}
|
||||
if err := writer.append(writer.desc.max+1, ext); err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,31 +40,135 @@ type HistoryIndexIterator interface {
|
|||
Error() error
|
||||
}
|
||||
|
||||
// extFilter provides utilities for filtering index entries based on their
|
||||
// extension field.
|
||||
//
|
||||
// It supports two primary operations:
|
||||
//
|
||||
// - determine whether a given target node ID or any of its descendants
|
||||
// appears explicitly in the extension list.
|
||||
//
|
||||
// - determine whether a given target node ID or any of its descendants
|
||||
// is marked in the extension bitmap.
|
||||
//
|
||||
// Together, these checks allow callers to efficiently filter out the irrelevant
|
||||
// index entries during the lookup.
|
||||
type extFilter uint16
|
||||
|
||||
// exists takes the entire extension field in the index block and determines
|
||||
// whether the target ID or its descendants appears. Note, any of descendant
|
||||
// can implicitly mean the presence of ancestor.
|
||||
func (f extFilter) exists(ext []byte) (bool, error) {
|
||||
fn := uint16(f)
|
||||
list, err := decodeIDs(ext)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, elem := range list {
|
||||
if elem == fn {
|
||||
return true, nil
|
||||
}
|
||||
if isAncestor(fn, elem) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
const (
|
||||
// bitmapBytesTwoLevels is the size of the bitmap for two levels of the
|
||||
// 16-ary tree (16 nodes total, excluding the root).
|
||||
bitmapBytesTwoLevels = 2
|
||||
|
||||
// bitmapBytesThreeLevels is the size of the bitmap for three levels of
|
||||
// the 16-ary tree (272 nodes total, excluding the root).
|
||||
bitmapBytesThreeLevels = 34
|
||||
|
||||
// bitmapElementThresholdTwoLevels is the total number of elements in the
|
||||
// two levels of a 16-ary tree (16 nodes total, excluding the root).
|
||||
bitmapElementThresholdTwoLevels = 16
|
||||
|
||||
// bitmapElementThresholdThreeLevels is the total number of elements in the
|
||||
// two levels of a 16-ary tree (16 nodes total, excluding the root).
|
||||
bitmapElementThresholdThreeLevels = bitmapElementThresholdTwoLevels + 16*16
|
||||
)
|
||||
|
||||
// contains takes the bitmap from the block metadata and determines whether the
|
||||
// target ID or its descendants is marked in the bitmap. Note, any of descendant
|
||||
// can implicitly mean the presence of ancestor.
|
||||
func (f extFilter) contains(bitmap []byte) (bool, error) {
|
||||
id := int(f)
|
||||
if id == 0 {
|
||||
return true, nil
|
||||
}
|
||||
n := id - 1 // apply the position shift for excluding root node
|
||||
|
||||
switch len(bitmap) {
|
||||
case 0:
|
||||
return true, nil
|
||||
case bitmapBytesTwoLevels:
|
||||
if n >= bitmapElementThresholdTwoLevels {
|
||||
return false, fmt.Errorf("invalid extension filter %d for 2 bytes bitmap", id)
|
||||
}
|
||||
return isBitSet(bitmap, n), nil
|
||||
case bitmapBytesThreeLevels:
|
||||
if n >= bitmapElementThresholdThreeLevels {
|
||||
return false, fmt.Errorf("invalid extension filter %d for 34 bytes bitmap", id)
|
||||
} else if n >= bitmapElementThresholdTwoLevels {
|
||||
return isBitSet(bitmap, n), nil
|
||||
} else {
|
||||
// Check the element itself first
|
||||
if isBitSet(bitmap, n) {
|
||||
return true, nil
|
||||
}
|
||||
// Check descendants: the presence of any descendant implicitly
|
||||
// represents a mutation of its ancestor.
|
||||
for m := 16 + n*16; m < 32+n*16; m++ {
|
||||
if isBitSet(bitmap, m) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported bitmap size %d", len(bitmap))
|
||||
}
|
||||
}
|
||||
|
||||
// blockIterator is the iterator to traverse the indices within a single block.
|
||||
type blockIterator struct {
|
||||
// immutable fields
|
||||
data []byte // Reference to the data segment within the block reader
|
||||
restarts []uint16 // Offsets pointing to the restart sections within the data
|
||||
hasExt bool // Flag whether the extension is included in the data
|
||||
|
||||
// Optional extension filter
|
||||
filter *extFilter // Filters index entries based on the extension field.
|
||||
|
||||
// mutable fields
|
||||
id uint64 // ID of the element at the iterators current position
|
||||
ext []byte // Extension field of the element at the iterators current position
|
||||
dataPtr int // Current read position within the data slice
|
||||
restartPtr int // Index of the restart section where the iterator is currently positioned
|
||||
exhausted bool // Flag whether the iterator has been exhausted
|
||||
err error // Accumulated error during the traversal
|
||||
}
|
||||
|
||||
func newBlockIterator(data []byte, restarts []uint16) *blockIterator {
|
||||
func (br *blockReader) newIterator(filter *extFilter) *blockIterator {
|
||||
it := &blockIterator{
|
||||
data: data, // hold the slice directly with no deep copy
|
||||
restarts: restarts, // hold the slice directly with no deep copy
|
||||
data: br.data, // hold the slice directly with no deep copy
|
||||
restarts: br.restarts, // hold the slice directly with no deep copy
|
||||
hasExt: br.hasExt, // flag whether the extension should be resolved
|
||||
filter: filter, // optional extension filter
|
||||
}
|
||||
it.reset()
|
||||
return it
|
||||
}
|
||||
|
||||
func (it *blockIterator) set(dataPtr int, restartPtr int, id uint64) {
|
||||
func (it *blockIterator) set(dataPtr int, restartPtr int, id uint64, ext []byte) {
|
||||
it.id = id
|
||||
it.ext = ext
|
||||
|
||||
it.dataPtr = dataPtr
|
||||
it.restartPtr = restartPtr
|
||||
it.exhausted = dataPtr == len(it.data)
|
||||
|
|
@ -79,6 +183,8 @@ func (it *blockIterator) setErr(err error) {
|
|||
|
||||
func (it *blockIterator) reset() {
|
||||
it.id = 0
|
||||
it.ext = nil
|
||||
|
||||
it.dataPtr = -1
|
||||
it.restartPtr = -1
|
||||
it.exhausted = false
|
||||
|
|
@ -90,12 +196,26 @@ func (it *blockIterator) reset() {
|
|||
}
|
||||
}
|
||||
|
||||
// SeekGT moves the iterator to the first element whose id is greater than the
|
||||
func (it *blockIterator) resolveExt(pos int) ([]byte, int, error) {
|
||||
if !it.hasExt {
|
||||
return nil, 0, nil
|
||||
}
|
||||
length, n := binary.Uvarint(it.data[pos:])
|
||||
if n <= 0 {
|
||||
return nil, 0, fmt.Errorf("too short for extension, pos: %d, datalen: %d", pos, len(it.data))
|
||||
}
|
||||
if len(it.data[pos+n:]) < int(length) {
|
||||
return nil, 0, fmt.Errorf("too short for extension, pos: %d, length: %d, datalen: %d", pos, length, len(it.data))
|
||||
}
|
||||
return it.data[pos+n : pos+n+int(length)], n + int(length), nil
|
||||
}
|
||||
|
||||
// seekGT moves the iterator to the first element whose id is greater than the
|
||||
// given number. It returns whether such element exists.
|
||||
//
|
||||
// Note, this operation will unset the exhausted status and subsequent traversal
|
||||
// is allowed.
|
||||
func (it *blockIterator) SeekGT(id uint64) bool {
|
||||
func (it *blockIterator) seekGT(id uint64) bool {
|
||||
if it.err != nil {
|
||||
return false
|
||||
}
|
||||
|
|
@ -113,10 +233,16 @@ func (it *blockIterator) SeekGT(id uint64) bool {
|
|||
}
|
||||
if index == 0 {
|
||||
item, n := binary.Uvarint(it.data[it.restarts[0]:])
|
||||
|
||||
// If the restart size is 1, then the restart pointer shouldn't be 0.
|
||||
// It's not practical and should be denied in the first place.
|
||||
it.set(int(it.restarts[0])+n, 0, item)
|
||||
if n <= 0 {
|
||||
it.setErr(fmt.Errorf("failed to decode item at pos %d", it.restarts[0]))
|
||||
return false
|
||||
}
|
||||
ext, shift, err := it.resolveExt(int(it.restarts[0]) + n)
|
||||
if err != nil {
|
||||
it.setErr(err)
|
||||
return false
|
||||
}
|
||||
it.set(int(it.restarts[0])+n+shift, 0, item, ext)
|
||||
return true
|
||||
}
|
||||
var (
|
||||
|
|
@ -154,11 +280,18 @@ func (it *blockIterator) SeekGT(id uint64) bool {
|
|||
}
|
||||
pos += n
|
||||
|
||||
ext, shift, err := it.resolveExt(pos)
|
||||
if err != nil {
|
||||
it.setErr(err)
|
||||
return false
|
||||
}
|
||||
pos += shift
|
||||
|
||||
if result > id {
|
||||
if pos == limit {
|
||||
it.set(pos, restartIndex+1, result)
|
||||
it.set(pos, restartIndex+1, result, ext)
|
||||
} else {
|
||||
it.set(pos, restartIndex, result)
|
||||
it.set(pos, restartIndex, result, ext)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -171,7 +304,41 @@ func (it *blockIterator) SeekGT(id uint64) bool {
|
|||
// The element which is the first one greater than the specified id
|
||||
// is exactly the one located at the restart point.
|
||||
item, n := binary.Uvarint(it.data[it.restarts[index]:])
|
||||
it.set(int(it.restarts[index])+n, index, item)
|
||||
if n <= 0 {
|
||||
it.setErr(fmt.Errorf("failed to decode item at pos %d", it.restarts[index]))
|
||||
return false
|
||||
}
|
||||
ext, shift, err := it.resolveExt(int(it.restarts[index]) + n)
|
||||
if err != nil {
|
||||
it.setErr(err)
|
||||
return false
|
||||
}
|
||||
it.set(int(it.restarts[index])+n+shift, index, item, ext)
|
||||
return true
|
||||
}
|
||||
|
||||
// SeekGT implements HistoryIndexIterator, is the wrapper of the seekGT with
|
||||
// optional extension filter logic applied.
|
||||
func (it *blockIterator) SeekGT(id uint64) bool {
|
||||
if !it.seekGT(id) {
|
||||
return false
|
||||
}
|
||||
if it.filter == nil {
|
||||
return true
|
||||
}
|
||||
for {
|
||||
found, err := it.filter.exists(it.ext)
|
||||
if err != nil {
|
||||
it.setErr(err)
|
||||
return false
|
||||
}
|
||||
if found {
|
||||
break
|
||||
}
|
||||
if !it.next() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -183,10 +350,9 @@ func (it *blockIterator) init() {
|
|||
it.restartPtr = 0
|
||||
}
|
||||
|
||||
// Next implements the HistoryIndexIterator, moving the iterator to the next
|
||||
// element. If the iterator has been exhausted, and boolean with false should
|
||||
// be returned.
|
||||
func (it *blockIterator) Next() bool {
|
||||
// next moves the iterator to the next element. If the iterator has been exhausted,
|
||||
// and boolean with false should be returned.
|
||||
func (it *blockIterator) next() bool {
|
||||
if it.exhausted || it.err != nil {
|
||||
return false
|
||||
}
|
||||
|
|
@ -198,7 +364,6 @@ func (it *blockIterator) Next() bool {
|
|||
it.setErr(fmt.Errorf("failed to decode item at pos %d", it.dataPtr))
|
||||
return false
|
||||
}
|
||||
|
||||
var val uint64
|
||||
if it.dataPtr == int(it.restarts[it.restartPtr]) {
|
||||
val = v
|
||||
|
|
@ -206,16 +371,48 @@ func (it *blockIterator) Next() bool {
|
|||
val = it.id + v
|
||||
}
|
||||
|
||||
// Decode the extension field
|
||||
ext, shift, err := it.resolveExt(it.dataPtr + n)
|
||||
if err != nil {
|
||||
it.setErr(err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Move to the next restart section if the data pointer crosses the boundary
|
||||
nextRestartPtr := it.restartPtr
|
||||
if it.restartPtr < len(it.restarts)-1 && it.dataPtr+n == int(it.restarts[it.restartPtr+1]) {
|
||||
if it.restartPtr < len(it.restarts)-1 && it.dataPtr+n+shift == int(it.restarts[it.restartPtr+1]) {
|
||||
nextRestartPtr = it.restartPtr + 1
|
||||
}
|
||||
it.set(it.dataPtr+n, nextRestartPtr, val)
|
||||
it.set(it.dataPtr+n+shift, nextRestartPtr, val, ext)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Next implements the HistoryIndexIterator, moving the iterator to the next
|
||||
// element. It's a wrapper of next with optional extension filter logic applied.
|
||||
func (it *blockIterator) Next() bool {
|
||||
if !it.next() {
|
||||
return false
|
||||
}
|
||||
if it.filter == nil {
|
||||
return true
|
||||
}
|
||||
for {
|
||||
found, err := it.filter.exists(it.ext)
|
||||
if err != nil {
|
||||
it.setErr(err)
|
||||
return false
|
||||
}
|
||||
if found {
|
||||
break
|
||||
}
|
||||
if !it.next() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ID implements HistoryIndexIterator, returning the id of the element where the
|
||||
// iterator is positioned at.
|
||||
func (it *blockIterator) ID() uint64 {
|
||||
|
|
@ -226,15 +423,15 @@ func (it *blockIterator) ID() uint64 {
|
|||
// Exhausting all the elements is not considered to be an error.
|
||||
func (it *blockIterator) Error() error { return it.err }
|
||||
|
||||
// blockLoader defines the method to retrieve the specific block for reading.
|
||||
type blockLoader func(id uint32) (*blockReader, error)
|
||||
|
||||
// indexIterator is an iterator to traverse the history indices belonging to the
|
||||
// specific state entry.
|
||||
type indexIterator struct {
|
||||
// immutable fields
|
||||
descList []*indexBlockDesc
|
||||
loader blockLoader
|
||||
reader *indexReader
|
||||
|
||||
// Optional extension filter
|
||||
filter *extFilter
|
||||
|
||||
// mutable fields
|
||||
blockIt *blockIterator
|
||||
|
|
@ -243,10 +440,26 @@ type indexIterator struct {
|
|||
err error
|
||||
}
|
||||
|
||||
func newIndexIterator(descList []*indexBlockDesc, loader blockLoader) *indexIterator {
|
||||
// newBlockIter initializes the block iterator with the specified block ID.
|
||||
func (r *indexReader) newBlockIter(id uint32, filter *extFilter) (*blockIterator, error) {
|
||||
br, ok := r.readers[id]
|
||||
if !ok {
|
||||
var err error
|
||||
br, err = newBlockReader(readStateIndexBlock(r.state, r.db, id), r.bitmapSize != 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.readers[id] = br
|
||||
}
|
||||
return br.newIterator(filter), nil
|
||||
}
|
||||
|
||||
// newIterator initializes the index iterator with the specified extension filter.
|
||||
func (r *indexReader) newIterator(filter *extFilter) *indexIterator {
|
||||
it := &indexIterator{
|
||||
descList: descList,
|
||||
loader: loader,
|
||||
descList: r.descList,
|
||||
reader: r,
|
||||
filter: filter,
|
||||
}
|
||||
it.reset()
|
||||
return it
|
||||
|
|
@ -271,16 +484,33 @@ func (it *indexIterator) reset() {
|
|||
}
|
||||
|
||||
func (it *indexIterator) open(blockPtr int) error {
|
||||
id := it.descList[blockPtr].id
|
||||
br, err := it.loader(id)
|
||||
blockIt, err := it.reader.newBlockIter(it.descList[blockPtr].id, it.filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
it.blockIt = newBlockIterator(br.data, br.restarts)
|
||||
it.blockIt = blockIt
|
||||
it.blockPtr = blockPtr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it *indexIterator) applyFilter(index int) (int, error) {
|
||||
if it.filter == nil {
|
||||
return index, nil
|
||||
}
|
||||
for index < len(it.descList) {
|
||||
contain, err := it.filter.contains(it.descList[index].extBitmap)
|
||||
if err != nil {
|
||||
it.setErr(err)
|
||||
break
|
||||
}
|
||||
if contain {
|
||||
break
|
||||
}
|
||||
index++
|
||||
}
|
||||
return index, nil
|
||||
}
|
||||
|
||||
// SeekGT moves the iterator to the first element whose id is greater than the
|
||||
// given number. It returns whether such element exists.
|
||||
//
|
||||
|
|
@ -293,6 +523,11 @@ func (it *indexIterator) SeekGT(id uint64) bool {
|
|||
index := sort.Search(len(it.descList), func(i int) bool {
|
||||
return id < it.descList[i].max
|
||||
})
|
||||
index, err := it.applyFilter(index)
|
||||
if err != nil {
|
||||
it.setErr(err)
|
||||
return false
|
||||
}
|
||||
if index == len(it.descList) {
|
||||
return false
|
||||
}
|
||||
|
|
@ -304,7 +539,13 @@ func (it *indexIterator) SeekGT(id uint64) bool {
|
|||
return false
|
||||
}
|
||||
}
|
||||
return it.blockIt.SeekGT(id)
|
||||
// Terminate if the element which is greater than the id can be found in the
|
||||
// last block; otherwise move to the next block. It may happen that all the
|
||||
// target elements in this block are all less than id.
|
||||
if it.blockIt.SeekGT(id) {
|
||||
return true
|
||||
}
|
||||
return it.Next()
|
||||
}
|
||||
|
||||
func (it *indexIterator) init() error {
|
||||
|
|
@ -325,15 +566,23 @@ func (it *indexIterator) Next() bool {
|
|||
it.setErr(err)
|
||||
return false
|
||||
}
|
||||
|
||||
if it.blockIt.Next() {
|
||||
return true
|
||||
}
|
||||
if it.blockPtr == len(it.descList)-1 {
|
||||
it.blockPtr++
|
||||
|
||||
index, err := it.applyFilter(it.blockPtr)
|
||||
if err != nil {
|
||||
it.setErr(err)
|
||||
return false
|
||||
}
|
||||
it.blockPtr = index
|
||||
|
||||
if it.blockPtr == len(it.descList) {
|
||||
it.exhausted = true
|
||||
return false
|
||||
}
|
||||
if err := it.open(it.blockPtr + 1); err != nil {
|
||||
if err := it.open(it.blockPtr); err != nil {
|
||||
it.setErr(err)
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ package pathdb
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"math/rand"
|
||||
"slices"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
|
|
@ -28,12 +30,30 @@ import (
|
|||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
func makeTestIndexBlock(count int) ([]byte, []uint64) {
|
||||
func checkExt(f *extFilter, ext []uint16) bool {
|
||||
if f == nil {
|
||||
return true
|
||||
}
|
||||
fn := uint16(*f)
|
||||
|
||||
for _, n := range ext {
|
||||
if n == fn {
|
||||
return true
|
||||
}
|
||||
if isAncestor(fn, n) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func makeTestIndexBlock(count int, bitmapSize int) ([]byte, []uint64, [][]uint16) {
|
||||
var (
|
||||
marks = make(map[uint64]bool)
|
||||
elements []uint64
|
||||
elements = make([]uint64, 0, count)
|
||||
extList = make([][]uint16, 0, count)
|
||||
)
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0, bitmapSize), 0, bitmapSize != 0)
|
||||
for i := 0; i < count; i++ {
|
||||
n := uint64(rand.Uint32())
|
||||
if marks[n] {
|
||||
|
|
@ -45,17 +65,20 @@ func makeTestIndexBlock(count int) ([]byte, []uint64) {
|
|||
sort.Slice(elements, func(i, j int) bool { return elements[i] < elements[j] })
|
||||
|
||||
for i := 0; i < len(elements); i++ {
|
||||
bw.append(elements[i])
|
||||
ext := randomExt(bitmapSize, 5)
|
||||
extList = append(extList, ext)
|
||||
bw.append(elements[i], ext)
|
||||
}
|
||||
data := bw.finish()
|
||||
|
||||
return data, elements
|
||||
return data, elements, extList
|
||||
}
|
||||
|
||||
func makeTestIndexBlocks(db ethdb.KeyValueStore, stateIdent stateIdent, count int) []uint64 {
|
||||
func makeTestIndexBlocks(db ethdb.KeyValueStore, stateIdent stateIdent, count int, bitmapSize int) ([]uint64, [][]uint16) {
|
||||
var (
|
||||
marks = make(map[uint64]bool)
|
||||
elements []uint64
|
||||
extList [][]uint16
|
||||
)
|
||||
for i := 0; i < count; i++ {
|
||||
n := uint64(rand.Uint32())
|
||||
|
|
@ -67,15 +90,17 @@ func makeTestIndexBlocks(db ethdb.KeyValueStore, stateIdent stateIdent, count in
|
|||
}
|
||||
sort.Slice(elements, func(i, j int) bool { return elements[i] < elements[j] })
|
||||
|
||||
iw, _ := newIndexWriter(db, stateIdent, 0)
|
||||
iw, _ := newIndexWriter(db, stateIdent, 0, bitmapSize)
|
||||
for i := 0; i < len(elements); i++ {
|
||||
iw.append(elements[i])
|
||||
ext := randomExt(bitmapSize, 5)
|
||||
extList = append(extList, ext)
|
||||
iw.append(elements[i], ext)
|
||||
}
|
||||
batch := db.NewBatch()
|
||||
iw.finish(batch)
|
||||
batch.Write()
|
||||
|
||||
return elements
|
||||
return elements, extList
|
||||
}
|
||||
|
||||
func checkSeekGT(it HistoryIndexIterator, input uint64, exp bool, expVal uint64) error {
|
||||
|
|
@ -113,43 +138,40 @@ func checkNext(it HistoryIndexIterator, values []uint64) error {
|
|||
return it.Error()
|
||||
}
|
||||
|
||||
func TestBlockIteratorSeekGT(t *testing.T) {
|
||||
/* 0-size index block is not allowed
|
||||
|
||||
data, elements := makeTestIndexBlock(0)
|
||||
testBlockIterator(t, data, elements)
|
||||
*/
|
||||
|
||||
data, elements := makeTestIndexBlock(1)
|
||||
testBlockIterator(t, data, elements)
|
||||
|
||||
data, elements = makeTestIndexBlock(indexBlockRestartLen)
|
||||
testBlockIterator(t, data, elements)
|
||||
|
||||
data, elements = makeTestIndexBlock(3 * indexBlockRestartLen)
|
||||
testBlockIterator(t, data, elements)
|
||||
|
||||
data, elements = makeTestIndexBlock(indexBlockEntriesCap)
|
||||
testBlockIterator(t, data, elements)
|
||||
}
|
||||
|
||||
func testBlockIterator(t *testing.T, data []byte, elements []uint64) {
|
||||
br, err := newBlockReader(data)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open the block for reading, %v", err)
|
||||
func verifySeekGT(t *testing.T, elements []uint64, ext [][]uint16, newIter func(filter *extFilter) HistoryIndexIterator) {
|
||||
set := make(map[extFilter]bool)
|
||||
for _, extList := range ext {
|
||||
for _, f := range extList {
|
||||
set[extFilter(f)] = true
|
||||
}
|
||||
it := newBlockIterator(br.data, br.restarts)
|
||||
}
|
||||
filters := slices.Collect(maps.Keys(set))
|
||||
|
||||
for i := 0; i < 128; i++ {
|
||||
var filter *extFilter
|
||||
if rand.Intn(2) == 0 && len(filters) > 0 {
|
||||
filter = &filters[rand.Intn(len(filters))]
|
||||
} else {
|
||||
filter = nil
|
||||
}
|
||||
|
||||
var input uint64
|
||||
if rand.Intn(2) == 0 {
|
||||
input = elements[rand.Intn(len(elements))]
|
||||
} else {
|
||||
input = uint64(rand.Uint32())
|
||||
}
|
||||
|
||||
index := sort.Search(len(elements), func(i int) bool {
|
||||
return elements[i] > input
|
||||
})
|
||||
for index < len(elements) {
|
||||
if checkExt(filter, ext[index]) {
|
||||
break
|
||||
}
|
||||
index++
|
||||
}
|
||||
|
||||
var (
|
||||
exp bool
|
||||
expVal uint64
|
||||
|
|
@ -160,10 +182,17 @@ func testBlockIterator(t *testing.T, data []byte, elements []uint64) {
|
|||
} else {
|
||||
exp = true
|
||||
expVal = elements[index]
|
||||
if index < len(elements) {
|
||||
remains = elements[index+1:]
|
||||
|
||||
index++
|
||||
for index < len(elements) {
|
||||
if checkExt(filter, ext[index]) {
|
||||
remains = append(remains, elements[index])
|
||||
}
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
it := newIter(filter)
|
||||
if err := checkSeekGT(it, input, exp, expVal); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -175,62 +204,71 @@ func testBlockIterator(t *testing.T, data []byte, elements []uint64) {
|
|||
}
|
||||
}
|
||||
|
||||
func verifyTraversal(t *testing.T, elements []uint64, ext [][]uint16, newIter func(filter *extFilter) HistoryIndexIterator) {
|
||||
set := make(map[extFilter]bool)
|
||||
for _, extList := range ext {
|
||||
for _, f := range extList {
|
||||
set[extFilter(f)] = true
|
||||
}
|
||||
}
|
||||
filters := slices.Collect(maps.Keys(set))
|
||||
|
||||
for i := 0; i < 16; i++ {
|
||||
var filter *extFilter
|
||||
if len(filters) > 0 {
|
||||
filter = &filters[rand.Intn(len(filters))]
|
||||
} else {
|
||||
filter = nil
|
||||
}
|
||||
it := newIter(filter)
|
||||
|
||||
var (
|
||||
pos int
|
||||
exp []uint64
|
||||
)
|
||||
for pos < len(elements) {
|
||||
if checkExt(filter, ext[pos]) {
|
||||
exp = append(exp, elements[pos])
|
||||
}
|
||||
pos++
|
||||
}
|
||||
if err := checkNext(it, exp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockIteratorSeekGT(t *testing.T) {
|
||||
for _, size := range []int{0, 2, 34} {
|
||||
for _, n := range []int{1, indexBlockRestartLen, 3 * indexBlockRestartLen} {
|
||||
data, elements, ext := makeTestIndexBlock(n, size)
|
||||
|
||||
verifySeekGT(t, elements, ext, func(filter *extFilter) HistoryIndexIterator {
|
||||
br, err := newBlockReader(data, size != 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open the block for reading, %v", err)
|
||||
}
|
||||
return br.newIterator(filter)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexIteratorSeekGT(t *testing.T) {
|
||||
ident := newAccountIdent(common.Hash{0x1})
|
||||
|
||||
dbA := rawdb.NewMemoryDatabase()
|
||||
testIndexIterator(t, ident, dbA, makeTestIndexBlocks(dbA, ident, 1))
|
||||
for _, size := range []int{0, 2, 34} {
|
||||
for _, n := range []int{1, 4096, 3 * 4096} {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
elements, ext := makeTestIndexBlocks(db, ident, n, size)
|
||||
|
||||
dbB := rawdb.NewMemoryDatabase()
|
||||
testIndexIterator(t, ident, dbB, makeTestIndexBlocks(dbB, ident, 3*indexBlockEntriesCap))
|
||||
|
||||
dbC := rawdb.NewMemoryDatabase()
|
||||
testIndexIterator(t, ident, dbC, makeTestIndexBlocks(dbC, ident, indexBlockEntriesCap-1))
|
||||
|
||||
dbD := rawdb.NewMemoryDatabase()
|
||||
testIndexIterator(t, ident, dbD, makeTestIndexBlocks(dbD, ident, indexBlockEntriesCap+1))
|
||||
}
|
||||
|
||||
func testIndexIterator(t *testing.T, stateIdent stateIdent, db ethdb.Database, elements []uint64) {
|
||||
ir, err := newIndexReader(db, stateIdent)
|
||||
verifySeekGT(t, elements, ext, func(filter *extFilter) HistoryIndexIterator {
|
||||
ir, err := newIndexReader(db, ident, size)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open the index reader, %v", err)
|
||||
}
|
||||
it := newIndexIterator(ir.descList, func(id uint32) (*blockReader, error) {
|
||||
return newBlockReader(readStateIndexBlock(stateIdent, db, id))
|
||||
return ir.newIterator(filter)
|
||||
})
|
||||
|
||||
for i := 0; i < 128; i++ {
|
||||
var input uint64
|
||||
if rand.Intn(2) == 0 {
|
||||
input = elements[rand.Intn(len(elements))]
|
||||
} else {
|
||||
input = uint64(rand.Uint32())
|
||||
}
|
||||
index := sort.Search(len(elements), func(i int) bool {
|
||||
return elements[i] > input
|
||||
})
|
||||
var (
|
||||
exp bool
|
||||
expVal uint64
|
||||
remains []uint64
|
||||
)
|
||||
if index == len(elements) {
|
||||
exp = false
|
||||
} else {
|
||||
exp = true
|
||||
expVal = elements[index]
|
||||
if index < len(elements) {
|
||||
remains = elements[index+1:]
|
||||
}
|
||||
}
|
||||
if err := checkSeekGT(it, input, exp, expVal); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if exp {
|
||||
if err := checkNext(it, remains); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -242,56 +280,36 @@ func TestBlockIteratorTraversal(t *testing.T) {
|
|||
testBlockIterator(t, data, elements)
|
||||
*/
|
||||
|
||||
data, elements := makeTestIndexBlock(1)
|
||||
testBlockIteratorTraversal(t, data, elements)
|
||||
for _, size := range []int{0, 2, 34} {
|
||||
for _, n := range []int{1, indexBlockRestartLen, 3 * indexBlockRestartLen} {
|
||||
data, elements, ext := makeTestIndexBlock(n, size)
|
||||
|
||||
data, elements = makeTestIndexBlock(indexBlockRestartLen)
|
||||
testBlockIteratorTraversal(t, data, elements)
|
||||
|
||||
data, elements = makeTestIndexBlock(3 * indexBlockRestartLen)
|
||||
testBlockIteratorTraversal(t, data, elements)
|
||||
|
||||
data, elements = makeTestIndexBlock(indexBlockEntriesCap)
|
||||
testBlockIteratorTraversal(t, data, elements)
|
||||
}
|
||||
|
||||
func testBlockIteratorTraversal(t *testing.T, data []byte, elements []uint64) {
|
||||
br, err := newBlockReader(data)
|
||||
verifyTraversal(t, elements, ext, func(filter *extFilter) HistoryIndexIterator {
|
||||
br, err := newBlockReader(data, size != 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open the block for reading, %v", err)
|
||||
}
|
||||
it := newBlockIterator(br.data, br.restarts)
|
||||
|
||||
if err := checkNext(it, elements); err != nil {
|
||||
t.Fatal(err)
|
||||
return br.newIterator(filter)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexIteratorTraversal(t *testing.T) {
|
||||
ident := newAccountIdent(common.Hash{0x1})
|
||||
|
||||
dbA := rawdb.NewMemoryDatabase()
|
||||
testIndexIteratorTraversal(t, ident, dbA, makeTestIndexBlocks(dbA, ident, 1))
|
||||
for _, size := range []int{0, 2, 34} {
|
||||
for _, n := range []int{1, 4096, 3 * 4096} {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
elements, ext := makeTestIndexBlocks(db, ident, n, size)
|
||||
|
||||
dbB := rawdb.NewMemoryDatabase()
|
||||
testIndexIteratorTraversal(t, ident, dbB, makeTestIndexBlocks(dbB, ident, 3*indexBlockEntriesCap))
|
||||
|
||||
dbC := rawdb.NewMemoryDatabase()
|
||||
testIndexIteratorTraversal(t, ident, dbC, makeTestIndexBlocks(dbC, ident, indexBlockEntriesCap-1))
|
||||
|
||||
dbD := rawdb.NewMemoryDatabase()
|
||||
testIndexIteratorTraversal(t, ident, dbD, makeTestIndexBlocks(dbD, ident, indexBlockEntriesCap+1))
|
||||
}
|
||||
|
||||
func testIndexIteratorTraversal(t *testing.T, stateIdent stateIdent, db ethdb.KeyValueReader, elements []uint64) {
|
||||
ir, err := newIndexReader(db, stateIdent)
|
||||
verifyTraversal(t, elements, ext, func(filter *extFilter) HistoryIndexIterator {
|
||||
ir, err := newIndexReader(db, ident, size)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open the index reader, %v", err)
|
||||
}
|
||||
it := newIndexIterator(ir.descList, func(id uint32) (*blockReader, error) {
|
||||
return newBlockReader(readStateIndexBlock(stateIdent, db, id))
|
||||
return ir.newIterator(filter)
|
||||
})
|
||||
if err := checkNext(it, elements); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,19 +29,25 @@ import (
|
|||
)
|
||||
|
||||
func TestIndexReaderBasic(t *testing.T) {
|
||||
testIndexReaderBasic(t, 0)
|
||||
testIndexReaderBasic(t, 2)
|
||||
testIndexReaderBasic(t, 34)
|
||||
}
|
||||
|
||||
func testIndexReaderBasic(t *testing.T, bitmapSize int) {
|
||||
elements := []uint64{
|
||||
1, 5, 10, 11, 20,
|
||||
}
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
bw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0)
|
||||
bw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0, bitmapSize)
|
||||
for i := 0; i < len(elements); i++ {
|
||||
bw.append(elements[i])
|
||||
bw.append(elements[i], randomExt(bitmapSize, 5))
|
||||
}
|
||||
batch := db.NewBatch()
|
||||
bw.finish(batch)
|
||||
batch.Write()
|
||||
|
||||
br, err := newIndexReader(db, newAccountIdent(common.Hash{0xa}))
|
||||
br, err := newIndexReader(db, newAccountIdent(common.Hash{0xa}), bitmapSize)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct the index reader, %v", err)
|
||||
}
|
||||
|
|
@ -68,22 +74,28 @@ func TestIndexReaderBasic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIndexReaderLarge(t *testing.T) {
|
||||
testIndexReaderLarge(t, 0)
|
||||
testIndexReaderLarge(t, 2)
|
||||
testIndexReaderLarge(t, 34)
|
||||
}
|
||||
|
||||
func testIndexReaderLarge(t *testing.T, bitmapSize int) {
|
||||
var elements []uint64
|
||||
for i := 0; i < 10*indexBlockEntriesCap; i++ {
|
||||
for i := 0; i < 10*4096; i++ {
|
||||
elements = append(elements, rand.Uint64())
|
||||
}
|
||||
slices.Sort(elements)
|
||||
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
bw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0)
|
||||
bw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0, bitmapSize)
|
||||
for i := 0; i < len(elements); i++ {
|
||||
bw.append(elements[i])
|
||||
bw.append(elements[i], randomExt(bitmapSize, 5))
|
||||
}
|
||||
batch := db.NewBatch()
|
||||
bw.finish(batch)
|
||||
batch.Write()
|
||||
|
||||
br, err := newIndexReader(db, newAccountIdent(common.Hash{0xa}))
|
||||
br, err := newIndexReader(db, newAccountIdent(common.Hash{0xa}), bitmapSize)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct the index reader, %v", err)
|
||||
}
|
||||
|
|
@ -107,7 +119,7 @@ func TestIndexReaderLarge(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestEmptyIndexReader(t *testing.T) {
|
||||
br, err := newIndexReader(rawdb.NewMemoryDatabase(), newAccountIdent(common.Hash{0xa}))
|
||||
br, err := newIndexReader(rawdb.NewMemoryDatabase(), newAccountIdent(common.Hash{0xa}), 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct the index reader, %v", err)
|
||||
}
|
||||
|
|
@ -121,27 +133,33 @@ func TestEmptyIndexReader(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIndexWriterBasic(t *testing.T) {
|
||||
testIndexWriterBasic(t, 0)
|
||||
testIndexWriterBasic(t, 2)
|
||||
testIndexWriterBasic(t, 34)
|
||||
}
|
||||
|
||||
func testIndexWriterBasic(t *testing.T, bitmapSize int) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0)
|
||||
iw.append(2)
|
||||
if err := iw.append(1); err == nil {
|
||||
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0, bitmapSize)
|
||||
iw.append(2, randomExt(bitmapSize, 5))
|
||||
if err := iw.append(1, randomExt(bitmapSize, 5)); err == nil {
|
||||
t.Fatal("out-of-order insertion is not expected")
|
||||
}
|
||||
var maxElem uint64
|
||||
for i := 0; i < 10; i++ {
|
||||
iw.append(uint64(i + 3))
|
||||
iw.append(uint64(i+3), randomExt(bitmapSize, 5))
|
||||
maxElem = uint64(i + 3)
|
||||
}
|
||||
batch := db.NewBatch()
|
||||
iw.finish(batch)
|
||||
batch.Write()
|
||||
|
||||
iw, err := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), maxElem)
|
||||
iw, err := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), maxElem, bitmapSize)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct the block writer, %v", err)
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := iw.append(uint64(i + 100)); err != nil {
|
||||
if err := iw.append(uint64(i+100), randomExt(bitmapSize, 5)); err != nil {
|
||||
t.Fatalf("Failed to append item, %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -149,61 +167,37 @@ func TestIndexWriterBasic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIndexWriterWithLimit(t *testing.T) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0)
|
||||
testIndexWriterWithLimit(t, 0)
|
||||
testIndexWriterWithLimit(t, 2)
|
||||
testIndexWriterWithLimit(t, 34)
|
||||
}
|
||||
|
||||
var maxElem uint64
|
||||
for i := 0; i < indexBlockEntriesCap*2; i++ {
|
||||
iw.append(uint64(i + 1))
|
||||
maxElem = uint64(i + 1)
|
||||
func testIndexWriterWithLimit(t *testing.T, bitmapSize int) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0, bitmapSize)
|
||||
|
||||
// 200 iterations (with around 50 bytes extension) is enough to cross
|
||||
// the block boundary (4096 bytes)
|
||||
for i := 0; i < 200; i++ {
|
||||
iw.append(uint64(i+1), randomExt(bitmapSize, 50))
|
||||
}
|
||||
batch := db.NewBatch()
|
||||
iw.finish(batch)
|
||||
batch.Write()
|
||||
|
||||
suites := []struct {
|
||||
limit uint64
|
||||
expMax uint64
|
||||
}{
|
||||
// nothing to truncate
|
||||
{
|
||||
maxElem, maxElem,
|
||||
},
|
||||
// truncate the last element
|
||||
{
|
||||
maxElem - 1, maxElem - 1,
|
||||
},
|
||||
// truncation around the block boundary
|
||||
{
|
||||
uint64(indexBlockEntriesCap + 1),
|
||||
uint64(indexBlockEntriesCap + 1),
|
||||
},
|
||||
// truncation around the block boundary
|
||||
{
|
||||
uint64(indexBlockEntriesCap),
|
||||
uint64(indexBlockEntriesCap),
|
||||
},
|
||||
{
|
||||
uint64(1), uint64(1),
|
||||
},
|
||||
// truncate the entire index, it's in theory invalid
|
||||
{
|
||||
uint64(0), uint64(0),
|
||||
},
|
||||
}
|
||||
for i, suite := range suites {
|
||||
iw, err := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), suite.limit)
|
||||
for i := 0; i < 200; i++ {
|
||||
limit := uint64(i + 1)
|
||||
iw, err := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), limit, bitmapSize)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct the index writer, %v", err)
|
||||
}
|
||||
if iw.lastID != suite.expMax {
|
||||
t.Fatalf("Test %d, unexpected max value, got %d, want %d", i, iw.lastID, suite.expMax)
|
||||
if iw.lastID != limit {
|
||||
t.Fatalf("Test %d, unexpected max value, got %d, want %d", i, iw.lastID, limit)
|
||||
}
|
||||
|
||||
// Re-fill the elements
|
||||
var maxElem uint64
|
||||
for elem := suite.limit + 1; elem < indexBlockEntriesCap*4; elem++ {
|
||||
if err := iw.append(elem); err != nil {
|
||||
for elem := limit + 1; elem < 500; elem++ {
|
||||
if err := iw.append(elem, randomExt(bitmapSize, 5)); err != nil {
|
||||
t.Fatalf("Failed to append value %d: %v", elem, err)
|
||||
}
|
||||
maxElem = elem
|
||||
|
|
@ -215,12 +209,20 @@ func TestIndexWriterWithLimit(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIndexDeleterBasic(t *testing.T) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0)
|
||||
testIndexDeleterBasic(t, 0)
|
||||
testIndexDeleterBasic(t, 2)
|
||||
testIndexDeleterBasic(t, 34)
|
||||
}
|
||||
|
||||
func testIndexDeleterBasic(t *testing.T, bitmapSize int) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0, bitmapSize)
|
||||
|
||||
// 200 iterations (with around 50 bytes extension) is enough to cross
|
||||
// the block boundary (4096 bytes)
|
||||
var maxElem uint64
|
||||
for i := 0; i < indexBlockEntriesCap*4; i++ {
|
||||
iw.append(uint64(i + 1))
|
||||
for i := 0; i < 200; i++ {
|
||||
iw.append(uint64(i+1), randomExt(bitmapSize, 50))
|
||||
maxElem = uint64(i + 1)
|
||||
}
|
||||
batch := db.NewBatch()
|
||||
|
|
@ -228,11 +230,11 @@ func TestIndexDeleterBasic(t *testing.T) {
|
|||
batch.Write()
|
||||
|
||||
// Delete unknown id, the request should be rejected
|
||||
id, _ := newIndexDeleter(db, newAccountIdent(common.Hash{0xa}), maxElem)
|
||||
if err := id.pop(indexBlockEntriesCap * 5); err == nil {
|
||||
id, _ := newIndexDeleter(db, newAccountIdent(common.Hash{0xa}), maxElem, bitmapSize)
|
||||
if err := id.pop(500); err == nil {
|
||||
t.Fatal("Expect error to occur for unknown id")
|
||||
}
|
||||
for i := indexBlockEntriesCap * 4; i >= 1; i-- {
|
||||
for i := 200; i >= 1; i-- {
|
||||
if err := id.pop(uint64(i)); err != nil {
|
||||
t.Fatalf("Unexpected error for element popping, %v", err)
|
||||
}
|
||||
|
|
@ -243,57 +245,33 @@ func TestIndexDeleterBasic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIndexDeleterWithLimit(t *testing.T) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0)
|
||||
testIndexDeleterWithLimit(t, 0)
|
||||
testIndexDeleterWithLimit(t, 2)
|
||||
testIndexDeleterWithLimit(t, 34)
|
||||
}
|
||||
|
||||
var maxElem uint64
|
||||
for i := 0; i < indexBlockEntriesCap*2; i++ {
|
||||
iw.append(uint64(i + 1))
|
||||
maxElem = uint64(i + 1)
|
||||
func testIndexDeleterWithLimit(t *testing.T, bitmapSize int) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0, bitmapSize)
|
||||
|
||||
// 200 iterations (with around 50 bytes extension) is enough to cross
|
||||
// the block boundary (4096 bytes)
|
||||
for i := 0; i < 200; i++ {
|
||||
iw.append(uint64(i+1), randomExt(bitmapSize, 50))
|
||||
}
|
||||
batch := db.NewBatch()
|
||||
iw.finish(batch)
|
||||
batch.Write()
|
||||
|
||||
suites := []struct {
|
||||
limit uint64
|
||||
expMax uint64
|
||||
}{
|
||||
// nothing to truncate
|
||||
{
|
||||
maxElem, maxElem,
|
||||
},
|
||||
// truncate the last element
|
||||
{
|
||||
maxElem - 1, maxElem - 1,
|
||||
},
|
||||
// truncation around the block boundary
|
||||
{
|
||||
uint64(indexBlockEntriesCap + 1),
|
||||
uint64(indexBlockEntriesCap + 1),
|
||||
},
|
||||
// truncation around the block boundary
|
||||
{
|
||||
uint64(indexBlockEntriesCap),
|
||||
uint64(indexBlockEntriesCap),
|
||||
},
|
||||
{
|
||||
uint64(1), uint64(1),
|
||||
},
|
||||
// truncate the entire index, it's in theory invalid
|
||||
{
|
||||
uint64(0), uint64(0),
|
||||
},
|
||||
}
|
||||
for i, suite := range suites {
|
||||
id, err := newIndexDeleter(db, newAccountIdent(common.Hash{0xa}), suite.limit)
|
||||
for i := 0; i < 200; i++ {
|
||||
limit := uint64(i + 1)
|
||||
id, err := newIndexDeleter(db, newAccountIdent(common.Hash{0xa}), limit, bitmapSize)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct the index writer, %v", err)
|
||||
}
|
||||
if id.lastID != suite.expMax {
|
||||
t.Fatalf("Test %d, unexpected max value, got %d, want %d", i, id.lastID, suite.expMax)
|
||||
if id.lastID != limit {
|
||||
t.Fatalf("Test %d, unexpected max value, got %d, want %d", i, iw.lastID, limit)
|
||||
}
|
||||
|
||||
// Keep removing elements
|
||||
for elem := id.lastID; elem > 0; elem-- {
|
||||
if err := id.pop(elem); err != nil {
|
||||
|
|
@ -339,7 +317,7 @@ func TestBatchIndexerWrite(t *testing.T) {
|
|||
}
|
||||
}
|
||||
for addrHash, indexes := range accounts {
|
||||
ir, _ := newIndexReader(db, newAccountIdent(addrHash))
|
||||
ir, _ := newIndexReader(db, newAccountIdent(addrHash), 0)
|
||||
for i := 0; i < len(indexes)-1; i++ {
|
||||
n, err := ir.readGreaterThan(indexes[i])
|
||||
if err != nil {
|
||||
|
|
@ -359,7 +337,7 @@ func TestBatchIndexerWrite(t *testing.T) {
|
|||
}
|
||||
for addrHash, slots := range storages {
|
||||
for slotHash, indexes := range slots {
|
||||
ir, _ := newIndexReader(db, newStorageIdent(addrHash, slotHash))
|
||||
ir, _ := newIndexReader(db, newStorageIdent(addrHash, slotHash), 0)
|
||||
for i := 0; i < len(indexes)-1; i++ {
|
||||
n, err := ir.readGreaterThan(indexes[i])
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import (
|
|||
const (
|
||||
// The batch size for reading state histories
|
||||
historyReadBatch = 1000
|
||||
historyIndexBatch = 8 * 1024 * 1024 // The number of state history indexes for constructing or deleting as batch
|
||||
|
||||
stateHistoryIndexV0 = uint8(0) // initial version of state index structure
|
||||
stateHistoryIndexVersion = stateHistoryIndexV0 // the current state index version
|
||||
|
|
@ -191,12 +192,12 @@ func (b *batchIndexer) finish(force bool) error {
|
|||
for ident, list := range b.index {
|
||||
eg.Go(func() error {
|
||||
if !b.delete {
|
||||
iw, err := newIndexWriter(b.db, ident, indexed)
|
||||
iw, err := newIndexWriter(b.db, ident, indexed, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range list {
|
||||
if err := iw.append(n); err != nil {
|
||||
if err := iw.append(n, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -204,7 +205,7 @@ func (b *batchIndexer) finish(force bool) error {
|
|||
iw.finish(batch)
|
||||
})
|
||||
} else {
|
||||
id, err := newIndexDeleter(b.db, ident, indexed)
|
||||
id, err := newIndexDeleter(b.db, ident, indexed, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ type indexReaderWithLimitTag struct {
|
|||
}
|
||||
|
||||
// newIndexReaderWithLimitTag constructs a index reader with indexing position.
|
||||
func newIndexReaderWithLimitTag(db ethdb.KeyValueReader, state stateIdent, limit uint64) (*indexReaderWithLimitTag, error) {
|
||||
r, err := newIndexReader(db, state)
|
||||
func newIndexReaderWithLimitTag(db ethdb.KeyValueReader, state stateIdent, limit uint64, bitmapSize int) (*indexReaderWithLimitTag, error) {
|
||||
r, err := newIndexReader(db, state, bitmapSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -252,7 +252,7 @@ func (r *historyReader) read(state stateIdentQuery, stateID uint64, lastID uint6
|
|||
// state retrieval
|
||||
ir, ok := r.readers[state.String()]
|
||||
if !ok {
|
||||
ir, err = newIndexReaderWithLimitTag(r.disk, state.stateIdent, metadata.Last)
|
||||
ir, err = newIndexReaderWithLimitTag(r.disk, state.stateIdent, metadata.Last, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,17 +159,6 @@ func newTrienodeHistory(root common.Hash, parent common.Hash, block uint64, node
|
|||
}
|
||||
}
|
||||
|
||||
// sharedLen returns the length of the common prefix shared by a and b.
|
||||
func sharedLen(a, b []byte) int {
|
||||
n := min(len(a), len(b))
|
||||
for i := range n {
|
||||
if a[i] != b[i] {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// typ implements the history interface, returning the historical data type held.
|
||||
func (h *trienodeHistory) typ() historyType {
|
||||
return typeTrienodeHistory
|
||||
|
|
|
|||
86
triedb/pathdb/history_trienode_utils.go
Normal file
86
triedb/pathdb/history_trienode_utils.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// Copyright 2025 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 pathdb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// sharedLen returns the length of the common prefix shared by a and b.
|
||||
func sharedLen(a, b []byte) int {
|
||||
n := min(len(a), len(b))
|
||||
for i := range n {
|
||||
if a[i] != b[i] {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// encodeIDs sorts the given list of uint16 IDs and encodes them into a
|
||||
// compact byte slice using variable-length unsigned integer encoding.
|
||||
func encodeIDs(ids []uint16) []byte {
|
||||
slices.Sort(ids)
|
||||
buf := make([]byte, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
buf = binary.AppendUvarint(buf, uint64(id))
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// decodeIDs decodes a sequence of variable-length encoded uint16 IDs from the
|
||||
// given byte slice and returns them as a set.
|
||||
//
|
||||
// Returns an error if the input buffer does not contain a complete Uvarint value.
|
||||
func decodeIDs(buf []byte) ([]uint16, error) {
|
||||
var res []uint16
|
||||
for len(buf) > 0 {
|
||||
id, n := binary.Uvarint(buf)
|
||||
if n <= 0 {
|
||||
return nil, fmt.Errorf("too short for decoding node id, %v", buf)
|
||||
}
|
||||
buf = buf[n:]
|
||||
res = append(res, uint16(id))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// isAncestor reports whether node x is the ancestor of node y.
|
||||
func isAncestor(x, y uint16) bool {
|
||||
if y <= x {
|
||||
return false
|
||||
}
|
||||
for y > x {
|
||||
y = (y - 1) / 16 // parentID(y) = (y - 1) / 16
|
||||
if y == x {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isBitSet reports whether the bit at `index` in the byte slice `b` is set.
|
||||
func isBitSet(b []byte, index int) bool {
|
||||
return b[index/8]&(1<<(index%8)) != 0
|
||||
}
|
||||
|
||||
// setBit sets the bit at `index` in the byte slice `b` to 1.
|
||||
func setBit(b []byte, index int) {
|
||||
b[index/8] |= 1 << (index % 8)
|
||||
}
|
||||
46
triedb/pathdb/history_trienode_utils_test.go
Normal file
46
triedb/pathdb/history_trienode_utils_test.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2025 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 pathdb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsAncestor(t *testing.T) {
|
||||
suites := []struct {
|
||||
x, y uint16
|
||||
want bool
|
||||
}{
|
||||
{0, 1, true},
|
||||
{0, 16, true},
|
||||
{0, 17, true},
|
||||
{0, 272, true},
|
||||
|
||||
{1, 0, false},
|
||||
{1, 2, false},
|
||||
{1, 17, true},
|
||||
{1, 18, true},
|
||||
{17, 273, true},
|
||||
{1, 1, false},
|
||||
}
|
||||
for _, tc := range suites {
|
||||
result := isAncestor(tc.x, tc.y)
|
||||
if result != tc.want {
|
||||
t.Fatalf("isAncestor(%d, %d) = %v, want %v", tc.x, tc.y, result, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue