2020-04-07 09:41:12 +00:00
|
|
|
package dao
|
2019-12-13 15:43:46 +00:00
|
|
|
|
|
|
|
import (
|
2020-07-06 14:17:49 +00:00
|
|
|
"bytes"
|
2020-04-03 06:49:01 +00:00
|
|
|
"errors"
|
|
|
|
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
2020-07-06 14:17:49 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
2020-03-17 09:06:46 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2019-12-13 15:43:46 +00:00
|
|
|
)
|
|
|
|
|
2020-04-07 09:41:12 +00:00
|
|
|
// Cached is a data access object that mimics DAO, but has a write cache
|
2019-12-13 15:43:46 +00:00
|
|
|
// 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
|
2019-12-13 15:43:46 +00:00
|
|
|
accounts map[util.Uint160]*state.Account
|
|
|
|
contracts map[util.Uint160]*state.Contract
|
2020-03-12 11:31:45 +00:00
|
|
|
balances map[util.Uint160]*state.NEP5Balances
|
2020-03-12 11:49:59 +00:00
|
|
|
transfers map[util.Uint160]map[uint32]*state.NEP5TransferLog
|
2020-07-06 14:17:49 +00:00
|
|
|
|
|
|
|
dropNEP5Cache bool
|
2019-12-13 15:43:46 +00:00
|
|
|
}
|
|
|
|
|
2020-04-07 09:41:12 +00:00
|
|
|
// NewCached returns new Cached wrapping around given backing store.
|
|
|
|
func NewCached(d DAO) *Cached {
|
2019-12-13 15:43:46 +00:00
|
|
|
accs := make(map[util.Uint160]*state.Account)
|
|
|
|
ctrs := make(map[util.Uint160]*state.Contract)
|
2020-03-12 11:31:45 +00:00
|
|
|
balances := make(map[util.Uint160]*state.NEP5Balances)
|
2020-03-12 11:49:59 +00:00
|
|
|
transfers := make(map[util.Uint160]map[uint32]*state.NEP5TransferLog)
|
2020-07-06 14:17:49 +00:00
|
|
|
return &Cached{d.GetWrapped(), accs, ctrs, balances, transfers, false}
|
2019-12-13 15:43:46 +00:00
|
|
|
}
|
|
|
|
|
2020-04-07 09:41:12 +00:00
|
|
|
// GetAccountStateOrNew retrieves Account from cache or underlying store
|
2019-12-13 15:43:46 +00:00
|
|
|
// or creates a new one if it doesn't exist.
|
2020-04-07 09:41:12 +00:00
|
|
|
func (cd *Cached) GetAccountStateOrNew(hash util.Uint160) (*state.Account, error) {
|
2019-12-13 15:43:46 +00:00
|
|
|
if cd.accounts[hash] != nil {
|
|
|
|
return cd.accounts[hash], nil
|
|
|
|
}
|
2020-04-07 09:41:12 +00:00
|
|
|
return cd.DAO.GetAccountStateOrNew(hash)
|
2019-12-13 15:43:46 +00:00
|
|
|
}
|
|
|
|
|
2020-04-07 09:41:12 +00:00
|
|
|
// GetAccountState retrieves Account from cache or underlying store.
|
|
|
|
func (cd *Cached) GetAccountState(hash util.Uint160) (*state.Account, error) {
|
2019-12-13 15:43:46 +00:00
|
|
|
if cd.accounts[hash] != nil {
|
|
|
|
return cd.accounts[hash], nil
|
|
|
|
}
|
2020-04-07 09:41:12 +00:00
|
|
|
return cd.DAO.GetAccountState(hash)
|
2019-12-13 15:43:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// PutAccountState saves given Account in the cache.
|
2020-04-07 09:41:12 +00:00
|
|
|
func (cd *Cached) PutAccountState(as *state.Account) error {
|
2019-12-13 15:43:46 +00:00
|
|
|
cd.accounts[as.ScriptHash] = as
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
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) {
|
2019-12-13 15:43:46 +00:00
|
|
|
if cd.contracts[hash] != nil {
|
|
|
|
return cd.contracts[hash], nil
|
|
|
|
}
|
2020-04-07 09:41:12 +00:00
|
|
|
cs, err := cd.DAO.GetContractState(hash)
|
2019-12-13 15:43:46 +00:00
|
|
|
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 {
|
2019-12-13 15:43:46 +00:00
|
|
|
cd.contracts[cs.ScriptHash()] = cs
|
2020-04-07 09:41:12 +00:00
|
|
|
return cd.DAO.PutContractState(cs)
|
2019-12-13 15:43:46 +00:00
|
|
|
}
|
|
|
|
|
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 {
|
2019-12-13 15:43:46 +00:00
|
|
|
cd.contracts[hash] = nil
|
2020-04-07 09:41:12 +00:00
|
|
|
return cd.DAO.DeleteContractState(hash)
|
2019-12-13 15:43:46 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-03-12 11:49:59 +00:00
|
|
|
// 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) {
|
2020-03-12 11:49:59 +00:00
|
|
|
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)
|
2020-03-12 11:49:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2020-03-12 11:49:59 +00:00
|
|
|
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) {
|
2020-03-12 11:49:59 +00:00
|
|
|
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() >= nep5TransferBatchSize, cd.PutNEP5TransferLog(acc, index, lg)
|
|
|
|
}
|
|
|
|
|
2020-07-06 14:17:49 +00:00
|
|
|
// MigrateNEP5Balances migrates NEP5 balances from old contract to the new one.
|
|
|
|
func (cd *Cached) MigrateNEP5Balances(from, to util.Uint160) error {
|
|
|
|
var (
|
|
|
|
simpleDAO *Simple
|
|
|
|
cachedDAO = cd
|
|
|
|
ok bool
|
|
|
|
w = io.NewBufBinWriter()
|
|
|
|
)
|
|
|
|
for simpleDAO == nil {
|
|
|
|
simpleDAO, ok = cachedDAO.DAO.(*Simple)
|
|
|
|
if !ok {
|
|
|
|
cachedDAO, ok = cachedDAO.DAO.(*Cached)
|
|
|
|
if !ok {
|
|
|
|
panic("uknown DAO")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for acc, bs := range cd.balances {
|
|
|
|
err := simpleDAO.putNEP5Balances(acc, bs, w)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
w.Reset()
|
|
|
|
}
|
|
|
|
cd.dropNEP5Cache = true
|
|
|
|
var store = simpleDAO.Store
|
|
|
|
// Create another layer of cache because we can't change original storage
|
|
|
|
// while seeking.
|
|
|
|
var upStore = storage.NewMemCachedStore(store)
|
|
|
|
store.Seek([]byte{byte(storage.STNEP5Balances)}, func(k, v []byte) {
|
|
|
|
if !bytes.Contains(v, from[:]) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
bs := state.NewNEP5Balances()
|
|
|
|
reader := io.NewBinReaderFromBuf(v)
|
|
|
|
bs.DecodeBinary(reader)
|
|
|
|
if reader.Err != nil {
|
|
|
|
panic("bad nep5 balances")
|
|
|
|
}
|
|
|
|
tr, ok := bs.Trackers[from]
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
delete(bs.Trackers, from)
|
|
|
|
bs.Trackers[to] = tr
|
|
|
|
w.Reset()
|
|
|
|
bs.EncodeBinary(w.BinWriter)
|
|
|
|
if w.Err != nil {
|
|
|
|
panic("error on nep5 balance encoding")
|
|
|
|
}
|
|
|
|
err := upStore.Put(k, w.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
panic("can't put value in the DB")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
_, err := upStore.Persist()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-12-13 15:43:46 +00:00
|
|
|
// 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
|
2020-04-03 06:49:01 +00:00
|
|
|
// usage scenario it should be good enough if cd doesn't modify object
|
|
|
|
// caches (accounts/contracts/etc) in any way.
|
|
|
|
if ok {
|
2020-07-06 14:17:49 +00:00
|
|
|
if cd.dropNEP5Cache {
|
|
|
|
lowerCache.balances = make(map[util.Uint160]*state.NEP5Balances)
|
|
|
|
}
|
2020-04-07 09:41:12 +00:00
|
|
|
var simpleCache *Simple
|
2020-04-03 06:49:01 +00:00
|
|
|
for simpleCache == nil {
|
2020-04-07 09:41:12 +00:00
|
|
|
simpleCache, ok = lowerCache.DAO.(*Simple)
|
2020-04-03 06:49:01 +00:00
|
|
|
if !ok {
|
2020-04-07 09:41:12 +00:00
|
|
|
lowerCache, ok = cd.DAO.(*Cached)
|
2020-04-03 06:49:01 +00:00
|
|
|
if !ok {
|
2020-04-07 09:41:12 +00:00
|
|
|
return 0, errors.New("unsupported lower DAO")
|
2020-04-03 06:49:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return simpleCache.Persist()
|
|
|
|
}
|
2020-03-17 09:06:46 +00:00
|
|
|
buf := io.NewBufBinWriter()
|
|
|
|
|
2019-12-13 15:43:46 +00:00
|
|
|
for sc := range cd.accounts {
|
2020-04-07 09:41:12 +00:00
|
|
|
err := cd.DAO.putAccountState(cd.accounts[sc], buf)
|
2019-12-13 15:43:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2020-03-17 09:06:46 +00:00
|
|
|
buf.Reset()
|
2019-12-13 15:43:46 +00:00
|
|
|
}
|
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
|
|
|
|
}
|
2020-03-17 09:06:46 +00:00
|
|
|
buf.Reset()
|
2020-03-12 11:31:45 +00:00
|
|
|
}
|
2020-03-12 11:49:59 +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)
|
2020-03-12 11:49:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-07 09:41:12 +00:00
|
|
|
return cd.DAO.Persist()
|
2019-12-13 15:43:46 +00:00
|
|
|
}
|
2020-04-03 06:49:01 +00:00
|
|
|
|
2020-04-07 09:41:12 +00:00
|
|
|
// GetWrapped implements DAO interface.
|
|
|
|
func (cd *Cached) GetWrapped() DAO {
|
|
|
|
return &Cached{cd.DAO.GetWrapped(),
|
2020-04-03 06:49:01 +00:00
|
|
|
cd.accounts,
|
|
|
|
cd.contracts,
|
|
|
|
cd.balances,
|
|
|
|
cd.transfers,
|
2020-07-06 14:17:49 +00:00
|
|
|
false,
|
2020-04-03 06:49:01 +00:00
|
|
|
}
|
|
|
|
}
|