Fantom-foundation/go-lachesis

View on GitHub
app/store.go

Summary

Maintainability
A
3 hrs
Test Coverage
package app

import (
    "sync"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core/rawdb"
    "github.com/ethereum/go-ethereum/core/state"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/ethdb"
    "github.com/ethereum/go-ethereum/rlp"
    lru "github.com/hashicorp/golang-lru"

    "github.com/Fantom-foundation/go-lachesis/kvdb"
    "github.com/Fantom-foundation/go-lachesis/kvdb/nokeyiserr"
    "github.com/Fantom-foundation/go-lachesis/kvdb/table"
    "github.com/Fantom-foundation/go-lachesis/logger"
    "github.com/Fantom-foundation/go-lachesis/topicsdb"
)

// Store is a node persistent storage working over physical key-value database.
type Store struct {
    cfg StoreConfig

    mainDb kvdb.KeyValueStore
    table  struct {
        // score economy tables
        ActiveValidationScore  kvdb.KeyValueStore `table:"V"`
        DirtyValidationScore   kvdb.KeyValueStore `table:"v"`
        ActiveOriginationScore kvdb.KeyValueStore `table:"O"`
        DirtyOriginationScore  kvdb.KeyValueStore `table:"o"`
        BlockDowntime          kvdb.KeyValueStore `table:"m"`

        // PoI economy tables
        StakerPOIScore       kvdb.KeyValueStore `table:"s"`
        AddressPOIScore      kvdb.KeyValueStore `table:"a"`
        AddressFee           kvdb.KeyValueStore `table:"g"`
        StakerDelegationsFee kvdb.KeyValueStore `table:"d"`
        AddressLastTxTime    kvdb.KeyValueStore `table:"X"`
        TotalPoiFee          kvdb.KeyValueStore `table:"U"`

        // gas power economy tables
        GasPowerRefund kvdb.KeyValueStore `table:"R"`

        // SFC-related economy tables
        Validators   kvdb.KeyValueStore `table:"1"`
        Stakers      kvdb.KeyValueStore `table:"2"`
        Delegations  kvdb.KeyValueStore `table:"3"`
        SfcConstants kvdb.KeyValueStore `table:"4"`
        TotalSupply  kvdb.KeyValueStore `table:"5"`

        // API-only tables
        Receipts                    kvdb.KeyValueStore `table:"r"`
        DelegationOldRewards        kvdb.KeyValueStore `table:"6"`
        StakerOldRewards            kvdb.KeyValueStore `table:"7"`
        StakerDelegationsOldRewards kvdb.KeyValueStore `table:"8"`

        Evm      ethdb.Database
        EvmState state.Database
        EvmLogs  *topicsdb.Index
    }

    cache struct {
        Receipts      *lru.Cache `cache:"-"` // store by value
        Validators    *lru.Cache `cache:"-"` // store by pointer
        Stakers       *lru.Cache `cache:"-"` // store by pointer
        Delegations   *lru.Cache `cache:"-"` // store by pointer
        BlockDowntime *lru.Cache `cache:"-"` // store by pointer
    }

    mutex struct {
        BlockDowntime sync.Mutex
        Validators    sync.Mutex
        Stakers       sync.Mutex
        Delegations   sync.Mutex
    }

    logger.Instance
}

// NewStore creates store over key-value db.
func NewStore(mainDb kvdb.KeyValueStore, cfg StoreConfig) *Store {
    s := &Store{
        cfg:      cfg,
        mainDb:   mainDb,
        Instance: logger.MakeInstance(),
    }

    table.MigrateTables(&s.table, s.mainDb)

    evmTable := nokeyiserr.Wrap(table.New(s.mainDb, []byte("M"))) // ETH expects that "not found" is an error
    s.table.Evm = rawdb.NewDatabase(evmTable)
    s.table.EvmState = state.NewDatabaseWithCache(s.table.Evm, 16, "")
    s.table.EvmLogs = topicsdb.New(table.New(s.mainDb, []byte("L")))

    s.initCache()

    return s
}

func (s *Store) initCache() {
    s.cache.Receipts = s.makeCache(s.cfg.ReceiptsCacheSize)
    s.cache.Validators = s.makeCache(2)
    s.cache.Stakers = s.makeCache(s.cfg.StakersCacheSize)
    s.cache.Delegations = s.makeCache(s.cfg.DelegationsCacheSize)
    s.cache.BlockDowntime = s.makeCache(256)
}

// Commit changes.
func (s *Store) Commit() error {
    // Flush trie on the DB
    err := s.table.EvmState.TrieDB().Cap(0)
    if err != nil {
        s.Log.Error("Failed to clean trie DB cache", "err", err)
        return err
    }

    return nil
}

// StateDB returns state database.
func (s *Store) StateDB(from common.Hash) (*state.StateDB, error) {
    return state.New(common.Hash(from), s.table.EvmState, nil)
}

// StateDB returns state database.
func (s *Store) IndexLogs(recs ...*types.Log) {
    err := s.table.EvmLogs.Push(recs...)
    if err != nil {
        s.Log.Crit("DB logs index", "err", err)
    }
}

func (s *Store) EvmTable() ethdb.Database {
    return s.table.Evm
}

func (s *Store) EvmLogs() *topicsdb.Index {
    return s.table.EvmLogs
}

/*
 * Utils:
 */

// set RLP value
func (s *Store) set(table kvdb.KeyValueStore, key []byte, val interface{}) {
    buf, err := rlp.EncodeToBytes(val)
    if err != nil {
        s.Log.Crit("Failed to encode rlp", "err", err)
    }

    if err := table.Put(key, buf); err != nil {
        s.Log.Crit("Failed to put key-value", "err", err)
    }
}

// get RLP value
func (s *Store) get(table kvdb.KeyValueStore, key []byte, to interface{}) interface{} {
    buf, err := table.Get(key)
    if err != nil {
        s.Log.Crit("Failed to get key-value", "err", err)
    }
    if buf == nil {
        return nil
    }

    err = rlp.DecodeBytes(buf, to)
    if err != nil {
        s.Log.Crit("Failed to decode rlp", "err", err, "size", len(buf))
    }
    return to
}

func (s *Store) has(table kvdb.KeyValueStore, key []byte) bool {
    res, err := table.Has(key)
    if err != nil {
        s.Log.Crit("Failed to get key", "err", err)
    }
    return res
}

func (s *Store) dropTable(it ethdb.Iterator, t kvdb.KeyValueStore) {
    keys := make([][]byte, 0, 500) // don't write during iteration

    for it.Next() {
        keys = append(keys, it.Key())
    }

    for i := range keys {
        err := t.Delete(keys[i])
        if err != nil {
            s.Log.Crit("Failed to erase key-value", "err", err)
        }
    }
}

func (s *Store) makeCache(size int) *lru.Cache {
    if size <= 0 {
        return nil
    }

    cache, err := lru.New(size)
    if err != nil {
        s.Log.Crit("Error create LRU cache", "err", err)
        return nil
    }
    return cache
}