neo-go/pkg/core/dao/cacheddao.go

154 lines
4.3 KiB
Go
Raw Normal View History

2020-04-07 09:41:12 +00:00
package dao
import (
"errors"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
2020-04-07 09:41:12 +00:00
// Cached is a data access object that mimics DAO, but has a write cache
// for accounts and read cache for contracts. These are the most frequently used
// objects in the storeBlock().
2020-04-07 09:41:12 +00:00
type Cached struct {
DAO
contracts map[util.Uint160]*state.Contract
2020-03-12 11:31:45 +00:00
balances map[util.Uint160]*state.NEP5Balances
transfers map[util.Uint160]map[uint32]*state.NEP5TransferLog
dropNEP5Cache bool
}
2020-04-07 09:41:12 +00:00
// NewCached returns new Cached wrapping around given backing store.
func NewCached(d DAO) *Cached {
ctrs := make(map[util.Uint160]*state.Contract)
2020-03-12 11:31:45 +00:00
balances := make(map[util.Uint160]*state.NEP5Balances)
transfers := make(map[util.Uint160]map[uint32]*state.NEP5TransferLog)
return &Cached{d.GetWrapped(), ctrs, balances, transfers, false}
}
2020-04-07 09:41:12 +00:00
// GetContractState returns contract state from cache or underlying store.
func (cd *Cached) GetContractState(hash util.Uint160) (*state.Contract, error) {
if cd.contracts[hash] != nil {
return cd.contracts[hash], nil
}
2020-04-07 09:41:12 +00:00
cs, err := cd.DAO.GetContractState(hash)
if err == nil {
cd.contracts[hash] = cs
}
return cs, err
}
// PutContractState puts given contract state into the given store.
2020-04-07 09:41:12 +00:00
func (cd *Cached) PutContractState(cs *state.Contract) error {
cd.contracts[cs.ScriptHash()] = cs
2020-04-07 09:41:12 +00:00
return cd.DAO.PutContractState(cs)
}
2020-04-07 09:41:12 +00:00
// DeleteContractState deletes given contract state in cache and backing store.
func (cd *Cached) DeleteContractState(hash util.Uint160) error {
cd.contracts[hash] = nil
2020-04-07 09:41:12 +00:00
return cd.DAO.DeleteContractState(hash)
}
2020-03-12 11:31:45 +00:00
// GetNEP5Balances retrieves NEP5Balances for the acc.
2020-04-07 09:41:12 +00:00
func (cd *Cached) GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error) {
2020-03-12 11:31:45 +00:00
if bs := cd.balances[acc]; bs != nil {
return bs, nil
}
2020-04-07 09:41:12 +00:00
return cd.DAO.GetNEP5Balances(acc)
2020-03-12 11:31:45 +00:00
}
// PutNEP5Balances saves NEP5Balances for the acc.
2020-04-07 09:41:12 +00:00
func (cd *Cached) PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error {
2020-03-12 11:31:45 +00:00
cd.balances[acc] = bs
return nil
}
// GetNEP5TransferLog retrieves NEP5TransferLog for the acc.
2020-04-07 09:41:12 +00:00
func (cd *Cached) GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error) {
ts := cd.transfers[acc]
if ts != nil && ts[index] != nil {
return ts[index], nil
}
2020-04-07 09:41:12 +00:00
return cd.DAO.GetNEP5TransferLog(acc, index)
}
// PutNEP5TransferLog saves NEP5TransferLog for the acc.
2020-04-07 09:41:12 +00:00
func (cd *Cached) PutNEP5TransferLog(acc util.Uint160, index uint32, bs *state.NEP5TransferLog) error {
ts := cd.transfers[acc]
if ts == nil {
ts = make(map[uint32]*state.NEP5TransferLog, 2)
cd.transfers[acc] = ts
}
ts[index] = bs
return nil
}
// AppendNEP5Transfer appends new transfer to a transfer event log.
2020-04-07 09:41:12 +00:00
func (cd *Cached) AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.NEP5Transfer) (bool, error) {
lg, err := cd.GetNEP5TransferLog(acc, index)
if err != nil {
return false, err
}
if err := lg.Append(tr); err != nil {
return false, err
}
return lg.Size() >= state.NEP5TransferBatchSize, cd.PutNEP5TransferLog(acc, index, lg)
}
// Persist flushes all the changes made into the (supposedly) persistent
// underlying store.
2020-04-07 09:41:12 +00:00
func (cd *Cached) Persist() (int, error) {
lowerCache, ok := cd.DAO.(*Cached)
// If the lower DAO is Cached, we only need to flush the MemCached DB.
// This actually breaks DAO interface incapsulation, but for our current
// usage scenario it should be good enough if cd doesn't modify object
// caches (accounts/contracts/etc) in any way.
if ok {
if cd.dropNEP5Cache {
lowerCache.balances = make(map[util.Uint160]*state.NEP5Balances)
}
2020-04-07 09:41:12 +00:00
var simpleCache *Simple
for simpleCache == nil {
2020-04-07 09:41:12 +00:00
simpleCache, ok = lowerCache.DAO.(*Simple)
if !ok {
2020-04-07 09:41:12 +00:00
lowerCache, ok = cd.DAO.(*Cached)
if !ok {
2020-04-07 09:41:12 +00:00
return 0, errors.New("unsupported lower DAO")
}
}
}
return simpleCache.Persist()
}
buf := io.NewBufBinWriter()
2020-03-12 11:31:45 +00:00
for acc, bs := range cd.balances {
2020-04-07 09:41:12 +00:00
err := cd.DAO.putNEP5Balances(acc, bs, buf)
2020-03-12 11:31:45 +00:00
if err != nil {
return 0, err
}
buf.Reset()
2020-03-12 11:31:45 +00:00
}
for acc, ts := range cd.transfers {
for ind, lg := range ts {
2020-04-07 09:41:12 +00:00
err := cd.DAO.PutNEP5TransferLog(acc, ind, lg)
if err != nil {
return 0, err
}
}
}
2020-04-07 09:41:12 +00:00
return cd.DAO.Persist()
}
2020-04-07 09:41:12 +00:00
// GetWrapped implements DAO interface.
func (cd *Cached) GetWrapped() DAO {
return &Cached{cd.DAO.GetWrapped(),
cd.contracts,
cd.balances,
cd.transfers,
false,
}
}