diff --git a/lib/contract/storage/item.go b/lib/contract/storage/item.go new file mode 100644 index 000000000..e762a6e5c --- /dev/null +++ b/lib/contract/storage/item.go @@ -0,0 +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/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 new file mode 100644 index 000000000..df4aa61da --- /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, error) + GetStorageItem(address string, key string) (*storage.Item, error) +} + +// 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/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 +} 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/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/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..91e58bf5e --- /dev/null +++ b/lib/state/tree/tree.go @@ -0,0 +1,29 @@ +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 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 + + Commit() ([]byte, error) +} + +type Tree = MutableTree + +// Loader is the interface that make new tree +// +// LoadMutableTree returns new tree db related with the hash +// LoadImmutableTree too. +type Loader interface { + LoadMutableTree(hash []byte) (MutableTree, error) + LoadImmutableTree(hash []byte) (ImmutableTree, error) +}