From 0e5d8d2e145152fd02a8ac2155b6efd504cff825 Mon Sep 17 00:00:00 2001 From: vilkris Date: Sat, 11 Jan 2025 22:10:38 +0200 Subject: [PATCH] Add chain cache https://github.com/zenon-network/go-zenon/pull/44 --- chain/account/keys.go | 3 + chain/cache.go | 164 ++++++++++++++++++++++++ chain/cache/account.go | 69 ++++++++++ chain/cache/extractor.go | 78 ++++++++++++ chain/cache/extractor_test.go | 83 ++++++++++++ chain/cache/keys.go | 6 + chain/cache/spork.go | 38 ++++++ chain/cache/storage/db.go | 161 ++++++++++++++++++++++++ chain/cache/storage/db_test.go | 63 ++++++++++ chain/cache/storage/keys.go | 5 + chain/cache/store.go | 107 ++++++++++++++++ chain/chain.go | 17 ++- chain/interface.go | 8 ++ chain/momentum/keys.go | 2 + chain/store/cache.go | 19 +++ chain/tests/cache_test.go | 208 +++++++++++++++++++++++++++++++ common/db/enable_delete.go | 18 ++- common/db/interfaces.go | 3 + common/db/leveldb.go | 18 ++- common/db/memdb.go | 2 +- common/db/memdb_test.go | 6 +- common/db/merged.go | 16 +++ common/db/subdb.go | 3 + common/db/versioned_db.go | 2 +- pillar/worker.go | 9 +- pillar/worker_momentum.go | 10 +- pillar/worker_updater.go | 2 +- protocol/broadcaster.go | 12 +- protocol/chain_bridge.go | 9 ++ protocol/interfaces.go | 2 +- rpc/api/embedded/plasma.go | 4 +- rpc/api/utils.go | 1 + verifier/account_block.go | 38 ++++-- vm/embedded/definition/plasma.go | 2 + vm/embedded/definition/spork.go | 8 +- vm/plasma.go | 6 +- vm/supervisor.go | 29 ++++- vm/vm.go | 27 ++-- vm/vm_context/account_context.go | 10 +- vm/vm_context/interfaces.go | 1 + vm/vm_context/spork.go | 8 +- zenon/mock/interfaces.go | 1 + zenon/mock/zenon.go | 32 ++--- zenon/mock/zenon_test.go | 4 + zenon/zenon.go | 4 +- 45 files changed, 1235 insertions(+), 83 deletions(-) create mode 100644 chain/cache.go create mode 100644 chain/cache/account.go create mode 100644 chain/cache/extractor.go create mode 100644 chain/cache/extractor_test.go create mode 100644 chain/cache/keys.go create mode 100644 chain/cache/spork.go create mode 100644 chain/cache/storage/db.go create mode 100644 chain/cache/storage/db_test.go create mode 100644 chain/cache/storage/keys.go create mode 100644 chain/cache/store.go create mode 100644 chain/store/cache.go create mode 100644 chain/tests/cache_test.go diff --git a/chain/account/keys.go b/chain/account/keys.go index d5f522f..2c5cb7f 100644 --- a/chain/account/keys.go +++ b/chain/account/keys.go @@ -6,6 +6,9 @@ var ( chainPlasmaKey = []byte{5} receivedBlockPrefix = []byte{6} sequencerLastReceivedKey = []byte{7} + + StorageKeyPrefix = storageKeyPrefix + ChainPlasmaKey = chainPlasmaKey ) const ( diff --git a/chain/cache.go b/chain/cache.go new file mode 100644 index 0000000..276ddf5 --- /dev/null +++ b/chain/cache.go @@ -0,0 +1,164 @@ +package chain + +import ( + "fmt" + "sync" + + "github.com/inconshreveable/log15" + "github.com/pkg/errors" + "github.com/zenon-network/go-zenon/chain/cache" + "github.com/zenon-network/go-zenon/chain/cache/storage" + "github.com/zenon-network/go-zenon/chain/nom" + "github.com/zenon-network/go-zenon/chain/store" + "github.com/zenon-network/go-zenon/common" + "github.com/zenon-network/go-zenon/common/db" + "github.com/zenon-network/go-zenon/common/types" +) + +type chainCache struct { + manager storage.CacheManager + log log15.Logger + changes sync.Mutex +} + +func (c *chainCache) getFrontierStore() store.Cache { + if db := c.manager.DB(); db == nil { + return nil + } else { + return cache.NewCacheStore(storage.GetFrontierIdentifier(db), c.manager) + } +} + +func (c *chainCache) GetFrontierCacheStore() store.Cache { + c.changes.Lock() + defer c.changes.Unlock() + return c.getFrontierStore() +} + +func (c *chainCache) GetCacheStore(identifier types.HashHeight) store.Cache { + c.changes.Lock() + defer c.changes.Unlock() + return cache.NewCacheStore(identifier, c.manager) +} + +func (c *chainCache) UpdateCache(insertLocker sync.Locker, detailed *nom.DetailedMomentum, changes db.Patch) error { + if insertLocker == nil { + return errors.Errorf("insertLocker can't be nil") + } + if changes == nil { + return errors.Errorf("changes can't be nil") + } + c.changes.Lock() + defer c.changes.Unlock() + if err := c.update(detailed, changes); err != nil { + return err + } + return nil +} + +func (c *chainCache) update(detailed *nom.DetailedMomentum, changes db.Patch) error { + momentum := detailed.Momentum + c.log.Info("inserting new momentum to chain cache", "identifier", momentum.Identifier()) + store := c.getFrontierStore() + store.ApplyMomentum(detailed, changes) + patch, err := store.Changes() + if err != nil { + return err + } + if err := c.manager.Add(momentum.Identifier(), patch); err != nil { + return err + } + return nil +} + +func (c *chainCache) RollbackCacheTo(insertLocker sync.Locker, identifier types.HashHeight) error { + if insertLocker == nil { + return errors.Errorf("insertLocker can't be nil") + } + c.changes.Lock() + defer c.changes.Unlock() + if err := c.rollbackTo(identifier); err != nil { + return err + } + return nil +} + +func (c *chainCache) rollbackTo(identifier types.HashHeight) error { + c.log.Info("rollbacking cache", "to-identifier", identifier) + frontier := c.getFrontierStore().Identifier() + + if identifier.Height > frontier.Height { + return errors.Errorf("can't rollback cache. Expected identifier height %v is greater than frontier %v", identifier, frontier) + } + + if frontier.Height-identifier.Height > uint64(storage.GetRollbackCacheSize()) { + return errors.Errorf("can't rollback cache. Target identifier %v is outside the rollback cache", identifier) + } + + for { + store := c.getFrontierStore() + frontier := store.Identifier() + if frontier.Height == identifier.Height { + break + } + c.log.Info("rollbacking", "momentum-identifier", frontier) + if err := c.manager.Pop(); err != nil { + return err + } + } + + return nil +} + +func (c *chainCache) Init(chainManager db.Manager, momentumStore store.Momentum) error { + c.changes.Lock() + defer c.changes.Unlock() + chainFrontier := db.GetFrontierIdentifier(chainManager.Frontier()) + cacheFrontier := storage.GetFrontierIdentifier(c.manager.DB()) + + if cacheFrontier.Height == chainFrontier.Height { + if cacheFrontier.Hash != chainFrontier.Hash { + return errors.Errorf("The cache's state is incorrect. " + + "You can fix the problem by removing the cache database manually.") + } + return nil + } + + if cacheFrontier.Height > chainFrontier.Height { + if err := c.rollbackTo(chainFrontier); err != nil { + return err + } + return nil + } + + if chainFrontier.Height-cacheFrontier.Height >= 100000 { + fmt.Println("Initializing cache: 0%") + } + + for i := cacheFrontier.Height + 1; i <= chainFrontier.Height; i++ { + momentum, err := momentumStore.GetMomentumByHeight(i) + if err != nil { + return err + } + changes := chainManager.GetPatch(momentum.Identifier()) + detailed, err := momentumStore.PrefetchMomentum(momentum) + if err != nil { + return err + } + if err := c.update(detailed, changes); err != nil { + return err + } + if i%100000 == 0 { + fmt.Printf("Initializing cache: %d%%\n", i*100/chainFrontier.Height) + } + } + + return nil +} + +func NewChainCache(cacheManager storage.CacheManager) *chainCache { + return &chainCache{ + manager: cacheManager, + log: common.ChainLogger.New("submodule", "chain-cache"), + } +} diff --git a/chain/cache/account.go b/chain/cache/account.go new file mode 100644 index 0000000..5e7c0f8 --- /dev/null +++ b/chain/cache/account.go @@ -0,0 +1,69 @@ +package cache + +import ( + "math/big" + + "github.com/syndtr/goleveldb/leveldb" + "github.com/zenon-network/go-zenon/chain/nom" + "github.com/zenon-network/go-zenon/common" + "github.com/zenon-network/go-zenon/common/types" +) + +var ( + fusedAmountKeyPrefix = []byte{0} + chainPlasmaKeyPrefix = []byte{1} +) + +func getFusedAmountKeyPrefix(address []byte) []byte { + return common.JoinBytes(accountCacheKeyPrefix, fusedAmountKeyPrefix, address) +} + +func getChainPlasmaKeyPrefix(address []byte) []byte { + return common.JoinBytes(accountCacheKeyPrefix, chainPlasmaKeyPrefix, address) +} + +func (cs *cacheStore) GetStakeBeneficialAmount(address types.Address) (*big.Int, error) { + value, err := cs.findValue(getFusedAmountKeyPrefix(address.Bytes())) + if err == leveldb.ErrNotFound { + return big.NewInt(0), nil + } + if err != nil { + return nil, err + } + return big.NewInt(0).SetBytes(value), nil +} + +func (cs *cacheStore) GetChainPlasma(address types.Address) (*big.Int, error) { + value, err := cs.findValue(getChainPlasmaKeyPrefix(address.Bytes())) + if err == leveldb.ErrNotFound { + return big.NewInt(0), nil + } + if err != nil { + return nil, err + } + return big.NewInt(0).SetBytes(value), nil +} + +func (cs *cacheStore) pruneAccountCache(blocks []*nom.AccountBlock) error { + for _, block := range blocks { + all := append([]*nom.AccountBlock{block}, block.DescendantBlocks...) + for _, b := range all { + prefix := getFusedAmountKeyPrefix(b.Address.Bytes()) + fusedPlasmaKeys, err := cs.findExpiredKeys(prefix, b.MomentumAcknowledged.Height) + if err != nil { + return err + } + + prefix = getChainPlasmaKeyPrefix(b.Address.Bytes()) + chainPlasmaKeys, err := cs.findExpiredKeys(prefix, b.MomentumAcknowledged.Height) + if err != nil { + return err + } + + for _, key := range append(fusedPlasmaKeys, chainPlasmaKeys...) { + cs.changes.Delete(key) + } + } + } + return nil +} diff --git a/chain/cache/extractor.go b/chain/cache/extractor.go new file mode 100644 index 0000000..b956717 --- /dev/null +++ b/chain/cache/extractor.go @@ -0,0 +1,78 @@ +package cache + +import ( + "bytes" + + "github.com/zenon-network/go-zenon/chain/account" + "github.com/zenon-network/go-zenon/chain/momentum" + "github.com/zenon-network/go-zenon/chain/store" + "github.com/zenon-network/go-zenon/common" + "github.com/zenon-network/go-zenon/common/db" + "github.com/zenon-network/go-zenon/common/types" + "github.com/zenon-network/go-zenon/vm/embedded/definition" +) + +type cacheExtractor struct { + cache store.Cache + height uint64 + patch db.Patch +} + +func (e *cacheExtractor) Put(key []byte, value []byte) { + if cacheKey := e.tryToGetCacheKey(key, value); cacheKey != nil { + e.patch.Put(cacheKey, value) + } +} + +func (e *cacheExtractor) Delete(key []byte) { + if cacheKey := e.tryToGetCacheKey(key, nil); cacheKey != nil { + e.patch.Put(cacheKey, []byte{}) + } +} + +func (e *cacheExtractor) tryToGetCacheKey(key []byte, value []byte) []byte { + if bytes.HasPrefix(key, momentum.AccountStorePrefix) { + key = bytes.TrimPrefix(key, momentum.AccountStorePrefix) + keyWithoutAddress := key[types.AddressSize:] + address, err := types.BytesToAddress(key[:types.AddressSize]) + common.DealWithErr(err) + + // Cache fused plasma + if address == types.PlasmaContract { + prefix := common.JoinBytes(account.StorageKeyPrefix, definition.FusedAmountKeyPrefix) + if bytes.HasPrefix(keyWithoutAddress, prefix) { + beneficiary := bytes.TrimPrefix(keyWithoutAddress, prefix) + return e.getHeightKey(getFusedAmountKeyPrefix(beneficiary)) + } + } + + // Cache sporks + if address == types.SporkContract { + prefix := common.JoinBytes(account.StorageKeyPrefix, []byte{definition.SporkInfoPrefix}) + if bytes.HasPrefix(keyWithoutAddress, prefix) { + sporkId := bytes.TrimPrefix(keyWithoutAddress, prefix) + return e.getHeightKey(getSporkInfoKeyPrefix(sporkId)) + } + } + + // Cache chain plasma + if bytes.HasPrefix(keyWithoutAddress, account.ChainPlasmaKey) { + if value == nil { + return e.getHeightKey(getChainPlasmaKeyPrefix(address.Bytes())) + } + // Verify that the state has changed + current, err := e.cache.GetChainPlasma(address) + common.DealWithErr(err) + if current.Cmp(common.BytesToBigInt(value)) == 0 { + return nil + } + return e.getHeightKey(getChainPlasmaKeyPrefix(address.Bytes())) + } + } + + return nil +} + +func (e *cacheExtractor) getHeightKey(prefix []byte) []byte { + return common.JoinBytes(prefix, common.Uint64ToBytes(e.height)) +} diff --git a/chain/cache/extractor_test.go b/chain/cache/extractor_test.go new file mode 100644 index 0000000..d16327c --- /dev/null +++ b/chain/cache/extractor_test.go @@ -0,0 +1,83 @@ +package cache + +import ( + "encoding/hex" + "fmt" + "testing" + + "github.com/zenon-network/go-zenon/chain/cache/storage" + "github.com/zenon-network/go-zenon/common" + "github.com/zenon-network/go-zenon/common/db" + "github.com/zenon-network/go-zenon/common/types" +) + +func getMockPatch() db.Patch { + patch := db.NewPatch() + fusedPlasmaKey, _ := hex.DecodeString("0301b3b6e5adcb4c1ff61be98c6318c6318c6318c60402aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + patch.Put(fusedPlasmaKey, []byte{1}) + sporkKey, _ := hex.DecodeString("0301b3b6e5adcb4d00bc76318c6318c6318c6318c60401aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + patch.Put(sporkKey, []byte{1}) + chainPlasmaKey, _ := hex.DecodeString("03aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa05") + patch.Put(chainPlasmaKey, []byte{1}) + return patch +} + +func getMockIdentifier(height uint64) types.HashHeight { + return types.HashHeight{ + Hash: types.NewHash([]byte(fmt.Sprint(height))), + Height: height, + } +} + +func TestExtractor(t *testing.T) { + dir := t.TempDir() + m := storage.NewCacheDBManager(dir) + defer m.Stop() + + identifier := types.ZeroHashHeight + cs := NewCacheStore(identifier, m) + + changes := getMockPatch() + extractor := &cacheExtractor{cache: cs, height: 1, patch: db.NewPatch()} + if err := changes.Replay(extractor); err != nil { + t.Fatal(err) + } + common.ExpectString(t, db.DebugPatch(extractor.patch), ` +0300aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000001 - 01 +0400aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000001 - 01 +0301aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000001 - 01 +`) + + identifier = getMockIdentifier(1) + m.Add(identifier, extractor.patch) + + common.ExpectString(t, db.DebugDB(m.DB()), ` +00 - 0a220a2067b176705b46206614219f47a05aee7ae6a3edbe850bbbe214c536b989aea4d21001 +0300aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000001 - 01 +0301aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000001 - 01 +0400aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000001 - 01 +`) + + cs = NewCacheStore(identifier, m) + changes = getMockPatch() + extractor = &cacheExtractor{cache: cs, height: 2, patch: db.NewPatch()} + if err := changes.Replay(extractor); err != nil { + t.Fatal(err) + } + common.ExpectString(t, db.DebugPatch(extractor.patch), ` +0300aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000002 - 01 +0400aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000002 - 01 +`) + + identifier = getMockIdentifier(2) + m.Add(identifier, extractor.patch) + + common.ExpectString(t, db.DebugDB(m.DB()), ` +00 - 0a220a20b1b1bd1ed240b1496c81ccf19ceccf2af6fd24fac10ae42023628abbe26873101002 +0300aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000001 - 01 +0300aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000002 - 01 +0301aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000001 - 01 +0400aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000001 - 01 +0400aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000002 - 01 +`) +} diff --git a/chain/cache/keys.go b/chain/cache/keys.go new file mode 100644 index 0000000..8b8d5bf --- /dev/null +++ b/chain/cache/keys.go @@ -0,0 +1,6 @@ +package cache + +var ( + accountCacheKeyPrefix = []byte{3} + sporkCacheKeyPrefix = []byte{4} +) diff --git a/chain/cache/spork.go b/chain/cache/spork.go new file mode 100644 index 0000000..f937eb2 --- /dev/null +++ b/chain/cache/spork.go @@ -0,0 +1,38 @@ +package cache + +import ( + "github.com/zenon-network/go-zenon/common" + "github.com/zenon-network/go-zenon/common/types" + "github.com/zenon-network/go-zenon/vm/embedded/definition" +) + +var ( + sporkInfoKeyPrefix = []byte{0} +) + +func getSporkInfoKeyPrefix(id []byte) []byte { + return common.JoinBytes(sporkCacheKeyPrefix, sporkInfoKeyPrefix, id) +} + +func (cs *cacheStore) IsSporkActive(implemented *types.ImplementedSpork) (bool, error) { + identifier := cs.Identifier() + if identifier.Height == 1 { + return false, nil + } + + data, err := cs.findValue(getSporkInfoKeyPrefix(implemented.SporkId.Bytes())) + if err != nil { + return false, err + } + + if len(data) == 0 { + return false, nil + } + + spork := definition.ParseSporkInfo(data) + if spork.Activated && spork.EnforcementHeight <= identifier.Height && spork.Id == implemented.SporkId { + return true, nil + } + + return false, nil +} diff --git a/chain/cache/storage/db.go b/chain/cache/storage/db.go new file mode 100644 index 0000000..fae764b --- /dev/null +++ b/chain/cache/storage/db.go @@ -0,0 +1,161 @@ +package storage + +import ( + "path" + "runtime" + "sync" + + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/opt" + "github.com/syndtr/goleveldb/leveldb/util" + "github.com/zenon-network/go-zenon/common" + "github.com/zenon-network/go-zenon/common/db" + "github.com/zenon-network/go-zenon/common/types" +) + +const ( + rollbackCacheSize = 100 +) + +var ( + storageByte = []byte{85} + rollbackByte = []byte{119} +) + +func getConsensusOpenFilesCacheCapacity() int { + switch runtime.GOOS { + case "darwin": + return 20 + case "windows": + return 200 + default: + return 200 + } +} + +type CacheManager interface { + DB() db.DB + + Add(types.HashHeight, db.Patch) error + Pop() error + + Stop() error +} + +type cacheManager struct { + ldb *leveldb.DB + changes sync.Mutex + stopped bool +} + +func NewCacheDBManager(dataDir string) CacheManager { + opts := &opt.Options{OpenFilesCacheCapacity: getConsensusOpenFilesCacheCapacity()} + db, err := leveldb.OpenFile(path.Join(dataDir, "cache"), opts) + common.DealWithErr(err) + return &cacheManager{ + ldb: db, + } +} + +func GetRollbackCacheSize() int { + return rollbackCacheSize +} + +func GetFrontierIdentifier(db db.DB) types.HashHeight { + data, err := db.Get(frontierIdentifierKey) + if err == leveldb.ErrNotFound { + return types.ZeroHashHeight + } + common.DealWithErr(err) + hh, err := types.DeserializeHashHeight(data) + common.DealWithErr(err) + return *hh +} + +func (m *cacheManager) DB() db.DB { + m.changes.Lock() + defer m.changes.Unlock() + if m.stopped { + return nil + } + return db.NewLevelDBWrapper(m.ldb).Subset(storageByte) +} + +func (m *cacheManager) Add(identifier types.HashHeight, patch db.Patch) error { + temp := db.NewMemDB() + if err := temp.Put(frontierIdentifierKey, identifier.Serialize()); err != nil { + return err + } + frontierPatch, err := temp.Changes() + if err != nil { + return err + } + if err := frontierPatch.Replay(patch); err != nil { + return err + } + rollbackPatch := db.RollbackPatch(m.DB(), patch) + + m.changes.Lock() + defer m.changes.Unlock() + + if err := m.ldb.Put(common.JoinBytes(rollbackByte, common.Uint64ToBytes(identifier.Height)), rollbackPatch.Dump(), nil); err != nil { + return err + } + if identifier.Height > rollbackCacheSize { + if err := m.ldb.Delete(common.JoinBytes(rollbackByte, common.Uint64ToBytes(identifier.Height-rollbackCacheSize)), nil); err != nil { + return err + } + } + if err := db.ApplyPatch(db.NewLevelDBWrapperWithFullDelete(m.ldb).Subset(storageByte), patch); err != nil { + return err + } + // Compact the db manually since the automatic compaction mechanism causes performance issues when throughput increases. + if identifier.Height%100 == 0 { + m.ldb.CompactRange(*util.BytesPrefix([]byte{})) + } + return nil +} + +func (m *cacheManager) Pop() error { + frontierIdentifier := GetFrontierIdentifier(m.DB()) + rollbackPatch, err := m.getRollback(frontierIdentifier.Height) + if err != nil { + return err + } + + m.changes.Lock() + defer m.changes.Unlock() + + if err := db.ApplyPatch(db.NewLevelDBWrapperWithFullDelete(m.ldb).Subset(storageByte), rollbackPatch); err != nil { + return err + } + if err := m.ldb.Delete(common.JoinBytes(rollbackByte, common.Uint64ToBytes(frontierIdentifier.Height)), nil); err != nil { + return err + } + return nil +} + +func (m *cacheManager) Stop() error { + m.changes.Lock() + defer m.changes.Unlock() + if err := m.ldb.Close(); err != nil { + return err + } + m.stopped = true + m.ldb = nil + return nil +} + +func (m *cacheManager) getRollback(height uint64) (db.Patch, error) { + snapshot, _ := m.ldb.GetSnapshot() + value, err := snapshot.Get(common.JoinBytes(rollbackByte, common.Uint64ToBytes(height)), nil) + if err != nil { + return nil, err + } + + patch, err := db.NewPatchFromDump(value) + if err != nil { + return nil, err + } + return patch, nil +} diff --git a/chain/cache/storage/db_test.go b/chain/cache/storage/db_test.go new file mode 100644 index 0000000..144562e --- /dev/null +++ b/chain/cache/storage/db_test.go @@ -0,0 +1,63 @@ +package storage + +import ( + "fmt" + "testing" + + "github.com/zenon-network/go-zenon/common" + "github.com/zenon-network/go-zenon/common/db" + "github.com/zenon-network/go-zenon/common/types" +) + +func getMockPatch(value []byte) db.Patch { + patch := db.NewPatch() + patch.Put(value, value) + return patch +} + +func getMockIdentifier(height uint64) types.HashHeight { + return types.HashHeight{ + Hash: types.NewHash([]byte(fmt.Sprint(height))), + Height: height, + } +} + +func TestPop(t *testing.T) { + dir := t.TempDir() + m := NewCacheDBManager(dir) + defer m.Stop() + + for i := 1; i <= 10; i++ { + m.Add(getMockIdentifier(uint64(i)), getMockPatch([]byte{byte(i)})) + } + + common.ExpectString(t, db.DebugDB(m.DB()), ` +00 - 0a220a20dd121e36961a04627eacff629765dd3528471ed745c1e32222db4a8a5f3421c4100a +01 - 01 +02 - 02 +03 - 03 +04 - 04 +05 - 05 +06 - 06 +07 - 07 +08 - 08 +09 - 09 +0a - 0a +`) + + for i := 0; i < 5; i++ { + m.Pop() + } + + frontierIdentifier := GetFrontierIdentifier(m.DB()) + expectedIdentifier := getMockIdentifier(5) + common.Expect(t, frontierIdentifier, expectedIdentifier) + common.ExpectString(t, db.DebugDB(m.DB()), ` +00 - 0a220a2086bc56fc56af4c3cde021282f6b727ee9f90dd636e0b0c712a85d416c75e652d1005 +01 - 01 +02 - 02 +03 - 03 +04 - 04 +05 - 05 +`) +} diff --git a/chain/cache/storage/keys.go b/chain/cache/storage/keys.go new file mode 100644 index 0000000..1dabaf3 --- /dev/null +++ b/chain/cache/storage/keys.go @@ -0,0 +1,5 @@ +package storage + +var ( + frontierIdentifierKey = []byte{0} +) diff --git a/chain/cache/store.go b/chain/cache/store.go new file mode 100644 index 0000000..592612b --- /dev/null +++ b/chain/cache/store.go @@ -0,0 +1,107 @@ +package cache + +import ( + "github.com/zenon-network/go-zenon/chain/cache/storage" + "github.com/zenon-network/go-zenon/chain/nom" + "github.com/zenon-network/go-zenon/chain/store" + "github.com/zenon-network/go-zenon/common" + "github.com/zenon-network/go-zenon/common/db" + "github.com/zenon-network/go-zenon/common/types" +) + +type cacheStore struct { + identifier types.HashHeight + changes db.DB + db db.DB +} + +func NewCacheStore(identifier types.HashHeight, manager storage.CacheManager) store.Cache { + if manager == nil { + panic("cache store can't operate with nil db manager") + } + frontier := storage.GetFrontierIdentifier(manager.DB()) + if identifier.Height > frontier.Height { + panic("cache store identifier height cannot be greater than db height") + } + return &cacheStore{ + identifier: identifier, + changes: db.NewMemDB(), + db: manager.DB(), + } +} + +func (cs *cacheStore) Identifier() types.HashHeight { + return cs.identifier +} + +func (cs *cacheStore) Changes() (db.Patch, error) { + return cs.changes.Changes() +} + +func (cs *cacheStore) ApplyMomentum(detailed *nom.DetailedMomentum, changes db.Patch) error { + if len(detailed.AccountBlocks) == 0 { + return nil + } + extractor := &cacheExtractor{cache: cs, height: detailed.Momentum.Height, patch: db.NewPatch()} + if err := changes.Replay(extractor); err != nil { + return err + } + if err := cs.changes.Apply(extractor.patch); err != nil { + return err + } + err := cs.pruneAccountCache(detailed.AccountBlocks) + return err +} + +func (cs *cacheStore) findValue(prefix []byte) ([]byte, error) { + iterator := cs.db.NewIterator(prefix) + defer iterator.Release() + + if !iterator.Last() { + return []byte{}, nil + } + + for { + if getKeyHeight(iterator.Key()) <= cs.identifier.Height { + return iterator.Value(), nil + } + if !iterator.Prev() { + if iterator.Error() != nil { + return nil, iterator.Error() + } + return []byte{}, nil + } + } +} + +func (cs *cacheStore) findExpiredKeys(prefix []byte, validHeight uint64) ([][]byte, error) { + iterator := cs.db.NewIterator(prefix) + defer iterator.Release() + + keys := [][]byte{} + for { + if !iterator.Next() { + if iterator.Error() != nil { + return nil, iterator.Error() + } + break + } + if getKeyHeight(iterator.Key()) > validHeight { + break + } + key := make([]byte, len(iterator.Key())) + copy(key, iterator.Key()) + keys = append(keys, key) + } + + // Remove key of the current state, so that it's not returned as expired + if len(keys) > 0 { + keys = keys[:len(keys)-1] + } + return keys, nil +} + +func getKeyHeight(key []byte) uint64 { + const uint64Size = 8 + return common.BytesToUint64(key[len(key)-uint64Size:]) +} diff --git a/chain/chain.go b/chain/chain.go index 3786aa0..b2a4f7b 100644 --- a/chain/chain.go +++ b/chain/chain.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" + "github.com/zenon-network/go-zenon/chain/cache/storage" "github.com/zenon-network/go-zenon/chain/store" "github.com/zenon-network/go-zenon/common" "github.com/zenon-network/go-zenon/common/db" @@ -24,20 +25,25 @@ type chain struct { *accountPool *momentumPool *momentumEventManager + *chainCache chainManager db.Manager + cacheManager storage.CacheManager insert sync.Mutex } -func NewChain(chainManager db.Manager, genesis store.Genesis) *chain { +func NewChain(chainManager db.Manager, cacheManager storage.CacheManager, genesis store.Genesis) *chain { momentumPool := NewMomentumPool(chainManager, genesis) + cache := NewChainCache(cacheManager) return &chain{ log: common.ChainLogger, Genesis: genesis, accountPool: newAccountPool(momentumPool), momentumPool: momentumPool, momentumEventManager: momentumPool.momentumEventManager, + chainCache: cache, chainManager: chainManager, + cacheManager: cacheManager, } } @@ -59,6 +65,11 @@ func (c *chain) Init() error { if err != nil { return err } + + if err := c.chainCache.Init(c.chainManager, frontierStore); err != nil { + return err + } + fmt.Printf("Initialized NoM. Height: %v, Hash: %v\n", frontier.Height, frontier.Hash) c.log.Info("initialized nom", "identifier", frontier.Identifier()) @@ -94,6 +105,10 @@ func (c *chain) Stop() error { c.UnRegister(c.accountPool) + if err := c.cacheManager.Stop(); err != nil { + return err + } + return c.chainManager.Stop() } diff --git a/chain/interface.go b/chain/interface.go index 3f40416..242d00c 100644 --- a/chain/interface.go +++ b/chain/interface.go @@ -23,6 +23,7 @@ type Chain interface { AccountPool MomentumPool MomentumEventManager + ChainCache } type MomentumEventListener interface { @@ -63,3 +64,10 @@ type AccountPool interface { GetAllUncommittedAccountBlocks() []*nom.AccountBlock GetUncommittedAccountBlocksByAddress(address types.Address) []*nom.AccountBlock } + +type ChainCache interface { + UpdateCache(insertLocker sync.Locker, detailed *nom.DetailedMomentum, changes db.Patch) error + RollbackCacheTo(insertLocker sync.Locker, identifier types.HashHeight) error + GetCacheStore(identifier types.HashHeight) store.Cache + GetFrontierCacheStore() store.Cache +} diff --git a/chain/momentum/keys.go b/chain/momentum/keys.go index 591cd09..75aec12 100644 --- a/chain/momentum/keys.go +++ b/chain/momentum/keys.go @@ -8,4 +8,6 @@ var ( blockConfirmationHeightPrefix = []byte{5} accountZNNBalancePrefix = []byte{8} accountHeaderByHashPrefix = []byte{9} + + AccountStorePrefix = accountStorePrefix ) diff --git a/chain/store/cache.go b/chain/store/cache.go new file mode 100644 index 0000000..c2d84fa --- /dev/null +++ b/chain/store/cache.go @@ -0,0 +1,19 @@ +package store + +import ( + "math/big" + + "github.com/zenon-network/go-zenon/chain/nom" + "github.com/zenon-network/go-zenon/common/db" + "github.com/zenon-network/go-zenon/common/types" +) + +type Cache interface { + Identifier() types.HashHeight + GetStakeBeneficialAmount(types.Address) (*big.Int, error) + GetChainPlasma(types.Address) (*big.Int, error) + IsSporkActive(*types.ImplementedSpork) (bool, error) + + ApplyMomentum(*nom.DetailedMomentum, db.Patch) error + Changes() (db.Patch, error) +} diff --git a/chain/tests/cache_test.go b/chain/tests/cache_test.go new file mode 100644 index 0000000..8274f90 --- /dev/null +++ b/chain/tests/cache_test.go @@ -0,0 +1,208 @@ +package tests + +import ( + "math/big" + "testing" + + g "github.com/zenon-network/go-zenon/chain/genesis/mock" + "github.com/zenon-network/go-zenon/chain/nom" + "github.com/zenon-network/go-zenon/common" + "github.com/zenon-network/go-zenon/common/types" + "github.com/zenon-network/go-zenon/rpc/api" + "github.com/zenon-network/go-zenon/rpc/api/embedded" + "github.com/zenon-network/go-zenon/vm/constants" + "github.com/zenon-network/go-zenon/vm/embedded/definition" + "github.com/zenon-network/go-zenon/zenon/mock" +) + +func activateSpork(z mock.MockZenon) { + sporkAPI := embedded.NewSporkApi(z) + z.InsertSendBlock(&nom.AccountBlock{ + Address: g.Spork.Address, + ToAddress: types.SporkContract, + Data: definition.ABISpork.PackMethodPanic(definition.SporkCreateMethodName, + "spork-accelerator", // name + "activate spork for accelerator", // description + ), + }, nil, mock.SkipVmChanges) + z.InsertNewMomentum() + + sporkList, _ := sporkAPI.GetAll(0, 10) + id := sporkList.List[0].Id + + z.InsertSendBlock(&nom.AccountBlock{ + Address: g.Spork.Address, + ToAddress: types.SporkContract, + Data: definition.ABISpork.PackMethodPanic(definition.SporkActivateMethodName, + id, // id + ), + }, nil, mock.SkipVmChanges) + z.InsertNewMomentum() + types.AcceleratorSpork.SporkId = id + types.ImplementedSporksMap[id] = true + z.InsertMomentumsTo(20) +} + +func TestCache_ChainPlasma(t *testing.T) { + z := mock.NewMockZenon(t) + defer z.StopPanic() + ledgerApi := api.NewLedgerApi(z) + + z.InsertSendBlock(&nom.AccountBlock{ + Address: g.User1.Address, + ToAddress: g.User6.Address, + TokenStandard: types.ZnnTokenStandard, + Amount: big.NewInt(10 * g.Zexp), + }, nil, mock.SkipVmChanges) + z.InsertNewMomentum() + + momentums, err := ledgerApi.GetMomentumsByHeight(1, 2) + common.FailIfErr(t, err) + + z.InsertSendBlock(&nom.AccountBlock{ + Address: g.User1.Address, + ToAddress: g.User6.Address, + TokenStandard: types.ZnnTokenStandard, + Amount: big.NewInt(10 * g.Zexp), + MomentumAcknowledged: momentums.List[0].Identifier(), + }, nil, mock.SkipVmChanges) + z.InsertNewMomentum() + + store := z.Chain().GetFrontierCacheStore() + current, err := store.GetChainPlasma(g.User1.Address) + common.FailIfErr(t, err) + common.ExpectAmount(t, current, big.NewInt(42000)) + + store = z.Chain().GetCacheStore(momentums.List[1].Identifier()) + current, err = store.GetChainPlasma(g.User1.Address) + common.FailIfErr(t, err) + common.ExpectAmount(t, current, big.NewInt(21000)) + + store = z.Chain().GetCacheStore(momentums.List[0].Identifier()) + current, err = store.GetChainPlasma(g.User1.Address) + common.FailIfErr(t, err) + common.ExpectAmount(t, current, common.Big0) +} + +func TestCache_Spork(t *testing.T) { + z := mock.NewMockZenon(t) + defer z.StopPanic() + ledgerApi := api.NewLedgerApi(z) + + activateSpork(z) + + momentums, err := ledgerApi.GetMomentumsByHeight(1, 20) + common.FailIfErr(t, err) + + store := z.Chain().GetCacheStore(momentums.List[0].Identifier()) + isActive, err := store.IsSporkActive(types.AcceleratorSpork) + common.FailIfErr(t, err) + common.Expect(t, isActive, false) + + store = z.Chain().GetCacheStore(momentums.List[7].Identifier()) + isActive, err = store.IsSporkActive(types.AcceleratorSpork) + common.FailIfErr(t, err) + common.Expect(t, isActive, false) + + store = z.Chain().GetCacheStore(momentums.List[8].Identifier()) + isActive, err = store.IsSporkActive(types.AcceleratorSpork) + common.FailIfErr(t, err) + common.Expect(t, isActive, true) + + store = z.Chain().GetCacheStore(momentums.List[19].Identifier()) + isActive, err = store.IsSporkActive(types.AcceleratorSpork) + common.FailIfErr(t, err) + common.Expect(t, isActive, true) +} + +func TestCache_FusedPlasma(t *testing.T) { + z := mock.NewMockZenon(t) + defer z.StopPanic() + ledgerApi := api.NewLedgerApi(z) + + constants.FuseExpiration = 100 + + defer z.CallContract(&nom.AccountBlock{ + Address: g.User1.Address, + ToAddress: types.PlasmaContract, + Data: definition.ABIPlasma.PackMethodPanic(definition.FuseMethodName, g.User6.Address), + TokenStandard: types.QsrTokenStandard, + Amount: big.NewInt(10 * g.Zexp), + }).Error(t, nil) + + z.InsertMomentumsTo(101) + + defer z.CallContract(&nom.AccountBlock{ + Address: g.User1.Address, + ToAddress: types.PlasmaContract, + Data: definition.ABIPlasma.PackMethodPanic(definition.CancelFuseMethodName, types.HexToHashPanic("6fce867a507bf026e4299761b6dd7fa51d288fed75716adcbd71bd6d241fc7ee")), + }).Error(t, nil) + + z.InsertNewMomentum() + z.InsertNewMomentum() + + momentum, err := ledgerApi.GetMomentumsByHeight(1, 1) + common.FailIfErr(t, err) + + store := z.Chain().GetCacheStore(momentum.List[0].Identifier()) + amount, err := store.GetStakeBeneficialAmount(g.User6.Address) + common.FailIfErr(t, err) + common.ExpectAmount(t, amount, common.Big0) + + momentum, err = ledgerApi.GetMomentumsByHeight(3, 1) + common.FailIfErr(t, err) + + store = z.Chain().GetCacheStore(momentum.List[0].Identifier()) + amount, err = store.GetStakeBeneficialAmount(g.User6.Address) + common.FailIfErr(t, err) + common.ExpectAmount(t, amount, big.NewInt(10*g.Zexp)) + + momentum, err = ledgerApi.GetMomentumsByHeight(102, 1) + common.FailIfErr(t, err) + + store = z.Chain().GetCacheStore(momentum.List[0].Identifier()) + amount, err = store.GetStakeBeneficialAmount(g.User6.Address) + common.FailIfErr(t, err) + common.ExpectAmount(t, amount, big.NewInt(10*g.Zexp)) + + momentum, err = ledgerApi.GetMomentumsByHeight(103, 1) + common.FailIfErr(t, err) + + store = z.Chain().GetCacheStore(momentum.List[0].Identifier()) + amount, err = store.GetStakeBeneficialAmount(g.User6.Address) + common.FailIfErr(t, err) + common.ExpectAmount(t, amount, common.Big0) +} + +func TestCache_Rollback(t *testing.T) { + z := mock.NewMockZenon(t) + defer z.StopPanic() + + z.InsertMomentumsTo(200) + + frontier := z.Chain().GetFrontierCacheStore().Identifier() + common.Expect(t, frontier.Height, 200) + + momentum, err := z.Chain().GetFrontierMomentumStore().GetMomentumByHeight(99) + common.FailIfErr(t, err) + + insert := z.Chain().AcquireInsert("") + err = z.Chain().RollbackCacheTo(insert, momentum.Identifier()) + insert.Unlock() + + // Expect rollback to fail when trying to rollback more than rollbackCacheSize + common.ExpectTrue(t, err != nil) + frontier = z.Chain().GetFrontierCacheStore().Identifier() + common.Expect(t, frontier.Height, 200) + + momentum, err = z.Chain().GetFrontierMomentumStore().GetMomentumByHeight(100) + common.FailIfErr(t, err) + + insert = z.Chain().AcquireInsert("") + err = z.Chain().RollbackCacheTo(insert, momentum.Identifier()) + insert.Unlock() + + common.ExpectTrue(t, err == nil) + frontier = z.Chain().GetFrontierCacheStore().Identifier() + common.Expect(t, frontier.Height, 100) +} diff --git a/common/db/enable_delete.go b/common/db/enable_delete.go index 5f115dd..c00a5f8 100644 --- a/common/db/enable_delete.go +++ b/common/db/enable_delete.go @@ -26,7 +26,8 @@ func (p *enableDeletePatch) Delete(key []byte) { } type enableDeleteDB struct { - db db + db db + fullDelete bool } func (d *enableDeleteDB) Has(key []byte) (bool, error) { @@ -55,7 +56,11 @@ func (d *enableDeleteDB) Put(key, value []byte) error { return d.db.Put(key, common.JoinBytes(existsByte, value)) } func (d *enableDeleteDB) Delete(key []byte) error { - return d.db.Put(key, []byte{}) + if d.fullDelete { + return d.db.Delete(key) + } else { + return d.db.Put(key, []byte{}) + } } func (d *enableDeleteDB) NewIterator(prefix []byte) StorageIterator { return newEnableDeleteIterator(d.db.NewIterator(prefix)) @@ -76,7 +81,7 @@ func (d *enableDeleteDB) Changes() (Patch, error) { return edp.p, nil } func (d *enableDeleteDB) Snapshot() DB { - return enableDelete(newMergedDb([]db{newMemDBInternal(), d.db})) + return enableDelete(newMergedDb([]db{newMemDBInternal(), d.db}), d.fullDelete) } func (d *enableDeleteDB) Apply(patch Patch) error { pa := &patchApplier{ @@ -89,7 +94,7 @@ func (d *enableDeleteDB) Apply(patch Patch) error { return pa.err } func (d *enableDeleteDB) Subset(prefix []byte) DB { - return enableDelete(newSubDB(prefix, d.db)) + return enableDelete(newSubDB(prefix, d.db), d.fullDelete) } type enableDeleteIterator struct { @@ -109,8 +114,9 @@ func newEnableDeleteIterator(iterator StorageIterator) StorageIterator { StorageIterator: iterator, } } -func enableDelete(db db) DB { +func enableDelete(db db, fullDelete bool) DB { return &enableDeleteDB{ - db: db, + db: db, + fullDelete: fullDelete, } } diff --git a/common/db/interfaces.go b/common/db/interfaces.go index 75108be..0cd9e76 100644 --- a/common/db/interfaces.go +++ b/common/db/interfaces.go @@ -28,6 +28,8 @@ type Transaction interface { type StorageIterator interface { Next() bool + Prev() bool + Last() bool Key() []byte Value() []byte @@ -53,6 +55,7 @@ type db interface { Get([]byte) ([]byte, error) Has([]byte) (bool, error) Put(key, value []byte) error + Delete(key []byte) error NewIterator(prefix []byte) StorageIterator diff --git a/common/db/leveldb.go b/common/db/leveldb.go index d22c1c0..e2c691a 100644 --- a/common/db/leveldb.go +++ b/common/db/leveldb.go @@ -41,6 +41,9 @@ func (ro *levelDBROWrapper) Has(key []byte) (bool, error) { func (ro *levelDBROWrapper) Put(key []byte, value []byte) error { panic("unimplemented") } +func (ro *levelDBROWrapper) Delete(key []byte) error { + panic("unimplemented") +} func (ro *levelDBROWrapper) changesInternal(prefix []byte) (Patch, error) { panic("unimplemented") } @@ -51,6 +54,7 @@ func (ro *levelDBROWrapper) NewIterator(prefix []byte) StorageIterator { type LevelDBLike interface { LevelDBLikeRO Put(key []byte, value []byte, wo *opt.WriteOptions) error + Delete(key []byte, wo *opt.WriteOptions) error } type levelDBWrapper struct { @@ -66,6 +70,9 @@ func (ldbw *levelDBWrapper) Has(key []byte) (bool, error) { func (ldbw *levelDBWrapper) Put(key, value []byte) error { return ldbw.db.Put(key, value, nil) } +func (ldbw *levelDBWrapper) Delete(key []byte) error { + return ldbw.db.Delete(key, nil) +} func (ldbw *levelDBWrapper) NewIterator(prefix []byte) StorageIterator { return ldbw.db.NewIterator(util.BytesPrefix(prefix), nil) } @@ -89,14 +96,21 @@ func NewLevelDBSnapshotWrapper(ldb *leveldb.Snapshot) DB { &levelDBROWrapper{ db: ldb, }, - })) + }), false) } func NewLevelDBWrapper(db *leveldb.DB) DB { return enableDelete( &levelDBWrapper{ db: db, - }) + }, false) +} + +func NewLevelDBWrapperWithFullDelete(db *leveldb.DB) DB { + return enableDelete( + &levelDBWrapper{ + db: db, + }, true) } func NewLevelDB(dirname string) (DB, *leveldb.DB) { diff --git a/common/db/memdb.go b/common/db/memdb.go index a8a5231..6de36db 100644 --- a/common/db/memdb.go +++ b/common/db/memdb.go @@ -44,5 +44,5 @@ func newMemDBInternal() db { } func NewMemDB() DB { - return enableDelete(newMemDBInternal()) + return enableDelete(newMemDBInternal(), false) } diff --git a/common/db/memdb_test.go b/common/db/memdb_test.go index ba16fe6..b1a6517 100644 --- a/common/db/memdb_test.go +++ b/common/db/memdb_test.go @@ -61,7 +61,7 @@ func TestMergedIterators(t *testing.T) { common.ExpectString(t, DebugDB(enableDelete(newMergedDb([]db{ db1, db2, - }))), ` + }), false)), ` 000101 - 102001 000102 - 000103 - 102013 @@ -72,7 +72,7 @@ func TestMergedIterators(t *testing.T) { 000302 - `) common.ExpectString(t, DebugDB(enableDelete(newMergedDb([]db{ db1, newSkipDelete(db2), - }))), ` + }), false)), ` 000101 - 102001 000102 - 000103 - 102013 @@ -82,7 +82,7 @@ func TestMergedIterators(t *testing.T) { 000302 - `) common.ExpectString(t, DebugDB(enableDelete(newSkipDelete(newMergedDb([]db{ db1, db2, - })))), ` + })), false)), ` 000101 - 102001 000103 - 102013 000201 - 102003 diff --git a/common/db/merged.go b/common/db/merged.go index 3884d29..fdc3cb4 100644 --- a/common/db/merged.go +++ b/common/db/merged.go @@ -38,6 +38,16 @@ func (u *mergedDB) Has(key []byte) (bool, error) { func (u *mergedDB) Put(key, value []byte) error { return u.dbs[0].Put(key, value) } +func (u *mergedDB) Delete(key []byte) error { + for _, db := range u.dbs { + if ok, err := db.Has(key); err != nil { + return err + } else if ok { + return db.Delete(key) + } + } + return nil +} func (u *mergedDB) NewIterator(prefix []byte) StorageIterator { iterators := make([]StorageIterator, len(u.dbs)) for i := range u.dbs { @@ -87,6 +97,12 @@ func newMergedIterator(iterators []StorageIterator) StorageIterator { func (mi *mergedIterator) Next() bool { return mi.step() } +func (mi *mergedIterator) Prev() bool { + panic("unimplemented") +} +func (mi *mergedIterator) Last() bool { + panic("unimplemented") +} func (mi *mergedIterator) Key() []byte { if mi.current == noCurrent || mi.err != nil { return nil diff --git a/common/db/subdb.go b/common/db/subdb.go index e868a52..964b90b 100644 --- a/common/db/subdb.go +++ b/common/db/subdb.go @@ -34,6 +34,9 @@ func (u *subDB) Has(key []byte) (bool, error) { func (u *subDB) Put(key, value []byte) error { return u.db.Put(common.JoinBytes(u.prefix, key), value) } +func (u *subDB) Delete(key []byte) error { + return u.db.Delete(common.JoinBytes(u.prefix, key)) +} func (u *subDB) NewIterator(prefix []byte) StorageIterator { return newSubIterator(len(u.prefix), u.db.NewIterator(common.JoinBytes(u.prefix, prefix))) } diff --git a/common/db/versioned_db.go b/common/db/versioned_db.go index b25523f..e3a661a 100644 --- a/common/db/versioned_db.go +++ b/common/db/versioned_db.go @@ -286,7 +286,7 @@ func (m *ldbManager) Get(identifier types.HashHeight) DB { newSubDB(frontierByte, newLevelDBSnapshotWrapper(snapshot)), })), }) - return enableDelete(u) + return enableDelete(u, false) } func (m *ldbManager) GetPatch(identifier types.HashHeight) Patch { m.changes.Lock() diff --git a/pillar/worker.go b/pillar/worker.go index 1979b77..08b4a33 100644 --- a/pillar/worker.go +++ b/pillar/worker.go @@ -94,7 +94,8 @@ func (w *worker) work(task common.TaskResolver, e consensus.ProducerEvent) { var momentumStore store.Momentum w.log.Info("producing momentum", "event", e) - momentum, err := w.generateMomentum(e) + transaction, detailed, err := w.generateMomentum(e) + if err != nil { w.log.Error("failed to generate momentum", "reason", err) return @@ -107,10 +108,10 @@ func (w *worker) work(task common.TaskResolver, e consensus.ProducerEvent) { return } if common.Clock.Now().After(e.StartTime.Add(3 * time.Second)) { - w.log.Error("do not broadcast own momentum", "identifier", momentum.Momentum.Identifier(), "reason", "too-late") + w.log.Error("do not broadcast own momentum", "identifier", transaction.Momentum.Identifier(), "reason", "too-late") } else { - w.log.Info("broadcasting own momentum", "identifier", momentum.Momentum.Identifier()) - w.broadcaster.CreateMomentum(momentum) + w.log.Info("broadcasting own momentum", "identifier", transaction.Momentum.Identifier()) + w.broadcaster.CreateMomentum(transaction, detailed) } if task.ShouldStop() { diff --git a/pillar/worker_momentum.go b/pillar/worker_momentum.go index 0adfdf2..b928895 100644 --- a/pillar/worker_momentum.go +++ b/pillar/worker_momentum.go @@ -5,7 +5,7 @@ import ( "github.com/zenon-network/go-zenon/consensus" ) -func (w *worker) generateMomentum(e consensus.ProducerEvent) (*nom.MomentumTransaction, error) { +func (w *worker) generateMomentum(e consensus.ProducerEvent) (*nom.MomentumTransaction, *nom.DetailedMomentum, error) { insert := w.chain.AcquireInsert("momentum-generator") defer insert.Unlock() @@ -14,7 +14,7 @@ func (w *worker) generateMomentum(e consensus.ProducerEvent) (*nom.MomentumTrans previousMomentum, err := store.GetFrontierMomentum() if err != nil { - return nil, err + return nil, nil, err } m := &nom.Momentum{ @@ -26,8 +26,10 @@ func (w *worker) generateMomentum(e consensus.ProducerEvent) (*nom.MomentumTrans Version: uint64(1), } m.EnsureCache() - return w.supervisor.GenerateMomentum(&nom.DetailedMomentum{ + detailed := &nom.DetailedMomentum{ Momentum: m, AccountBlocks: blocks, - }, w.coinbase.Signer) + } + transaction, err := w.supervisor.GenerateMomentum(detailed, w.coinbase.Signer) + return transaction, detailed, err } diff --git a/pillar/worker_updater.go b/pillar/worker_updater.go index bbcf09e..f805c1f 100644 --- a/pillar/worker_updater.go +++ b/pillar/worker_updater.go @@ -13,7 +13,7 @@ import ( func canPerformEmbeddedUpdate(momentumStore store.Momentum, pool chain.AccountPool, contract types.Address) error { store := pool.GetFrontierAccountStore(contract) - context := vm_context.NewAccountContext(momentumStore, store, nil) + context := vm_context.NewAccountContext(momentumStore, store, nil, nil) return implementation.CanPerformUpdate(context) } diff --git a/protocol/broadcaster.go b/protocol/broadcaster.go index b198494..ab441c8 100644 --- a/protocol/broadcaster.go +++ b/protocol/broadcaster.go @@ -28,10 +28,16 @@ func (b *broadcaster) SyncInfo() *SyncInfo { // CreateMomentum is called when our node created a momentum. // The momentum will be inserted in the chain and broadcasted. -func (b *broadcaster) CreateMomentum(momentumTransaction *nom.MomentumTransaction) { +func (b *broadcaster) CreateMomentum(momentumTransaction *nom.MomentumTransaction, detailed *nom.DetailedMomentum) { b.log.Info("creating own momentum", "identifier", momentumTransaction.Momentum.Identifier()) insert := b.chain.AcquireInsert(fmt.Sprintf("zenon - create momentum %v", momentumTransaction.Momentum.Identifier())) - err := b.chain.AddMomentumTransaction(insert, momentumTransaction) + err := b.chain.UpdateCache(insert, detailed, momentumTransaction.Changes) + if err != nil { + insert.Unlock() + b.log.Error("failed to insert own momentum to chain cache", "reason", err) + return + } + err = b.chain.AddMomentumTransaction(insert, momentumTransaction) insert.Unlock() if err != nil { b.log.Error("failed to insert own momentum", "reason", err) @@ -39,7 +45,7 @@ func (b *broadcaster) CreateMomentum(momentumTransaction *nom.MomentumTransactio } store := b.chain.GetFrontierMomentumStore() - detailed, err := store.PrefetchMomentum(momentumTransaction.Momentum) + detailed, err = store.PrefetchMomentum(momentumTransaction.Momentum) if err != nil { b.log.Error("failed to insert own momentum", "reason", err) return diff --git a/protocol/chain_bridge.go b/protocol/chain_bridge.go index 4e1d0c1..8187e64 100644 --- a/protocol/chain_bridge.go +++ b/protocol/chain_bridge.go @@ -176,6 +176,11 @@ func (c chainBridge) InsertChain(momentums []*nom.DetailedMomentum) (int, error) if err != nil { return 0, errors.Errorf("unable to rollback to %v. Reason:%v", target.Identifier(), err) } + + err = c.chain.RollbackCacheTo(insert, target.Identifier()) + if err != nil { + return 0, errors.Errorf("unable to rollback cache to %v. Reason:%v", target.Identifier(), err) + } } // Insert momentum now @@ -203,6 +208,10 @@ func (c chainBridge) InsertChain(momentums []*nom.DetailedMomentum) (int, error) if err != nil { return index + start, err } + if err := c.chain.UpdateCache(insert, detailed, transaction.Changes); err != nil { + log.Error("error while inserting cache", "reason", err, "momentum-identifier", detailed.Momentum.Identifier()) + return index + start, err + } if err := c.chain.AddMomentumTransaction(insert, transaction); err != nil { log.Error("error while inserting momentum", "reason", err, "momentum-identifier", detailed.Momentum.Identifier()) return index + start, err diff --git a/protocol/interfaces.go b/protocol/interfaces.go index 1f8a8ab..13c514d 100644 --- a/protocol/interfaces.go +++ b/protocol/interfaces.go @@ -51,6 +51,6 @@ type ChainBridge interface { type Broadcaster interface { SyncInfo() *SyncInfo - CreateMomentum(*nom.MomentumTransaction) + CreateMomentum(*nom.MomentumTransaction, *nom.DetailedMomentum) CreateAccountBlock(*nom.AccountBlockTransaction) } diff --git a/rpc/api/embedded/plasma.go b/rpc/api/embedded/plasma.go index a794d3f..ad7701a 100644 --- a/rpc/api/embedded/plasma.go +++ b/rpc/api/embedded/plasma.go @@ -176,7 +176,7 @@ func (a *PlasmaApi) Get(address types.Address) (*PlasmaInfo, error) { return nil, err } - available, err := vm.AvailablePlasma(context.MomentumStore(), context) + available, err := vm.AvailablePlasma(context.CacheStore(), context) if err != nil { return nil, err } @@ -250,7 +250,7 @@ func (a *PlasmaApi) GetRequiredPoWForAccountBlock(param GetRequiredParam) (*GetR return nil, errors.New("toAddress is nil") } - availablePlasma, err := vm.AvailablePlasma(context.MomentumStore(), context) + availablePlasma, err := vm.AvailablePlasma(context.CacheStore(), context) if err != nil { return nil, err } diff --git a/rpc/api/utils.go b/rpc/api/utils.go index f2e04b9..8cf9bc1 100644 --- a/rpc/api/utils.go +++ b/rpc/api/utils.go @@ -37,6 +37,7 @@ func GetFrontierContext(c chain.Chain, addr types.Address) (*nom.Momentum, vm_co context := vm_context.NewAccountContext( store, c.GetFrontierAccountStore(addr), + c.GetFrontierCacheStore(), nil, ) return frontier, context, nil diff --git a/verifier/account_block.go b/verifier/account_block.go index c4c01de..a17ca93 100644 --- a/verifier/account_block.go +++ b/verifier/account_block.go @@ -45,9 +45,13 @@ func (av *accountVerifier) getContext(block *nom.AccountBlock) (store.Account, s if block.MomentumAcknowledged.IsZero() { return nil, nil, ErrABMAMustNotBeZero } - momentumStore := av.chain.GetMomentumStore(block.MomentumAcknowledged) - if momentumStore == nil { - return nil, nil, ErrABMAMissing + + var momentumStore store.Momentum + if types.IsEmbeddedAddress(block.Address) { + momentumStore = av.chain.GetMomentumStore(block.MomentumAcknowledged) + if momentumStore == nil { + return nil, nil, ErrABMAMissing + } } accountStore := av.chain.GetAccountStore(block.Address, block.Previous()) @@ -90,6 +94,7 @@ func (av *accountVerifier) AccountBlock(block *nom.AccountBlock) error { block: block, accountStore: accountStore, momentumStore: momentumStore, + frontierStore: av.chain.GetFrontierMomentumStore(), }).all() } func (av *accountVerifier) AccountBlockTransaction(transaction *nom.AccountBlockTransaction) error { @@ -106,6 +111,7 @@ func (av *accountVerifier) AccountBlockTransaction(transaction *nom.AccountBlock transaction: transaction, accountStore: accountStore, momentumStore: momentumStore, + frontierStore: av.chain.GetFrontierMomentumStore(), }).all() } @@ -120,6 +126,7 @@ type accountBlockVerifier struct { block *nom.AccountBlock accountStore store.Account momentumStore store.Momentum + frontierStore store.Momentum } func (abv *accountBlockVerifier) all() error { @@ -165,8 +172,8 @@ func (abv *accountBlockVerifier) chainIdentifier() error { if abv.block.ChainIdentifier == 0 { return ErrMChainIdentifierMissing } - if abv.block.ChainIdentifier != abv.momentumStore.ChainIdentifier() { - return fmt.Errorf("%w - expected %v but received %v", ErrMChainIdentifierMismatch, abv.momentumStore.ChainIdentifier(), abv.block.ChainIdentifier) + if abv.block.ChainIdentifier != abv.frontierStore.ChainIdentifier() { + return fmt.Errorf("%w - expected %v but received %v", ErrMChainIdentifierMismatch, abv.frontierStore.ChainIdentifier(), abv.block.ChainIdentifier) } return nil } @@ -274,12 +281,15 @@ func (abv *accountBlockVerifier) previous() error { return nil } func (abv *accountBlockVerifier) momentumAcknowledged() error { - momentum, err := abv.momentumStore.GetFrontierMomentum() - if err != nil { - return InternalError(err) - } - if momentum.Identifier() != abv.block.MomentumAcknowledged { - return InternalError(errors.Errorf("impossible scenario. verifier momentum-store exists but frontier is different. Expected MomentumAcknowledged %v but got %v from MomentumStore", abv.block.MomentumAcknowledged, momentum.Identifier())) + if abv.momentumStore != nil { + momentum, err := abv.momentumStore.GetFrontierMomentum() + if err != nil { + return InternalError(err) + } + identifier := momentum.Identifier() + if identifier != abv.block.MomentumAcknowledged { + return InternalError(errors.Errorf("impossible scenario. momentum store exists but frontier is different. Expected MomentumAcknowledged %v but got %v from momentum store", abv.block.MomentumAcknowledged, identifier)) + } } // all checks are done by the parent @@ -295,7 +305,7 @@ func (abv *accountBlockVerifier) momentumAcknowledged() error { } } - height, err := abv.momentumStore.GetBlockConfirmationHeight(abv.block.FromBlockHash) + height, err := abv.frontierStore.GetBlockConfirmationHeight(abv.block.FromBlockHash) if err != nil { return InternalError(err) } @@ -324,7 +334,7 @@ func (abv *accountBlockVerifier) fromHash() error { } // check that from-hash is a valid hash - sendBlock, err := abv.momentumStore.GetAccountBlockByHash(abv.block.FromBlockHash) + sendBlock, err := abv.frontierStore.GetAccountBlockByHash(abv.block.FromBlockHash) if err != nil { return InternalError(err) } else if sendBlock == nil { @@ -365,6 +375,7 @@ type accountBlockTransactionVerifier struct { transaction *nom.AccountBlockTransaction accountStore store.Account momentumStore store.Momentum + frontierStore store.Momentum } func (abvt *accountBlockTransactionVerifier) all() error { @@ -445,6 +456,7 @@ func (abvt *accountBlockTransactionVerifier) descendantBlocks() error { block: dBlock, accountStore: abvt.accountStore, momentumStore: abvt.momentumStore, + frontierStore: abvt.frontierStore, }).all(); err != nil { return DescendantVerifyError(err) } diff --git a/vm/embedded/definition/plasma.go b/vm/embedded/definition/plasma.go index c934b59..a52765c 100644 --- a/vm/embedded/definition/plasma.go +++ b/vm/embedded/definition/plasma.go @@ -46,6 +46,8 @@ var ( fusionInfoKeyPrefix = []byte{1} fusedAmountKeyPrefix = []byte{2} + + FusedAmountKeyPrefix = fusedAmountKeyPrefix ) type FusionInfo struct { diff --git a/vm/embedded/definition/spork.go b/vm/embedded/definition/spork.go index 97c2e53..2307d65 100644 --- a/vm/embedded/definition/spork.go +++ b/vm/embedded/definition/spork.go @@ -38,6 +38,8 @@ var ( const ( _ byte = iota sporkInfoPrefix + + SporkInfoPrefix = sporkInfoPrefix ) type Spork struct { @@ -66,7 +68,7 @@ func (spork *Spork) Key() []byte { return common.JoinBytes([]byte{sporkInfoPrefix}, spork.Id.Bytes()) } -func parseSporkInfo(data []byte) *Spork { +func ParseSporkInfo(data []byte) *Spork { spork := new(Spork) ABISpork.UnpackVariablePanic(spork, sporkInfoVariableName, data) return spork @@ -81,7 +83,7 @@ func GetSporkInfoById(context db.DB, id types.Hash) *Spork { if len(data) == 0 { return nil } else { - return parseSporkInfo(data) + return ParseSporkInfo(data) } } func GetAllSporks(context db.DB) []*Spork { @@ -94,7 +96,7 @@ func GetAllSporks(context db.DB) []*Spork { common.DealWithErr(iterator.Error()) break } - spork := parseSporkInfo(iterator.Value()) + spork := ParseSporkInfo(iterator.Value()) sporks = append(sporks, spork) } return sporks diff --git a/vm/plasma.go b/vm/plasma.go index c13cb77..10ef73f 100644 --- a/vm/plasma.go +++ b/vm/plasma.go @@ -54,13 +54,13 @@ func FussedAmountToPlasma(amount *big.Int) uint64 { // *Takes* into consideration used plasma by unconfirmed blocks. // // Plasma equals to fusedPlasma - plasmaUsedByUnconfirmedBlocks -func AvailablePlasma(momentum store.Momentum, account store.Account) (uint64, error) { +func AvailablePlasma(cache store.Cache, account store.Account) (uint64, error) { address := *account.Address() - committed, err := momentum.GetAccountStore(address).GetChainPlasma() + committed, err := cache.GetChainPlasma(address) if err != nil { return 0, err } - fused, err := momentum.GetStakeBeneficialAmount(address) + fused, err := cache.GetStakeBeneficialAmount(address) if err != nil { return 0, err } diff --git a/vm/supervisor.go b/vm/supervisor.go index 61bde3b..9719645 100644 --- a/vm/supervisor.go +++ b/vm/supervisor.go @@ -45,8 +45,31 @@ func NewSupervisor(chain chain.Chain, consensus consensus.Consensus) *Supervisor } func (s *Supervisor) newBlockContext(block *nom.AccountBlock) vm_context.AccountVmContext { + if types.IsEmbeddedAddress(block.Address) { + return s.newEmbeddedBlockContext(block) + } else { + return s.newUserBlockContext(block) + } +} + +func (s *Supervisor) newUserBlockContext(block *nom.AccountBlock) vm_context.AccountVmContext { + accountStore := s.chain.GetAccountStore(block.Address, block.Previous()) + cacheStore := s.chain.GetCacheStore(block.MomentumAcknowledged) + if accountStore == nil { + panic(fmt.Sprintf("can't find accountStore for %v %v", block.Address, block.Previous())) + } + return vm_context.NewAccountContext( + nil, + accountStore, + cacheStore, + nil, + ) +} + +func (s *Supervisor) newEmbeddedBlockContext(block *nom.AccountBlock) vm_context.AccountVmContext { momentumStore := s.chain.GetMomentumStore(block.MomentumAcknowledged) accountStore := s.chain.GetAccountStore(block.Address, block.Previous()) + cacheStore := s.chain.GetCacheStore(block.MomentumAcknowledged) cache := s.consensus.FixedPillarReader(block.MomentumAcknowledged) if momentumStore == nil { panic(fmt.Sprintf("can't find momentumStore for %v", block.MomentumAcknowledged)) @@ -60,9 +83,11 @@ func (s *Supervisor) newBlockContext(block *nom.AccountBlock) vm_context.Account return vm_context.NewAccountContext( momentumStore, accountStore, + cacheStore, cache, ) } + func (s *Supervisor) newMomentumContext(momentum *nom.Momentum) vm_context.MomentumVMContext { return vm_context.NewMomentumVMContext( s.chain.GetMomentumStore(momentum.Previous()), @@ -129,7 +154,7 @@ func (s *Supervisor) GenerateAutoReceive(sendBlock *nom.AccountBlock) (*Contract if err := s.setBlockPlasma(context, template); err != nil { return nil, err } - vm := NewVM(context) + vm := NewVM(context, s.chain.GetFrontierMomentumStore()) block, methodErr, err := vm.generateEmbeddedReceive(template.FromBlockHash) if err := s.verifier.AccountBlock(block); err != nil { return nil, err @@ -212,7 +237,7 @@ func (s *Supervisor) applyBlock(block *nom.AccountBlock, signFunc SignFunc) (tra return nil, err } context := s.newBlockContext(block) - vm := NewVM(context) + vm := NewVM(context, s.chain.GetFrontierMomentumStore()) err := vm.applyBlock(block) if err != nil { return nil, err diff --git a/vm/vm.go b/vm/vm.go index 18e016f..673c11b 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -7,6 +7,7 @@ import ( "github.com/zenon-network/go-zenon/chain" "github.com/zenon-network/go-zenon/chain/nom" + "github.com/zenon-network/go-zenon/chain/store" "github.com/zenon-network/go-zenon/common" "github.com/zenon-network/go-zenon/common/db" "github.com/zenon-network/go-zenon/common/types" @@ -35,12 +36,14 @@ func errToStatus(err error) uint64 { } type VM struct { - context vm_context.AccountVmContext + context vm_context.AccountVmContext + frontierStore store.Momentum } -func NewVM(context vm_context.AccountVmContext) *VM { +func NewVM(context vm_context.AccountVmContext, frontierStore store.Momentum) *VM { return &VM{ - context: context, + context: context, + frontierStore: frontierStore, } } @@ -50,10 +53,14 @@ func enoughPlasma(context vm_context.AccountVmContext, block *nom.AccountBlock) return nil } - available, err := AvailablePlasma(context.MomentumStore(), context) - common.DealWithErr(err) - if available < block.FusedPlasma { - return constants.ErrNotEnoughPlasma + // Prevent potentially expensive database read operations by only + // checking available plasma for blocks with fused plasma + if block.FusedPlasma > 0 { + available, err := AvailablePlasma(context.CacheStore(), context) + common.DealWithErr(err) + if available < block.FusedPlasma { + return constants.ErrNotEnoughPlasma + } } powPlasma := DifficultyToPlasma(block.Difficulty) @@ -62,9 +69,11 @@ func enoughPlasma(context vm_context.AccountVmContext, block *nom.AccountBlock) return constants.ErrBlockPlasmaLimitReached } - block.BasePlasma, err = GetBasePlasmaForAccountBlock(context, block) + basePlasma, err := GetBasePlasmaForAccountBlock(context, block) common.DealWithErr(err) + block.BasePlasma = basePlasma + if block.TotalPlasma < block.BasePlasma { return constants.ErrNotEnoughTotalPlasma } @@ -139,7 +148,7 @@ func (vm *VM) applySend(block *nom.AccountBlock) error { return nil } func (vm *VM) applyReceive(block *nom.AccountBlock) error { - fromBlock, err := vm.context.MomentumStore().GetAccountBlockByHash(block.FromBlockHash) + fromBlock, err := vm.frontierStore.GetAccountBlockByHash(block.FromBlockHash) if err != nil { return err } diff --git a/vm/vm_context/account_context.go b/vm/vm_context/account_context.go index 7b92181..c607cfd 100644 --- a/vm/vm_context/account_context.go +++ b/vm/vm_context/account_context.go @@ -13,20 +13,26 @@ type accountVmContext struct { api.PillarReader store.Account momentumStore store.Momentum + cacheStore store.Cache } func (ctx *accountVmContext) MomentumStore() store.Momentum { return ctx.momentumStore } -func NewAccountContext(momentumStore store.Momentum, accountBlock store.Account, pillarReader api.PillarReader) AccountVmContext { +func (ctx *accountVmContext) CacheStore() store.Cache { + return ctx.cacheStore +} + +func NewAccountContext(momentumStore store.Momentum, accountBlock store.Account, cacheStore store.Cache, pillarReader api.PillarReader) AccountVmContext { return &accountVmContext{ momentumStore: momentumStore, Account: accountBlock, + cacheStore: cacheStore, PillarReader: pillarReader, } } func NewGenesisAccountContext(address types.Address) AccountVmContext { - return NewAccountContext(nil, account.NewAccountStore(address, db.NewMemDB()), nil) + return NewAccountContext(nil, account.NewAccountStore(address, db.NewMemDB()), nil, nil) } diff --git a/vm/vm_context/interfaces.go b/vm/vm_context/interfaces.go index e7ed506..c7596b1 100644 --- a/vm/vm_context/interfaces.go +++ b/vm/vm_context/interfaces.go @@ -12,6 +12,7 @@ import ( type AccountVmContext interface { api.PillarReader store.Account + CacheStore() store.Cache MomentumStore() store.Momentum // ====== State ====== diff --git a/vm/vm_context/spork.go b/vm/vm_context/spork.go index 8eeecd2..7211e2b 100644 --- a/vm/vm_context/spork.go +++ b/vm/vm_context/spork.go @@ -6,25 +6,25 @@ import ( ) func (ctx *accountVmContext) IsAcceleratorSporkEnforced() bool { - active, err := ctx.momentumStore.IsSporkActive(types.AcceleratorSpork) + active, err := ctx.cacheStore.IsSporkActive(types.AcceleratorSpork) common.DealWithErr(err) return active } func (ctx *accountVmContext) IsHtlcSporkEnforced() bool { - active, err := ctx.momentumStore.IsSporkActive(types.HtlcSpork) + active, err := ctx.cacheStore.IsSporkActive(types.HtlcSpork) common.DealWithErr(err) return active } func (ctx *accountVmContext) IsBridgeAndLiquiditySporkEnforced() bool { - active, err := ctx.momentumStore.IsSporkActive(types.BridgeAndLiquiditySpork) + active, err := ctx.cacheStore.IsSporkActive(types.BridgeAndLiquiditySpork) common.DealWithErr(err) return active } func (ctx *accountVmContext) IsNoPillarRegSporkEnforced() bool { - active, err := ctx.momentumStore.IsSporkActive(types.NoPillarRegSpork) + active, err := ctx.cacheStore.IsSporkActive(types.NoPillarRegSpork) common.DealWithErr(err) return active } diff --git a/zenon/mock/interfaces.go b/zenon/mock/interfaces.go index 43a7b9a..3d187a6 100644 --- a/zenon/mock/interfaces.go +++ b/zenon/mock/interfaces.go @@ -20,4 +20,5 @@ type MockZenon interface { SaveLogs(logger common.Logger) *common.Expecter ExpectBalance(address types.Address, standard types.ZenonTokenStandard, expected int64) + ExpectCacheFusedAmount(address types.Address, expected int64) } diff --git a/zenon/mock/zenon.go b/zenon/mock/zenon.go index 51485c0..8baf642 100644 --- a/zenon/mock/zenon.go +++ b/zenon/mock/zenon.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/zenon-network/go-zenon/chain" + cache "github.com/zenon-network/go-zenon/chain/cache/storage" "github.com/zenon-network/go-zenon/chain/genesis" g "github.com/zenon-network/go-zenon/chain/genesis/mock" "github.com/zenon-network/go-zenon/chain/nom" @@ -22,7 +23,6 @@ import ( "github.com/zenon-network/go-zenon/protocol" "github.com/zenon-network/go-zenon/verifier" "github.com/zenon-network/go-zenon/vm" - "github.com/zenon-network/go-zenon/vm/vm_context" "github.com/zenon-network/go-zenon/zenon" ) @@ -142,10 +142,14 @@ func (zenon *mockZenon) SyncInfo() *protocol.SyncInfo { func (zenon *mockZenon) SyncState() protocol.SyncState { return protocol.SyncDone } -func (zenon *mockZenon) CreateMomentum(momentumTransaction *nom.MomentumTransaction) { +func (zenon *mockZenon) CreateMomentum(momentumTransaction *nom.MomentumTransaction, detailed *nom.DetailedMomentum) { insert := zenon.chain.AcquireInsert("mock-zenon create-momentum") defer insert.Unlock() - err := zenon.chain.AddMomentumTransaction(insert, momentumTransaction) + err := zenon.chain.UpdateCache(insert, detailed, momentumTransaction.Changes) + if err != nil { + panic(fmt.Errorf("failed to insert own momentum to chain cache. reason:%w", err)) + } + err = zenon.chain.AddMomentumTransaction(insert, momentumTransaction) if err != nil { panic(fmt.Errorf("failed to insert own momentum. reason:%w", err)) } @@ -265,18 +269,6 @@ func (zenon *mockZenon) InsertReceiveBlock(fromHeader types.AccountHeader, templ return nil } -func (zenon *mockZenon) EmbeddedContext(address types.Address) vm_context.AccountVmContext { - momentumStore := zenon.chain.GetFrontierMomentumStore() - accountStore := zenon.chain.GetFrontierAccountStore(address) - - return vm_context.NewAccountContext( - momentumStore, - accountStore, - zenon.consensus.FixedPillarReader(momentumStore.Identifier()), - ) - -} - func (zenon *mockZenon) SaveLogs(logger common.Logger) *common.Expecter { return common.SaveLogs(logger) } @@ -288,6 +280,14 @@ func (zenon *mockZenon) ExpectBalance(address types.Address, standard types.Zeno } common.ExpectAmount(zenon.t, amount, big.NewInt(expected)) } +func (zenon *mockZenon) ExpectCacheFusedAmount(address types.Address, expected int64) { + amount, err := zenon.chain.GetFrontierCacheStore().GetStakeBeneficialAmount(address) + common.FailIfErr(zenon.t, err) + if amount == nil { + amount = big.NewInt(0) + } + common.ExpectAmount(zenon.t, amount, big.NewInt(expected)) +} // protocol @@ -366,7 +366,7 @@ func newMockZenon(t common.T, customEpochDuration time.Duration) MockZenon { common.SupervisorLogger.SetHandler(log15.LvlFilterHandler(log15.LvlError, log15.StderrHandler)) consensus.EpochDuration = customEpochDuration - ch := chain.NewChain(db.NewLevelDBManager(t.TempDir()), genesis.NewGenesis(g.EmbeddedGenesis)) + ch := chain.NewChain(db.NewLevelDBManager(t.TempDir()), cache.NewCacheDBManager(t.TempDir()), genesis.NewGenesis(g.EmbeddedGenesis)) cs := consensus.NewConsensus(db.NewMemDB(), ch, true) supervisor := vm.NewSupervisor(ch, cs) zenon := &mockZenon{ diff --git a/zenon/mock/zenon_test.go b/zenon/mock/zenon_test.go index d6d37e1..e6d3149 100644 --- a/zenon/mock/zenon_test.go +++ b/zenon/mock/zenon_test.go @@ -17,11 +17,15 @@ func TestStateGenesis(t *testing.T) { store := z.Chain().GetFrontierMomentumStore() common.ExpectBytes(t, store.Identifier().Hash.Bytes(), "0x0385d849ee33b94c8783288c148e3ae741c2ecec98b08b3f59d6bcc219168fe5") + cacheStore := z.Chain().GetFrontierCacheStore() + common.ExpectBytes(t, cacheStore.Identifier().Hash.Bytes(), "0x0385d849ee33b94c8783288c148e3ae741c2ecec98b08b3f59d6bcc219168fe5") + genesis, err := store.GetMomentumByHeight(1) common.FailIfErr(t, err) common.ExpectString(t, string(genesis.Data[0:43]), "This is the genesis config used for testing") z.ExpectBalance(g.User1.Address, types.ZnnTokenStandard, 12000*g.Zexp) + z.ExpectCacheFusedAmount(g.User1.Address, 10000*g.Zexp) } func TestStateProducer(t *testing.T) { diff --git a/zenon/zenon.go b/zenon/zenon.go index 6b48070..67e99c7 100644 --- a/zenon/zenon.go +++ b/zenon/zenon.go @@ -4,6 +4,7 @@ import ( "github.com/syndtr/goleveldb/leveldb" "github.com/zenon-network/go-zenon/chain" + cache "github.com/zenon-network/go-zenon/chain/cache/storage" "github.com/zenon-network/go-zenon/consensus" "github.com/zenon-network/go-zenon/pillar" "github.com/zenon-network/go-zenon/protocol" @@ -30,8 +31,7 @@ func NewZenon(cfg *Config) (Zenon, error) { z := &zenon{ config: cfg, } - - z.chain = chain.NewChain(cfg.NewDBManager("nom"), cfg.GenesisConfig) + z.chain = chain.NewChain(cfg.NewDBManager("nom"), cache.NewCacheDBManager(cfg.DataDir), cfg.GenesisConfig) db, levelDb := cfg.NewLevelDB("consensus") z.consensus = consensus.NewConsensus(db, z.chain, false) z.verifier = verifier.NewVerifier(z.chain, z.consensus)