erigon-pulse/ethdb/prune/storage_mode.go
2022-01-22 11:18:24 +07:00

417 lines
8.9 KiB
Go

package prune
import (
"encoding/binary"
"errors"
"fmt"
"math"
"reflect"
"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/erigon/params"
)
var DefaultMode = Mode{
Initialised: true,
History: Distance(math.MaxUint64), // all off
Receipts: Distance(math.MaxUint64),
TxIndex: Distance(math.MaxUint64),
CallTraces: Distance(math.MaxUint64),
Experiments: Experiments{}, // all off
}
type Experiments struct {
TEVM bool
}
func FromCli(flags string, exactHistory, exactReceipts, exactTxIndex, exactCallTraces,
beforeH, beforeR, beforeT, beforeC uint64, experiments []string) (Mode, error) {
mode := DefaultMode
if flags == "default" || flags == "disabled" {
return DefaultMode, nil
}
mode.Initialised = true
for _, flag := range flags {
switch flag {
case 'h':
mode.History = Distance(params.FullImmutabilityThreshold)
case 'r':
mode.Receipts = Distance(params.FullImmutabilityThreshold)
case 't':
mode.TxIndex = Distance(params.FullImmutabilityThreshold)
case 'c':
mode.CallTraces = Distance(params.FullImmutabilityThreshold)
default:
return DefaultMode, fmt.Errorf("unexpected flag found: %c", flag)
}
}
if exactHistory > 0 {
mode.History = Distance(exactHistory)
}
if exactReceipts > 0 {
mode.Receipts = Distance(exactReceipts)
}
if exactTxIndex > 0 {
mode.TxIndex = Distance(exactTxIndex)
}
if exactCallTraces > 0 {
mode.CallTraces = Distance(exactCallTraces)
}
if beforeH > 0 {
mode.History = Before(beforeH)
}
if beforeR > 0 {
mode.Receipts = Before(beforeR)
}
if beforeT > 0 {
mode.TxIndex = Before(beforeT)
}
if beforeC > 0 {
mode.CallTraces = Before(beforeC)
}
for _, ex := range experiments {
switch ex {
case "tevm":
mode.Experiments.TEVM = true
case "":
// skip
default:
return DefaultMode, fmt.Errorf("unexpected experiment found: %s", ex)
}
}
return mode, nil
}
func Get(db kv.Getter) (Mode, error) {
prune := DefaultMode
prune.Initialised = true
blockAmount, err := get(db, kv.PruneHistory)
if err != nil {
return prune, err
}
if blockAmount != nil {
prune.History = blockAmount
}
blockAmount, err = get(db, kv.PruneReceipts)
if err != nil {
return prune, err
}
if blockAmount != nil {
prune.Receipts = blockAmount
}
blockAmount, err = get(db, kv.PruneTxIndex)
if err != nil {
return prune, err
}
if blockAmount != nil {
prune.TxIndex = blockAmount
}
blockAmount, err = get(db, kv.PruneCallTraces)
if err != nil {
return prune, err
}
if blockAmount != nil {
prune.CallTraces = blockAmount
}
v, err := db.GetOne(kv.DatabaseInfo, kv.StorageModeTEVM)
if err != nil {
return prune, err
}
prune.Experiments.TEVM = len(v) == 1 && v[0] == 1
return prune, nil
}
type Mode struct {
Initialised bool // Set when the values are initialised (not default)
History BlockAmount
Receipts BlockAmount
TxIndex BlockAmount
CallTraces BlockAmount
Experiments Experiments
}
type BlockAmount interface {
PruneTo(stageHead uint64) uint64
Enabled() bool
toValue() uint64
dbType() []byte
useDefaultValue() bool
}
// Distance amount of blocks to keep in DB
// but manual manipulation with such distance is very unsafe
// for example:
// deleteUntil := currentStageProgress - pruningDistance
// may delete whole db - because of uint64 underflow when pruningDistance > currentStageProgress
type Distance uint64
func (p Distance) Enabled() bool { return p != math.MaxUint64 }
func (p Distance) toValue() uint64 { return uint64(p) }
func (p Distance) useDefaultValue() bool { return uint64(p) == params.FullImmutabilityThreshold }
func (p Distance) dbType() []byte { return kv.PruneTypeOlder }
func (p Distance) PruneTo(stageHead uint64) uint64 {
if p == 0 {
panic("pruning distance were not set")
}
if uint64(p) > stageHead {
return 0
}
return stageHead - uint64(p)
}
// Before number after which keep in DB
type Before uint64
func (b Before) Enabled() bool { return b > 0 }
func (b Before) toValue() uint64 { return uint64(b) }
func (b Before) useDefaultValue() bool { return uint64(b) == 0 }
func (b Before) dbType() []byte { return kv.PruneTypeBefore }
func (b Before) PruneTo(uint64) uint64 {
if b == 0 {
return uint64(b)
}
return uint64(b) - 1
}
func (m Mode) String() string {
if !m.Initialised {
return "default"
}
long := ""
short := "--prune="
if m.History.Enabled() {
if m.History.useDefaultValue() {
short += "h"
} else {
long += fmt.Sprintf(" --prune.h.%s=%d", m.History.dbType(), m.History.toValue())
}
}
if m.Receipts.Enabled() {
if m.Receipts.useDefaultValue() {
short += "r"
} else {
long += fmt.Sprintf(" --prune.r.%s=%d", m.Receipts.dbType(), m.Receipts.toValue())
}
}
if m.TxIndex.Enabled() {
if m.TxIndex.useDefaultValue() {
short += "t"
} else {
long += fmt.Sprintf(" --prune.t.%s=%d", m.TxIndex.dbType(), m.TxIndex.toValue())
}
}
if m.CallTraces.Enabled() {
if m.CallTraces.useDefaultValue() {
short += "c"
} else {
long += fmt.Sprintf(" --prune.c.%s=%d", m.CallTraces.dbType(), m.CallTraces.toValue())
}
}
if m.Experiments.TEVM {
long += " --experiments.tevm=enabled"
}
return short + long
}
func Override(db kv.RwTx, sm Mode) error {
var (
err error
)
err = set(db, kv.PruneHistory, sm.History)
if err != nil {
return err
}
err = set(db, kv.PruneReceipts, sm.Receipts)
if err != nil {
return err
}
err = set(db, kv.PruneTxIndex, sm.TxIndex)
if err != nil {
return err
}
err = set(db, kv.PruneCallTraces, sm.CallTraces)
if err != nil {
return err
}
err = setMode(db, kv.StorageModeTEVM, sm.Experiments.TEVM)
if err != nil {
return err
}
return nil
}
// EnsureNotChanged - prohibit change some configs after node creation. prohibit from human mistakes
func EnsureNotChanged(tx kv.GetPut, pruneMode Mode) (Mode, error) {
err := setIfNotExist(tx, pruneMode)
if err != nil {
return pruneMode, err
}
pm, err := Get(tx)
if err != nil {
return pruneMode, err
}
if pruneMode.Initialised {
// If storage mode is not explicitly specified, we take whatever is in the database
if !reflect.DeepEqual(pm, pruneMode) {
return pm, errors.New("not allowed change of --prune flag, last time you used: " + pm.String())
}
}
return pm, nil
}
func setIfNotExist(db kv.GetPut, pm Mode) error {
var (
err error
)
if !pm.Initialised {
pm = DefaultMode
}
pruneDBData := map[string]BlockAmount{
string(kv.PruneHistory): pm.History,
string(kv.PruneReceipts): pm.Receipts,
string(kv.PruneTxIndex): pm.TxIndex,
string(kv.PruneCallTraces): pm.CallTraces,
}
for key, value := range pruneDBData {
err = setOnEmpty(db, []byte(key), value)
if err != nil {
return err
}
}
err = setModeOnEmpty(db, kv.StorageModeTEVM, pm.Experiments.TEVM)
if err != nil {
return err
}
return nil
}
func createBlockAmount(pruneType []byte, v []byte) (BlockAmount, error) {
var blockAmount BlockAmount
switch string(pruneType) {
case string(kv.PruneTypeOlder):
blockAmount = Distance(binary.BigEndian.Uint64(v))
case string(kv.PruneTypeBefore):
blockAmount = Before(binary.BigEndian.Uint64(v))
default:
return nil, fmt.Errorf("unexpected block amount type: %s", string(pruneType))
}
return blockAmount, nil
}
func get(db kv.Getter, key []byte) (BlockAmount, error) {
v, err := db.GetOne(kv.DatabaseInfo, key)
if err != nil {
return nil, err
}
vType, err := db.GetOne(kv.DatabaseInfo, keyType(key))
if err != nil {
return nil, err
}
if v != nil {
blockAmount, err := createBlockAmount(vType, v)
if err != nil {
return nil, err
}
return blockAmount, nil
}
return nil, nil
}
func set(db kv.Putter, key []byte, blockAmount BlockAmount) error {
v := make([]byte, 8)
binary.BigEndian.PutUint64(v, blockAmount.toValue())
if err := db.Put(kv.DatabaseInfo, key, v); err != nil {
return err
}
keyType := keyType(key)
if err := db.Put(kv.DatabaseInfo, keyType, blockAmount.dbType()); err != nil {
return err
}
return nil
}
func keyType(name []byte) []byte {
return append(name, []byte("Type")...)
}
func setOnEmpty(db kv.GetPut, key []byte, blockAmount BlockAmount) error {
mode, err := db.GetOne(kv.DatabaseInfo, key)
if err != nil {
return err
}
if len(mode) == 0 {
v := make([]byte, 8)
binary.BigEndian.PutUint64(v, blockAmount.toValue())
if err = db.Put(kv.DatabaseInfo, key, v); err != nil {
return err
}
if err = db.Put(kv.DatabaseInfo, keyType(key), blockAmount.dbType()); err != nil {
return err
}
}
return nil
}
func setMode(db kv.RwTx, key []byte, currentValue bool) error {
val := []byte{2}
if currentValue {
val = []byte{1}
}
if err := db.Put(kv.DatabaseInfo, key, val); err != nil {
return err
}
return nil
}
func setModeOnEmpty(db kv.GetPut, key []byte, currentValue bool) error {
mode, err := db.GetOne(kv.DatabaseInfo, key)
if err != nil {
return err
}
if len(mode) == 0 {
val := []byte{2}
if currentValue {
val = []byte{1}
}
if err = db.Put(kv.DatabaseInfo, key, val); err != nil {
return err
}
}
return nil
}