From 8f3afab881f76bbb5fcb63cf942b51c35c9fb2f3 Mon Sep 17 00:00:00 2001 From: anarcher Date: Fri, 24 Aug 2018 17:04:45 +0900 Subject: [PATCH 1/4] Add state and treedb interface --- lib/contract/storage/item.go | 5 ++ lib/state/interface.go | 43 +++++++++++++++++ lib/state/tree/eth/builder.go | 28 +++++++++++ lib/state/tree/eth/db.go | 82 +++++++++++++++++++++++++++++++++ lib/state/tree/eth/trie.go | 62 +++++++++++++++++++++++++ lib/state/tree/eth/trie_test.go | 44 ++++++++++++++++++ lib/state/tree/tree.go | 20 ++++++++ 7 files changed, 284 insertions(+) create mode 100644 lib/contract/storage/item.go create mode 100644 lib/state/interface.go create mode 100644 lib/state/tree/eth/builder.go create mode 100644 lib/state/tree/eth/db.go create mode 100644 lib/state/tree/eth/trie.go create mode 100644 lib/state/tree/eth/trie_test.go create mode 100644 lib/state/tree/tree.go diff --git a/lib/contract/storage/item.go b/lib/contract/storage/item.go new file mode 100644 index 000000000..f85114221 --- /dev/null +++ b/lib/contract/storage/item.go @@ -0,0 +1,5 @@ +package storage + +type Item struct { + Value []byte +} diff --git a/lib/state/interface.go b/lib/state/interface.go new file mode 100644 index 000000000..760e44dfd --- /dev/null +++ b/lib/state/interface.go @@ -0,0 +1,43 @@ +package state + +import ( + "boscoin.io/sebak/lib/block" + "boscoin.io/sebak/lib/contract/storage" +) + +// Reader is the interface that only account and storage state +type Reader interface { + GetAccount(address string) *block.BlockAccount + GetStorageItem(address string, key string) *storage.Item +} + +// Writer is the interface that account and storage state +type Writer interface { + SetAccount(account *block.BlockAccount) error + SetStorageItem(address, key string, item *storage.Item) error +} + +// ReadWriter is the interface that groups the Reader and Writer methods. +type ReadWriter interface { + Reader + Writer +} + +// Committer executes working state to persist + +// Hash returns working state's hash root +// Commit performs working state to persisted +// Reset state to underlying State (empty working state) +type Committer interface { + Hash() ([]byte, error) + Commit([]byte) error + Reset() error +} + +// Updatable performs to write state and commit this write by hash +// +// It can us used with WorldState.Update(func updater(up Updatable) error) ([]byte,error) +type Updatable interface { + Writer + Committer +} diff --git a/lib/state/tree/eth/builder.go b/lib/state/tree/eth/builder.go new file mode 100644 index 000000000..928726fb8 --- /dev/null +++ b/lib/state/tree/eth/builder.go @@ -0,0 +1,28 @@ +package eth + +import ( + "boscoin.io/sebak/lib/state/tree" + "boscoin.io/sebak/lib/storage" +) + +type Builder struct { + db *sebakstorage.LevelDBBackend +} + +var _ tree.Builder = (*Builder)(nil) + +func NewBuilder(db *sebakstorage.LevelDBBackend) *Builder { + b := &Builder{ + db: db, + } + return b +} + +func (b *Builder) Build(hash []byte) (tree.Tree, error) { + ethdb := NewEthDB(b.db) + trie, err := NewTrie(hash, ethdb) + if err != nil { + return nil, err + } + return trie, nil +} diff --git a/lib/state/tree/eth/db.go b/lib/state/tree/eth/db.go new file mode 100644 index 000000000..f4af19deb --- /dev/null +++ b/lib/state/tree/eth/db.go @@ -0,0 +1,82 @@ +package eth + +import ( + "boscoin.io/sebak/lib/storage" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/syndtr/goleveldb/leveldb" +) + +type EthDB struct { + ldbBackend *sebakstorage.LevelDBBackend +} + +var _ ethdb.Database = (*EthDB)(nil) + +func NewEthDB(ldb *sebakstorage.LevelDBBackend) *EthDB { + return &EthDB{ + ldbBackend: ldb, + } +} + +func (db *EthDB) Put(key []byte, value []byte) error { + return db.ldbBackend.Core.Put(key, value, nil) +} + +func (db *EthDB) Has(key []byte) (bool, error) { + return db.ldbBackend.Core.Has(key, nil) +} + +func (db *EthDB) Get(key []byte) ([]byte, error) { + dat, err := db.ldbBackend.Core.Get(key, nil) + if err != nil { + return nil, err + } + return dat, nil +} + +func (db *EthDB) Delete(key []byte) error { + return db.ldbBackend.Core.Delete(key, nil) +} + +func (db *EthDB) Close() { + db.ldbBackend.DB.Close() +} + +func (db *EthDB) NewBatch() ethdb.Batch { + return &ldbBatch{db: db.ldbBackend, b: new(leveldb.Batch)} +} + +func (db *EthDB) BackEnd() *sebakstorage.LevelDBBackend { + return db.ldbBackend +} + +type ldbBatch struct { + db *sebakstorage.LevelDBBackend + b *leveldb.Batch + size int +} + +func (b *ldbBatch) Put(key, value []byte) error { + b.b.Put(key, value) + b.size += len(value) + return nil +} + +func (b *ldbBatch) Delete(key []byte) error { + b.b.Delete(key) + b.size += 1 + return nil +} + +func (b *ldbBatch) Write() error { + return b.db.Core.Write(b.b, nil) +} + +func (b *ldbBatch) ValueSize() int { + return b.size +} + +func (b *ldbBatch) Reset() { + b.b.Reset() + b.size = 0 +} diff --git a/lib/state/tree/eth/trie.go b/lib/state/tree/eth/trie.go new file mode 100644 index 000000000..742525807 --- /dev/null +++ b/lib/state/tree/eth/trie.go @@ -0,0 +1,62 @@ +package eth + +import ( + "boscoin.io/sebak/lib/state/tree" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/trie" +) + +type Trie struct { + trie *trie.Trie +} + +var _ tree.Tree = (*Trie)(nil) // Implements tree/Tree interface + +func NewTrie(root []byte, db *EthDB) (*Trie, error) { + hash := ethcommon.BytesToHash(root) + trieDB := trie.NewDatabase(db) + ethTrie, err := trie.New(hash, trieDB) + if err != nil { + return nil, err + } + + t := &Trie{ + trie: ethTrie, + } + return t, nil +} + +func (t *Trie) Hash() []byte { + hash := t.trie.Hash() + return hash.Bytes() +} + +func (t *Trie) Commit() ([]byte, error) { + hash, err := t.trie.Commit(nil) + if err != nil { + return nil, err + } + return hash.Bytes(), nil +} + +func (t *Trie) Set(key, value []byte) error { + if err := t.trie.TryUpdate(key, value); err != nil { + return err + } + return nil +} + +func (t *Trie) Get(key []byte) ([]byte, error) { + value, err := t.trie.TryGet(key) + if err != nil { + return nil, err + } + return value, nil +} + +func (t *Trie) Delete(key []byte) error { + if err := t.trie.TryDelete(key); err != nil { + return err + } + return nil +} diff --git a/lib/state/tree/eth/trie_test.go b/lib/state/tree/eth/trie_test.go new file mode 100644 index 000000000..1d3b591d6 --- /dev/null +++ b/lib/state/tree/eth/trie_test.go @@ -0,0 +1,44 @@ +package eth + +import ( + "bytes" + "testing" + + ethcommon "github.com/ethereum/go-ethereum/common" + + "boscoin.io/sebak/lib/storage" +) + +func TestEthTrieHash(t *testing.T) { + var root = ethcommon.Hash{} + + st, _ := sebakstorage.NewTestMemoryLevelDBBackend() + defer st.Close() + + db := NewEthDB(st) + trie, err := NewTrie(root.Bytes(), db) + if err != nil { + t.Fatal(err) + } + + hash1 := trie.Hash() + + if err := trie.Set([]byte("a"), []byte("b")); err != nil { + t.Fatal(err) + } + + hash2 := trie.Hash() + + hash3, err := trie.Commit() + if err != nil { + t.Fatal(err) + } + + if bytes.Equal(hash1, hash2) { + t.Errorf("hash1 == hash2 %v,%v", hash1, hash2) + } + + if !bytes.Equal(hash2, hash3) { + t.Errorf("hash2 != hash3 %v,%v", hash2, hash3) + } +} diff --git a/lib/state/tree/tree.go b/lib/state/tree/tree.go new file mode 100644 index 000000000..89efec4a6 --- /dev/null +++ b/lib/state/tree/tree.go @@ -0,0 +1,20 @@ +package tree + +// Tree is the interface that is Merkle tree db +// +// eth's trie or tendermint's IAVL can be an implementation of this interface +type Tree interface { + Set(key, value []byte) error + Get(key []byte) (value []byte, err error) + Delete(key []byte) error + + Hash() []byte //current working root hash + Commit() ([]byte, error) +} + +// Builder is the interface that make new tree +// +// Build returns new tree db related with the hash +type Builder interface { + Build(hash []byte) (Tree, error) +} From 6b31b89db58060230543194558fa1d4409d431d3 Mon Sep 17 00:00:00 2001 From: anarcher Date: Tue, 28 Aug 2018 13:14:52 +0900 Subject: [PATCH 2/4] Add state.Cache --- lib/state/cache.go | 169 +++++++++++++++++++++++++++++++++++++++++ lib/state/interface.go | 4 +- 2 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 lib/state/cache.go diff --git a/lib/state/cache.go b/lib/state/cache.go new file mode 100644 index 000000000..fb3e8c159 --- /dev/null +++ b/lib/state/cache.go @@ -0,0 +1,169 @@ +package state + +import ( + "fmt" + "sort" + + "boscoin.io/sebak/lib/block" + "boscoin.io/sebak/lib/contract/storage" +) + +type Cache struct { + reader Reader + accounts map[string]*accountInfo +} + +type accountInfo struct { + account *block.BlockAccount + items map[string]*storage.Item + updated bool + removed bool //TODO Remove Account? +} + +var _ ReadWriter = (*Cache)(nil) + +func NewCache(reader Reader) *Cache { + cache := &Cache{ + reader: reader, + accounts: make(map[string]*accountInfo), + } + + return cache +} + +func (c *Cache) Flush(w Writer) error { + if err := c.Sync(w); err != nil { + return err + } + if err := c.Reset(); err != nil { + return err + } + return nil +} + +func (c *Cache) Sync(w Writer) error { + var addressList []string + for address := range c.accounts { + addressList = append(addressList, address) + } + sort.Strings(addressList) + + for _, address := range addressList { + aInfo := c.accounts[address] + if aInfo.updated { + var keys []string + for key := range aInfo.items { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + item := aInfo.items[key] + if err := w.SetStorageItem(address, key, item); err != nil { + return err + } + } + + if err := w.SetAccount(aInfo.account); err != nil { + return err + } + } + } + + return nil +} + +func (c *Cache) Reset() error { + c.accounts = make(map[string]*accountInfo) + return nil +} + +func (c *Cache) GetAccount(address string) (*block.BlockAccount, error) { + a, err := c.get(address) + if err != nil { + return nil, err + } + if a.removed == true { + return nil, err + } + return a.account, nil +} + +func (c *Cache) SetAccount(account *block.BlockAccount) error { + a, err := c.get(account.Address) + if err != nil { + return err + } + if a != nil { + c.accounts[account.Address] = a + return nil + } + + a = &accountInfo{ + account: account, + items: map[string]*storage.Item{}, + updated: true, + } + c.accounts[account.Address] = a + return nil +} + +func (c *Cache) GetStorageItem(address, key string) (*storage.Item, error) { + a, err := c.get(address) + if err != nil { + return nil, err + } + + if a == nil { + return nil, fmt.Errorf("GetStorageItem on a empty account: %s", address) + } + + if item := a.items[key]; item != nil { + return item, nil + } + + item, err := c.reader.GetStorageItem(address, key) + if err != nil { + return nil, err + } + + a.items[key] = item + + return item, nil +} + +func (c *Cache) SetStorageItem(address, key string, item *storage.Item) error { + a, err := c.get(address) + if err != nil { + return err + } + + if a == nil { + return fmt.Errorf("SetStorageItem on a empty account: %s", address) + } + + a.items[key] = item + a.updated = true + + return nil +} + +func (c *Cache) get(address string) (*accountInfo, error) { + a := c.accounts[address] + if a == nil { + ba, err := c.reader.GetAccount(address) + if err != nil { + return nil, err + } + if ba == nil { + return nil, nil + } + + a = &accountInfo{ + account: ba, + items: make(map[string]*storage.Item), + } + c.accounts[address] = a + } + return a, nil + +} diff --git a/lib/state/interface.go b/lib/state/interface.go index 760e44dfd..df4aa61da 100644 --- a/lib/state/interface.go +++ b/lib/state/interface.go @@ -7,8 +7,8 @@ import ( // Reader is the interface that only account and storage state type Reader interface { - GetAccount(address string) *block.BlockAccount - GetStorageItem(address string, key string) *storage.Item + GetAccount(address string) (*block.BlockAccount, error) + GetStorageItem(address string, key string) (*storage.Item, error) } // Writer is the interface that account and storage state From 8289b82c1d1f24fc542c5aa9dbdaaeddbf9d56b9 Mon Sep 17 00:00:00 2001 From: anarcher Date: Tue, 28 Aug 2018 16:44:36 +0900 Subject: [PATCH 3/4] Change builder to loader in tree. and Tree separates MutableTree and ImmutableTree --- lib/state/tree/eth/builder.go | 28 -------------------------- lib/state/tree/eth/loader.go | 37 +++++++++++++++++++++++++++++++++++ lib/state/tree/tree.go | 23 +++++++++++++++------- 3 files changed, 53 insertions(+), 35 deletions(-) delete mode 100644 lib/state/tree/eth/builder.go create mode 100644 lib/state/tree/eth/loader.go diff --git a/lib/state/tree/eth/builder.go b/lib/state/tree/eth/builder.go deleted file mode 100644 index 928726fb8..000000000 --- a/lib/state/tree/eth/builder.go +++ /dev/null @@ -1,28 +0,0 @@ -package eth - -import ( - "boscoin.io/sebak/lib/state/tree" - "boscoin.io/sebak/lib/storage" -) - -type Builder struct { - db *sebakstorage.LevelDBBackend -} - -var _ tree.Builder = (*Builder)(nil) - -func NewBuilder(db *sebakstorage.LevelDBBackend) *Builder { - b := &Builder{ - db: db, - } - return b -} - -func (b *Builder) Build(hash []byte) (tree.Tree, error) { - ethdb := NewEthDB(b.db) - trie, err := NewTrie(hash, ethdb) - if err != nil { - return nil, err - } - return trie, nil -} diff --git a/lib/state/tree/eth/loader.go b/lib/state/tree/eth/loader.go new file mode 100644 index 000000000..301715e7b --- /dev/null +++ b/lib/state/tree/eth/loader.go @@ -0,0 +1,37 @@ +package eth + +import ( + "boscoin.io/sebak/lib/state/tree" + "boscoin.io/sebak/lib/storage" +) + +type Loader struct { + db *sebakstorage.LevelDBBackend +} + +var _ tree.Loader = (*Loader)(nil) + +func NewLoader(db *sebakstorage.LevelDBBackend) *Loader { + l := &Loader{ + db: db, + } + return l +} + +func (l *Loader) loadTree(hash []byte) (*Trie, error) { + + ethdb := NewEthDB(l.db) + trie, err := NewTrie(hash, ethdb) + if err != nil { + return nil, err + } + return trie, nil +} + +func (l *Loader) LoadMutableTree(hash []byte) (tree.MutableTree, error) { + return l.loadTree(hash) +} + +func (l *Loader) LoadImmutableTree(hash []byte) (tree.ImmutableTree, error) { + return l.loadTree(hash) +} diff --git a/lib/state/tree/tree.go b/lib/state/tree/tree.go index 89efec4a6..91e58bf5e 100644 --- a/lib/state/tree/tree.go +++ b/lib/state/tree/tree.go @@ -3,18 +3,27 @@ package tree // Tree is the interface that is Merkle tree db // // eth's trie or tendermint's IAVL can be an implementation of this interface -type Tree interface { - Set(key, value []byte) error +type ImmutableTree interface { Get(key []byte) (value []byte, err error) + Hash() []byte //current working root hash +} + +type MutableTree interface { + ImmutableTree + + Set(key, value []byte) error Delete(key []byte) error - Hash() []byte //current working root hash Commit() ([]byte, error) } -// Builder is the interface that make new tree +type Tree = MutableTree + +// Loader is the interface that make new tree // -// Build returns new tree db related with the hash -type Builder interface { - Build(hash []byte) (Tree, error) +// LoadMutableTree returns new tree db related with the hash +// LoadImmutableTree too. +type Loader interface { + LoadMutableTree(hash []byte) (MutableTree, error) + LoadImmutableTree(hash []byte) (ImmutableTree, error) } From d27bd32941a1dadfbf123487174eb9d0e7a0a01a Mon Sep 17 00:00:00 2001 From: anarcher Date: Wed, 29 Aug 2018 17:43:20 +0900 Subject: [PATCH 4/4] Add state.State --- lib/contract/storage/item.go | 12 +++++++ lib/state/state.go | 62 ++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 lib/state/state.go diff --git a/lib/contract/storage/item.go b/lib/contract/storage/item.go index f85114221..e762a6e5c 100644 --- a/lib/contract/storage/item.go +++ b/lib/contract/storage/item.go @@ -1,5 +1,17 @@ package storage +import ( + common "boscoin.io/sebak/lib/common" +) + type Item struct { Value []byte } + +func (i *Item) Serialize() (encoded []byte, err error) { + encoded, err = common.EncodeJSONValue(i) + return +} +func (i *Item) Deserialize(encoded []byte) (err error) { + return common.DecodeJSONValue(encoded, i) +} diff --git a/lib/state/state.go b/lib/state/state.go new file mode 100644 index 000000000..3044728b2 --- /dev/null +++ b/lib/state/state.go @@ -0,0 +1,62 @@ +package state + +import ( + logging "github.com/inconshreveable/log15" + + "boscoin.io/sebak/lib/block" + cstorage "boscoin.io/sebak/lib/contract/storage" + "boscoin.io/sebak/lib/state/tree" +) + +type State struct { + tree tree.ImmutableTree + treeLoader tree.Loader + hash []byte // Current state's hash + + logger logging.Logger +} + +var _ Reader = (*State)(nil) + +func NewState(treeLoader tree.Loader, hash []byte) *State { + tree, err := treeLoader.LoadMutableTree(hash) + if err != nil { + panic(err) + } + + s := &State{ + treeLoader: treeLoader, + tree: tree, + logger: logging.New("module", "state"), + } + + return s +} + +func (s *State) GetAccount(address string) (*block.BlockAccount, error) { + value, err := s.tree.Get([]byte(address)) + if err != nil { + return nil, err + } + + account := &block.BlockAccount{} + if err := account.Deserialize(value); err != nil { + return nil, err + } + + return account, nil +} + +func (s *State) GetStorageItem(address, key string) (*cstorage.Item, error) { + value, err := s.tree.Get([]byte(address)) + if err != nil { + return nil, err + } + + item := &cstorage.Item{} + if err := item.Deserialize(value); err != nil { + return nil, err + } + + return item, nil +}