package prune import ( "encoding/binary" "fmt" "math" "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 } 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 }