les: historical data garbage collection (#19570)
This change introduces garbage collection for the light client. Historical chain data is deleted periodically. If you want to disable the GC, use the --light.nopruning flag.
This commit is contained in:
@ -80,6 +80,39 @@ func ReadAllHashes(db ethdb.Iteratee, number uint64) []common.Hash {
|
||||
return hashes
|
||||
}
|
||||
|
||||
// ReadAllCanonicalHashes retrieves all canonical number and hash mappings at the
|
||||
// certain chain range. If the accumulated entries reaches the given threshold,
|
||||
// abort the iteration and return the semi-finish result.
|
||||
func ReadAllCanonicalHashes(db ethdb.Iteratee, from uint64, to uint64, limit int) ([]uint64, []common.Hash) {
|
||||
// Short circuit if the limit is 0.
|
||||
if limit == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var (
|
||||
numbers []uint64
|
||||
hashes []common.Hash
|
||||
)
|
||||
// Construct the key prefix of start point.
|
||||
start, end := headerHashKey(from), headerHashKey(to)
|
||||
it := db.NewIterator(nil, start)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
if bytes.Compare(it.Key(), end) >= 0 {
|
||||
break
|
||||
}
|
||||
if key := it.Key(); len(key) == len(headerPrefix)+8+1 && bytes.Equal(key[len(key)-1:], headerHashSuffix) {
|
||||
numbers = append(numbers, binary.BigEndian.Uint64(key[len(headerPrefix):len(headerPrefix)+8]))
|
||||
hashes = append(hashes, common.BytesToHash(it.Value()))
|
||||
// If the accumulated entries reaches the limit threshold, return.
|
||||
if len(numbers) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return numbers, hashes
|
||||
}
|
||||
|
||||
// ReadHeaderNumber returns the header number assigned to a hash.
|
||||
func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 {
|
||||
data, _ := db.Get(headerNumberKey(hash))
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@ -424,3 +425,35 @@ func TestAncientStorage(t *testing.T) {
|
||||
t.Fatalf("invalid td returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalHashIteration(t *testing.T) {
|
||||
var cases = []struct {
|
||||
from, to uint64
|
||||
limit int
|
||||
expect []uint64
|
||||
}{
|
||||
{1, 8, 0, nil},
|
||||
{1, 8, 1, []uint64{1}},
|
||||
{1, 8, 10, []uint64{1, 2, 3, 4, 5, 6, 7}},
|
||||
{1, 9, 10, []uint64{1, 2, 3, 4, 5, 6, 7, 8}},
|
||||
{2, 9, 10, []uint64{2, 3, 4, 5, 6, 7, 8}},
|
||||
{9, 10, 10, nil},
|
||||
}
|
||||
// Test empty db iteration
|
||||
db := NewMemoryDatabase()
|
||||
numbers, _ := ReadAllCanonicalHashes(db, 0, 10, 10)
|
||||
if len(numbers) != 0 {
|
||||
t.Fatalf("No entry should be returned to iterate an empty db")
|
||||
}
|
||||
// Fill database with testing data.
|
||||
for i := uint64(1); i <= 8; i++ {
|
||||
WriteCanonicalHash(db, common.Hash{}, i)
|
||||
WriteTd(db, common.Hash{}, i, big.NewInt(10)) // Write some interferential data
|
||||
}
|
||||
for i, c := range cases {
|
||||
numbers, _ := ReadAllCanonicalHashes(db, c.from, c.to, c.limit)
|
||||
if !reflect.DeepEqual(numbers, c.expect) {
|
||||
t.Fatalf("Case %d failed, want %v, got %v", i, c.expect, numbers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@ -151,3 +152,24 @@ func WriteBloomBits(db ethdb.KeyValueWriter, bit uint, section uint64, head comm
|
||||
log.Crit("Failed to store bloom bits", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteBloombits removes all compressed bloom bits vector belonging to the
|
||||
// given section range and bit index.
|
||||
func DeleteBloombits(db ethdb.Database, bit uint, from uint64, to uint64) {
|
||||
start, end := bloomBitsKey(bit, from, common.Hash{}), bloomBitsKey(bit, to, common.Hash{})
|
||||
it := db.NewIterator(nil, start)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
if bytes.Compare(it.Key(), end) >= 0 {
|
||||
break
|
||||
}
|
||||
if len(it.Key()) != len(bloomBitsPrefix)+2+8+32 {
|
||||
continue
|
||||
}
|
||||
db.Delete(it.Key())
|
||||
}
|
||||
if it.Error() != nil {
|
||||
log.Crit("Failed to delete bloom bits", "err", it.Error())
|
||||
}
|
||||
}
|
||||
|
@ -17,12 +17,14 @@
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
@ -106,3 +108,46 @@ func TestLookupStorage(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteBloomBits(t *testing.T) {
|
||||
// Prepare testing data
|
||||
db := NewMemoryDatabase()
|
||||
for i := uint(0); i < 2; i++ {
|
||||
for s := uint64(0); s < 2; s++ {
|
||||
WriteBloomBits(db, i, s, params.MainnetGenesisHash, []byte{0x01, 0x02})
|
||||
WriteBloomBits(db, i, s, params.RinkebyGenesisHash, []byte{0x01, 0x02})
|
||||
}
|
||||
}
|
||||
check := func(bit uint, section uint64, head common.Hash, exist bool) {
|
||||
bits, _ := ReadBloomBits(db, bit, section, head)
|
||||
if exist && !bytes.Equal(bits, []byte{0x01, 0x02}) {
|
||||
t.Fatalf("Bloombits mismatch")
|
||||
}
|
||||
if !exist && len(bits) > 0 {
|
||||
t.Fatalf("Bloombits should be removed")
|
||||
}
|
||||
}
|
||||
// Check the existence of written data.
|
||||
check(0, 0, params.MainnetGenesisHash, true)
|
||||
check(0, 0, params.RinkebyGenesisHash, true)
|
||||
|
||||
// Check the existence of deleted data.
|
||||
DeleteBloombits(db, 0, 0, 1)
|
||||
check(0, 0, params.MainnetGenesisHash, false)
|
||||
check(0, 0, params.RinkebyGenesisHash, false)
|
||||
check(0, 1, params.MainnetGenesisHash, true)
|
||||
check(0, 1, params.RinkebyGenesisHash, true)
|
||||
|
||||
// Check the existence of deleted data.
|
||||
DeleteBloombits(db, 0, 0, 2)
|
||||
check(0, 0, params.MainnetGenesisHash, false)
|
||||
check(0, 0, params.RinkebyGenesisHash, false)
|
||||
check(0, 1, params.MainnetGenesisHash, false)
|
||||
check(0, 1, params.RinkebyGenesisHash, false)
|
||||
|
||||
// Bit1 shouldn't be affect.
|
||||
check(1, 0, params.MainnetGenesisHash, true)
|
||||
check(1, 0, params.RinkebyGenesisHash, true)
|
||||
check(1, 1, params.MainnetGenesisHash, true)
|
||||
check(1, 1, params.RinkebyGenesisHash, true)
|
||||
}
|
||||
|
@ -287,12 +287,12 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) {
|
||||
backoff = true
|
||||
continue
|
||||
|
||||
case *number < params.ImmutabilityThreshold:
|
||||
log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", params.ImmutabilityThreshold)
|
||||
case *number < params.FullImmutabilityThreshold:
|
||||
log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", params.FullImmutabilityThreshold)
|
||||
backoff = true
|
||||
continue
|
||||
|
||||
case *number-params.ImmutabilityThreshold <= f.frozen:
|
||||
case *number-params.FullImmutabilityThreshold <= f.frozen:
|
||||
log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen)
|
||||
backoff = true
|
||||
continue
|
||||
@ -304,7 +304,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) {
|
||||
continue
|
||||
}
|
||||
// Seems we have data ready to be frozen, process in usable batches
|
||||
limit := *number - params.ImmutabilityThreshold
|
||||
limit := *number - params.FullImmutabilityThreshold
|
||||
if limit-f.frozen > freezerBatchLimit {
|
||||
limit = f.frozen + freezerBatchLimit
|
||||
}
|
||||
|
Reference in New Issue
Block a user