From 566f81641747a0ae56263ee29ef13f311e92bc7e Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 13:25:47 +0300 Subject: [PATCH 01/29] change trie to iavl --- Gopkg.lock | 24 +- Gopkg.toml | 12 +- core/state/database.go | 154 ------------ core/state/statedb.go | 82 ++++--- trie/database.go | 356 --------------------------- trie/encoding.go | 114 --------- trie/errors.go | 35 --- trie/hasher.go | 212 ----------------- trie/iterator.go | 528 ----------------------------------------- trie/node.go | 224 ----------------- trie/proof.go | 153 ------------ trie/secure_trie.go | 196 --------------- trie/sync.go | 330 -------------------------- trie/trie.go | 478 ------------------------------------- 14 files changed, 50 insertions(+), 2848 deletions(-) delete mode 100644 core/state/database.go delete mode 100644 trie/database.go delete mode 100644 trie/encoding.go delete mode 100644 trie/errors.go delete mode 100644 trie/hasher.go delete mode 100644 trie/iterator.go delete mode 100644 trie/node.go delete mode 100644 trie/proof.go delete mode 100644 trie/secure_trie.go delete mode 100644 trie/sync.go delete mode 100644 trie/trie.go diff --git a/Gopkg.lock b/Gopkg.lock index 6d73aeccd..8bfeb8213 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -13,6 +13,12 @@ packages = ["btcec"] revision = "f673a4b563b57b9a95832545c878669a7fa801d9" +[[projects]] + name = "github.com/danil-lashin/iavl" + packages = ["."] + revision = "646a1dd997127d6b198a8a65c3717125fa13ffe5" + version = "v0.11.1" + [[projects]] name = "github.com/davecgh/go-spew" packages = ["spew"] @@ -111,15 +117,6 @@ revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" version = "v1.2.0" -[[projects]] - branch = "master" - name = "github.com/hashicorp/golang-lru" - packages = [ - ".", - "simplelru" - ] - revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3" - [[projects]] name = "github.com/hashicorp/hcl" packages = [ @@ -227,6 +224,7 @@ revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a" [[projects]] + branch = "master" name = "github.com/rcrowley/go-metrics" packages = ["."] revision = "e2704e165165ec55d062f5919b4b29494e9fa790" @@ -471,12 +469,6 @@ packages = ["."] revision = "788fd78401277ebd861206a03c884797c6ec5541" -[[projects]] - branch = "v2" - name = "gopkg.in/karalabe/cookiejar.v2" - packages = ["collections/prque"] - revision = "8dcd6a7f4951f6ff3ee9cbb919a06d8925822e57" - [[projects]] name = "gopkg.in/yaml.v2" packages = ["."] @@ -486,6 +478,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "7f0b438359efe00bcf457cdb1437543bd234748f62e5b9ed64d56e57be975282" + inputs-digest = "7486c859e8417d585f25054ac2b5adfc43dc7c13d99a7c88ad60d4bafdd5c20d" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 3487a731c..3e72185da 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -41,10 +41,6 @@ name = "github.com/gorilla/websocket" version = "=1.2.0" -[[constraint]] - branch = "master" - name = "github.com/hashicorp/golang-lru" - [[constraint]] branch = "master" name = "github.com/rcrowley/go-metrics" @@ -58,8 +54,8 @@ name = "github.com/syndtr/goleveldb" [[constraint]] - name = "github.com/tendermint/go-amino" - version = "=v0.12.0" + name = "github.com/danil-lashin/iavl" + version = "=v0.11.1" [[constraint]] name = "github.com/tendermint/tendermint" @@ -69,10 +65,6 @@ branch = "v1" name = "gopkg.in/check.v1" -[[constraint]] - branch = "v2" - name = "gopkg.in/karalabe/cookiejar.v2" - [prune] go-tests = true unused-packages = true diff --git a/core/state/database.go b/core/state/database.go deleted file mode 100644 index 047608b7d..000000000 --- a/core/state/database.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package state - -import ( - "fmt" - "sync" - - "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/mintdb" - "github.com/MinterTeam/minter-go-node/trie" - lru "github.com/hashicorp/golang-lru" -) - -// Trie cache generation limit after which to evic trie nodes from memory. -var MaxTrieCacheGen = uint16(120) - -const ( - // Number of past tries to keep. This value is chosen such that - // reasonable chain reorg depths will hit an existing trie. - maxPastTries = 12 - - // Number of codehash->size associations to keep. - codeSizeCacheSize = 100000 -) - -// Database wraps access to tries and contract code. -type Database interface { - // OpenTrie opens the main account trie. - OpenTrie(root types.Hash) (Trie, error) - - // OpenStorageTrie opens the storage trie of an account. - OpenStorageTrie(addrHash, root types.Hash) (Trie, error) - - // CopyTrie returns an independent copy of the given trie. - CopyTrie(Trie) Trie - - // TrieDB retrieves the low level trie database used for data storage. - TrieDB() *trie.Database -} - -// Trie is a Ethereum Merkle Trie. -type Trie interface { - TryGet(key []byte) ([]byte, error) - TryUpdate(key, value []byte) error - TryDelete(key []byte) error - Commit(onleaf trie.LeafCallback) (types.Hash, error) - Hash() types.Hash - NodeIterator(startKey []byte) trie.NodeIterator - GetKey([]byte) []byte // TODO(fjl): remove this when SecureTrie is removed - Prove(key []byte, fromLevel uint, proofDb mintdb.Putter) error -} - -// NewDatabase creates a backing store for state. The returned database is safe for -// concurrent use and retains cached trie nodes in memory. The pool is an optional -// intermediate trie-node memory pool between the low level storage layer and the -// high level trie abstraction. -func NewDatabase(db mintdb.Database) Database { - csc, _ := lru.New(codeSizeCacheSize) - return &cachingDB{ - db: trie.NewDatabase(db), - codeSizeCache: csc, - } -} - -type cachingDB struct { - db *trie.Database - mu sync.Mutex - pastTries []*trie.SecureTrie - codeSizeCache *lru.Cache -} - -// OpenTrie opens the main account trie. -func (db *cachingDB) OpenTrie(root types.Hash) (Trie, error) { - db.mu.Lock() - defer db.mu.Unlock() - - for i := len(db.pastTries) - 1; i >= 0; i-- { - if db.pastTries[i].Hash() == root { - return cachedTrie{db.pastTries[i].Copy(), db}, nil - } - } - tr, err := trie.NewSecure(root, db.db, MaxTrieCacheGen) - if err != nil { - return nil, err - } - return cachedTrie{tr, db}, nil -} - -func (db *cachingDB) pushTrie(t *trie.SecureTrie) { - db.mu.Lock() - defer db.mu.Unlock() - - if len(db.pastTries) >= maxPastTries { - copy(db.pastTries, db.pastTries[1:]) - db.pastTries[len(db.pastTries)-1] = t - } else { - db.pastTries = append(db.pastTries, t) - } -} - -// OpenStorageTrie opens the storage trie of an account. -func (db *cachingDB) OpenStorageTrie(addrHash, root types.Hash) (Trie, error) { - return trie.NewSecure(root, db.db, 0) -} - -// CopyTrie returns an independent copy of the given trie. -func (db *cachingDB) CopyTrie(t Trie) Trie { - switch t := t.(type) { - case cachedTrie: - return cachedTrie{t.SecureTrie.Copy(), db} - case *trie.SecureTrie: - return t.Copy() - default: - panic(fmt.Errorf("unknown trie type %T", t)) - } -} - -// TrieDB retrieves any intermediate trie-node caching layer. -func (db *cachingDB) TrieDB() *trie.Database { - return db.db -} - -// cachedTrie inserts its trie into a cachingDB on commit. -type cachedTrie struct { - *trie.SecureTrie - db *cachingDB -} - -func (m cachedTrie) Commit(onleaf trie.LeafCallback) (types.Hash, error) { - root, err := m.SecureTrie.Commit(onleaf) - if err == nil { - m.db.pushTrie(m.SecureTrie) - } - return root, err -} - -func (m cachedTrie) Prove(key []byte, fromLevel uint, proofDb mintdb.Putter) error { - return m.SecureTrie.Prove(key, fromLevel, proofDb) -} diff --git a/core/state/statedb.go b/core/state/statedb.go index f955a1939..ed57fe3df 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -20,6 +20,8 @@ package state import ( "fmt" "github.com/MinterTeam/minter-go-node/eventsdb" + "github.com/danil-lashin/iavl" + dbm "github.com/tendermint/tendermint/libs/db" "math/big" "sync" @@ -57,8 +59,8 @@ var ( // * Coins // * Accounts type StateDB struct { - db Database - trie Trie + db dbm.DB + iavl *iavl.MutableTree // This map holds 'live' objects, which will get modified while processing a state transition. stateObjects map[types.Address]*stateObject @@ -96,14 +98,19 @@ type StakeCache struct { } // Create a new state from a given trie -func New(root types.Hash, db Database) (*StateDB, error) { - tr, err := db.OpenTrie(root) +func New(height int64, db dbm.DB) (*StateDB, error) { + + tree := iavl.NewMutableTree(db, 128) + + _, err := tree.LoadVersion(height) + if err != nil { return nil, err } + return &StateDB{ db: db, - trie: tr, + iavl: tree, stateObjects: make(map[types.Address]*stateObject), stateObjectsDirty: make(map[types.Address]struct{}), stateCoins: make(map[types.CoinSymbol]*stateCoin), @@ -171,7 +178,7 @@ func (s *StateDB) GetNonce(addr types.Address) uint64 { } // Database retrieves the low level database supporting the lower level trie ops. -func (s *StateDB) Database() Database { +func (s *StateDB) Database() dbm.DB { return s.db } @@ -220,7 +227,8 @@ func (s *StateDB) updateStateObject(stateObject *stateObject) { if err != nil { panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err)) } - s.setError(s.trie.TryUpdate(append(addressPrefix, addr[:]...), data)) + + s.iavl.Set(append(addressPrefix, addr[:]...), data) } func (s *StateDB) updateStateFrozenFund(stateFrozenFund *stateFrozenFund) { @@ -233,7 +241,8 @@ func (s *StateDB) updateStateFrozenFund(stateFrozenFund *stateFrozenFund) { binary.BigEndian.PutUint64(height, stateFrozenFund.blockHeight) key := append(frozenFundsPrefix, height...) - s.setError(s.trie.TryUpdate(key, data)) + + s.iavl.Set(key, data) } func (s *StateDB) updateStateCoin(stateCoin *stateCoin) { @@ -242,7 +251,8 @@ func (s *StateDB) updateStateCoin(stateCoin *stateCoin) { if err != nil { panic(fmt.Errorf("can't encode coin at %x: %v", symbol[:], err)) } - s.setError(s.trie.TryUpdate(append(coinPrefix, symbol[:]...), data)) + + s.iavl.Set(append(coinPrefix, symbol[:]...), data) } func (s *StateDB) updateStateCandidates(stateCandidates *stateCandidates) { @@ -250,8 +260,8 @@ func (s *StateDB) updateStateCandidates(stateCandidates *stateCandidates) { if err != nil { panic(fmt.Errorf("can't encode candidates: %v", err)) } - err = s.trie.TryUpdate(candidatesKey, data) - s.setError(err) + + s.iavl.Set(candidatesKey, data) } func (s *StateDB) updateStateValidators(validators *stateValidators) { @@ -259,21 +269,22 @@ func (s *StateDB) updateStateValidators(validators *stateValidators) { if err != nil { panic(fmt.Errorf("can't encode validators: %v", err)) } - err = s.trie.TryUpdate(validatorsKey, data) - s.setError(err) + + s.iavl.Set(validatorsKey, data) } // deleteStateObject removes the given object from the state trie. func (s *StateDB) deleteStateObject(stateObject *stateObject) { stateObject.deleted = true addr := stateObject.Address() - s.setError(s.trie.TryDelete(append(addressPrefix, addr[:]...))) + + s.iavl.Remove(append(addressPrefix, addr[:]...)) } // deleteStateCoin removes the given object from the state trie. func (s *StateDB) deleteStateCoin(stateCoin *stateCoin) { symbol := stateCoin.Symbol() - s.setError(s.trie.TryDelete(append(coinPrefix, symbol[:]...))) + s.iavl.Remove(append(coinPrefix, symbol[:]...)) } // deleteStateObject removes the given object from the state trie. @@ -282,7 +293,7 @@ func (s *StateDB) deleteFrozenFunds(stateFrozenFund *stateFrozenFund) { height := make([]byte, 8) binary.BigEndian.PutUint64(height, stateFrozenFund.blockHeight) key := append(frozenFundsPrefix, height...) - s.setError(s.trie.TryDelete(key)) + s.iavl.Remove(key) } // Retrieve a state frozen funds by block height. Returns nil if not found. @@ -297,9 +308,8 @@ func (s *StateDB) getStateFrozenFunds(blockHeight uint64) (stateFrozenFund *stat key := append(frozenFundsPrefix, height...) // Load the object from the database. - enc, err := s.trie.TryGet(key) + _, enc := s.iavl.Get(key) if len(enc) == 0 { - s.setError(err) return nil } var data FrozenFunds @@ -321,9 +331,8 @@ func (s *StateDB) getStateCoin(symbol types.CoinSymbol) (stateCoin *stateCoin) { } // Load the object from the database. - enc, err := s.trie.TryGet(append(coinPrefix, symbol[:]...)) + _, enc := s.iavl.Get(append(coinPrefix, symbol[:]...)) if len(enc) == 0 { - s.setError(err) return nil } var data Coin @@ -349,9 +358,8 @@ func (s *StateDB) getStateCandidates() (stateCandidates *stateCandidates) { } // Load the object from the database. - enc, err := s.trie.TryGet(candidatesKey) + _, enc := s.iavl.Get(candidatesKey) if len(enc) == 0 { - s.setError(err) return nil } var data Candidates @@ -377,9 +385,8 @@ func (s *StateDB) getStateValidators() (stateValidators *stateValidators) { } // Load the object from the database. - enc, err := s.trie.TryGet(validatorsKey) + _, enc := s.iavl.Get(validatorsKey) if len(enc) == 0 { - s.setError(err) return nil } var data Validators @@ -404,9 +411,8 @@ func (s *StateDB) getStateObject(addr types.Address) (stateObject *stateObject) } // Load the object from the database. - enc, err := s.trie.TryGet(append(addressPrefix, addr[:]...)) + _, enc := s.iavl.Get(append(addressPrefix, addr[:]...)) if len(enc) == 0 { - s.setError(err) return nil } var data Account @@ -616,7 +622,7 @@ func (s *StateDB) CreateCandidate( } // Commit writes the state to the underlying in-memory trie database. -func (s *StateDB) Commit(deleteEmptyObjects bool) (root types.Hash, err error) { +func (s *StateDB) Commit(deleteEmptyObjects bool) (root types.Hash, height int64, err error) { // Commit objects to the trie. for addr, stateObject := range s.stateObjects { @@ -629,7 +635,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root types.Hash, err error) { case isDirty: // Write any storage changes in the state object to its storage trie. if err := stateObject.CommitTrie(s.db); err != nil { - return types.Hash{}, err + return types.Hash{}, 0, err } // Update the object in the main account trie. s.updateStateObject(stateObject) @@ -675,18 +681,10 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root types.Hash, err error) { } // Write trie changes. - root, err = s.trie.Commit(func(leaf []byte, parent types.Hash) error { - var account Account - if err := rlp.DecodeBytes(leaf, &account); err != nil { - return nil - } - if account.Root != emptyState { - s.db.TrieDB().Reference(account.Root, parent) - } - return nil - }) - // log.Debug("Trie cache stats after commit", "misses", trie.CacheMisses(), "unloads", trie.CacheUnloads()) - return root, err + + hash, height, err := s.iavl.SaveVersion() + + return types.BytesToHash(hash), height, err } func (s *StateDB) CoinExists(symbol types.CoinSymbol) bool { @@ -980,7 +978,7 @@ func (s *StateDB) SubStake(sender types.Address, pubkey []byte, coin types.CoinS func (s *StateDB) IsCheckUsed(check *check.Check) bool { checkHash := check.Hash().Bytes() - data, _ := s.trie.TryGet(append(usedCheckPrefix, checkHash...)) + _, data := s.iavl.Get(append(usedCheckPrefix, checkHash...)) return len(data) != 0 } @@ -989,7 +987,7 @@ func (s *StateDB) UseCheck(check *check.Check) { checkHash := check.Hash().Bytes() trieHash := append(usedCheckPrefix, checkHash...) - s.setError(s.trie.TryUpdate(trieHash, []byte{0x1})) + s.iavl.Set(trieHash, []byte{0x1}) } func (s *StateDB) SetCandidateOnline(pubkey []byte) { diff --git a/trie/database.go b/trie/database.go deleted file mode 100644 index d69921f56..000000000 --- a/trie/database.go +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -import ( - "github.com/MinterTeam/minter-go-node/log" - "sync" - "time" - - // "github.com/ethereum/go-ethereum/log" - "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/mintdb" -) - -// secureKeyPrefix is the database key prefix used to store trie node preimages. -var secureKeyPrefix = []byte("secure-key-") - -// secureKeyLength is the length of the above prefix + 32byte hash. -const secureKeyLength = 11 + 32 - -// DatabaseReader wraps the Get and Has method of a backing store for the trie. -type DatabaseReader interface { - // Get retrieves the value associated with key form the database. - Get(key []byte) (value []byte, err error) - - // Has retrieves whether a key is present in the database. - Has(key []byte) (bool, error) -} - -// Database is an intermediate write layer between the trie data structures and -// the disk database. The aim is to accumulate trie writes in-memory and only -// periodically flush a couple tries to disk, garbage collecting the remainder. -type Database struct { - diskdb mintdb.Database // Persistent storage for matured trie nodes - - nodes map[types.Hash]*cachedNode // Data and references relationships of a node - preimages map[types.Hash][]byte // Preimages of nodes from the secure trie - seckeybuf [secureKeyLength]byte // Ephemeral buffer for calculating preimage keys - - gctime time.Duration // Time spent on garbage collection since last commit - gcnodes uint64 // Nodes garbage collected since last commit - gcsize types.StorageSize // Data storage garbage collected since last commit - - nodesSize types.StorageSize // Storage size of the nodes cache - preimagesSize types.StorageSize // Storage size of the preimages cache - - lock sync.RWMutex -} - -// cachedNode is all the information we know about a single cached node in the -// memory database write layer. -type cachedNode struct { - blob []byte // Cached data block of the trie node - parents int // Number of live nodes referencing this one - children map[types.Hash]int // Children referenced by this nodes -} - -// NewDatabase creates a new trie database to store ephemeral trie content before -// its written out to disk or garbage collected. -func NewDatabase(diskdb mintdb.Database) *Database { - return &Database{ - diskdb: diskdb, - nodes: map[types.Hash]*cachedNode{ - {}: {children: make(map[types.Hash]int)}, - }, - preimages: make(map[types.Hash][]byte), - } -} - -// DiskDB retrieves the persistent storage backing the trie database. -func (db *Database) DiskDB() DatabaseReader { - return db.diskdb -} - -// Insert writes a new trie node to the memory database if it's yet unknown. The -// method will make a copy of the slice. -func (db *Database) Insert(hash types.Hash, blob []byte) { - db.lock.Lock() - defer db.lock.Unlock() - - db.insert(hash, blob) -} - -// insert is the private locked version of Insert. -func (db *Database) insert(hash types.Hash, blob []byte) { - if _, ok := db.nodes[hash]; ok { - return - } - db.nodes[hash] = &cachedNode{ - blob: types.CopyBytes(blob), - children: make(map[types.Hash]int), - } - db.nodesSize += types.StorageSize(types.HashLength + len(blob)) -} - -// insertPreimage writes a new trie node pre-image to the memory database if it's -// yet unknown. The method will make a copy of the slice. -// -// Note, this method assumes that the database's lock is held! -func (db *Database) insertPreimage(hash types.Hash, preimage []byte) { - if _, ok := db.preimages[hash]; ok { - return - } - db.preimages[hash] = types.CopyBytes(preimage) - db.preimagesSize += types.StorageSize(types.HashLength + len(preimage)) -} - -// Node retrieves a cached trie node from memory. If it cannot be found cached, -// the method queries the persistent database for the content. -func (db *Database) Node(hash types.Hash) ([]byte, error) { - // Retrieve the node from cache if available - db.lock.RLock() - node := db.nodes[hash] - db.lock.RUnlock() - - if node != nil { - return node.blob, nil - } - // Content unavailable in memory, attempt to retrieve from disk - return db.diskdb.Get(hash[:]) -} - -// preimage retrieves a cached trie node pre-image from memory. If it cannot be -// found cached, the method queries the persistent database for the content. -func (db *Database) preimage(hash types.Hash) ([]byte, error) { - // Retrieve the node from cache if available - db.lock.RLock() - preimage := db.preimages[hash] - db.lock.RUnlock() - - if preimage != nil { - return preimage, nil - } - // Content unavailable in memory, attempt to retrieve from disk - return db.diskdb.Get(db.secureKey(hash[:])) -} - -// secureKey returns the database key for the preimage of key, as an ephemeral -// buffer. The caller must not hold onto the return value because it will become -// invalid on the next call. -func (db *Database) secureKey(key []byte) []byte { - buf := append(db.seckeybuf[:0], secureKeyPrefix...) - buf = append(buf, key...) - return buf -} - -// Nodes retrieves the hashes of all the nodes cached within the memory database. -// This method is extremely expensive and should only be used to validate internal -// states in test code. -func (db *Database) Nodes() []types.Hash { - db.lock.RLock() - defer db.lock.RUnlock() - - var hashes = make([]types.Hash, 0, len(db.nodes)) - for hash := range db.nodes { - if hash != (types.Hash{}) { // Special case for "root" references/nodes - hashes = append(hashes, hash) - } - } - return hashes -} - -// Reference adds a new reference from a parent node to a child node. -func (db *Database) Reference(child types.Hash, parent types.Hash) { - db.lock.RLock() - defer db.lock.RUnlock() - - db.reference(child, parent) -} - -// reference is the private locked version of Reference. -func (db *Database) reference(child types.Hash, parent types.Hash) { - // If the node does not exist, it's a node pulled from disk, skip - node, ok := db.nodes[child] - if !ok { - return - } - // If the reference already exists, only duplicate for roots - if _, ok = db.nodes[parent].children[child]; ok && parent != (types.Hash{}) { - return - } - node.parents++ - db.nodes[parent].children[child]++ -} - -// Dereference removes an existing reference from a parent node to a child node. -func (db *Database) Dereference(child types.Hash, parent types.Hash) { - db.lock.Lock() - defer db.lock.Unlock() - - nodes, storage, start := len(db.nodes), db.nodesSize, time.Now() - db.dereference(child, parent) - - db.gcnodes += uint64(nodes - len(db.nodes)) - db.gcsize += storage - db.nodesSize - db.gctime += time.Since(start) - - // log.Debug("Dereferenced trie from memory database", "nodes", nodes-len(db.nodes), "size", storage-db.nodesSize, "time", time.Since(start), - // "gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.nodes), "livesize", db.nodesSize) -} - -// dereference is the private locked version of Dereference. -func (db *Database) dereference(child types.Hash, parent types.Hash) { - // Dereference the parent-child - node := db.nodes[parent] - - node.children[child]-- - if node.children[child] == 0 { - delete(node.children, child) - } - // If the node does not exist, it's a previously committed node. - node, ok := db.nodes[child] - if !ok { - return - } - // If there are no more references to the child, delete it and cascade - node.parents-- - if node.parents == 0 { - for hash := range node.children { - db.dereference(hash, child) - } - delete(db.nodes, child) - db.nodesSize -= types.StorageSize(types.HashLength + len(node.blob)) - } -} - -// Commit iterates over all the children of a particular node, writes them out -// to disk, forcefully tearing down all references in both directions. -// -// As a side effect, all pre-images accumulated up to this point are also written. -func (db *Database) Commit(node types.Hash, report bool) error { - // Create a database batch to flush persistent data out. It is important that - // outside code doesn't see an inconsistent state (referenced data removed from - // memory cache during commit but not yet in persistent storage). This is ensured - // by only uncaching existing data when the database write finalizes. - db.lock.RLock() - - // start := time.Now() - batch := db.diskdb.NewBatch() - - // Move all of the accumulated preimages into a write batch - for hash, preimage := range db.preimages { - if err := batch.Put(db.secureKey(hash[:]), preimage); err != nil { - log.Error("Failed to commit preimage from trie database", "err", err) - db.lock.RUnlock() - return err - } - if batch.ValueSize() > mintdb.IdealBatchSize { - if err := batch.Write(); err != nil { - return err - } - batch.Reset() - } - } - // Move the trie itself into the batch, flushing if enough data is accumulated - // nodes, storage := len(db.nodes), db.nodesSize+db.preimagesSize - if err := db.commit(node, batch); err != nil { - log.Error("Failed to commit trie from trie database", "err", err) - db.lock.RUnlock() - return err - } - // Write batch ready, unlock for readers during persistence - if err := batch.Write(); err != nil { - log.Error("Failed to write trie to disk", "err", err) - db.lock.RUnlock() - return err - } - db.lock.RUnlock() - - // Write successful, clear out the flushed data - db.lock.Lock() - defer db.lock.Unlock() - - db.preimages = make(map[types.Hash][]byte) - db.preimagesSize = 0 - - db.uncache(node) - // - // logger := log.Info - // if !report { - // logger = log.Debug - // } - // logger("Persisted trie from memory database", "nodes", nodes-len(db.nodes), "size", storage-db.nodesSize, "time", time.Since(start), - // "gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.nodes), "livesize", db.nodesSize) - - // Reset the garbage collection statistics - db.gcnodes, db.gcsize, db.gctime = 0, 0, 0 - - return nil -} - -// commit is the private locked version of Commit. -func (db *Database) commit(hash types.Hash, batch mintdb.Batch) error { - // If the node does not exist, it's a previously committed node - node, ok := db.nodes[hash] - if !ok { - return nil - } - for child := range node.children { - if err := db.commit(child, batch); err != nil { - return err - } - } - if err := batch.Put(hash[:], node.blob); err != nil { - return err - } - // If we've reached an optimal match size, commit and start over - if batch.ValueSize() >= mintdb.IdealBatchSize { - if err := batch.Write(); err != nil { - return err - } - batch.Reset() - } - return nil -} - -// uncache is the post-processing step of a commit operation where the already -// persisted trie is removed from the cache. The reason behind the two-phase -// commit is to ensure consistent data availability while moving from memory -// to disk. -func (db *Database) uncache(hash types.Hash) { - // If the node does not exist, we're done on this path - node, ok := db.nodes[hash] - if !ok { - return - } - // Otherwise uncache the node's subtries and remove the node itself too - for child := range node.children { - db.uncache(child) - } - delete(db.nodes, hash) - db.nodesSize -= types.StorageSize(types.HashLength + len(node.blob)) -} - -// Size returns the current storage size of the memory cache in front of the -// persistent database layer. -func (db *Database) Size() types.StorageSize { - db.lock.RLock() - defer db.lock.RUnlock() - - return db.nodesSize + db.preimagesSize -} diff --git a/trie/encoding.go b/trie/encoding.go deleted file mode 100644 index e96a786e4..000000000 --- a/trie/encoding.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -// Trie keys are dealt with in three distinct encodings: -// -// KEYBYTES encoding contains the actual key and nothing else. This encoding is the -// input to most API functions. -// -// HEX encoding contains one byte for each nibble of the key and an optional trailing -// 'terminator' byte of value 0x10 which indicates whether or not the node at the key -// contains a value. Hex key encoding is used for nodes loaded in memory because it's -// convenient to access. -// -// COMPACT encoding is defined by the Ethereum Yellow Paper (it's called "hex prefix -// encoding" there) and contains the bytes of the key and a flag. The high nibble of the -// first byte contains the flag; the lowest bit encoding the oddness of the length and -// the second-lowest encoding whether the node at the key is a value node. The low nibble -// of the first byte is zero in the case of an even number of nibbles and the first nibble -// in the case of an odd number. All remaining nibbles (now an even number) fit properly -// into the remaining bytes. Compact encoding is used for nodes stored on disk. - -func hexToCompact(hex []byte) []byte { - terminator := byte(0) - if hasTerm(hex) { - terminator = 1 - hex = hex[:len(hex)-1] - } - buf := make([]byte, len(hex)/2+1) - buf[0] = terminator << 5 // the flag byte - if len(hex)&1 == 1 { - buf[0] |= 1 << 4 // odd flag - buf[0] |= hex[0] // first nibble is contained in the first byte - hex = hex[1:] - } - decodeNibbles(hex, buf[1:]) - return buf -} - -func compactToHex(compact []byte) []byte { - base := keybytesToHex(compact) - base = base[:len(base)-1] - // apply terminator flag - if base[0] >= 2 { - base = append(base, 16) - } - // apply odd flag - chop := 2 - base[0]&1 - return base[chop:] -} - -func keybytesToHex(str []byte) []byte { - l := len(str)*2 + 1 - var nibbles = make([]byte, l) - for i, b := range str { - nibbles[i*2] = b / 16 - nibbles[i*2+1] = b % 16 - } - nibbles[l-1] = 16 - return nibbles -} - -// hexToKeybytes turns hex nibbles into key bytes. -// This can only be used for keys of even length. -func hexToKeybytes(hex []byte) []byte { - if hasTerm(hex) { - hex = hex[:len(hex)-1] - } - if len(hex)&1 != 0 { - panic("can't convert hex key of odd length") - } - key := make([]byte, (len(hex)+1)/2) - decodeNibbles(hex, key) - return key -} - -func decodeNibbles(nibbles []byte, bytes []byte) { - for bi, ni := 0, 0; ni < len(nibbles); bi, ni = bi+1, ni+2 { - bytes[bi] = nibbles[ni]<<4 | nibbles[ni+1] - } -} - -// prefixLen returns the length of the common prefix of a and b. -func prefixLen(a, b []byte) int { - var i, length = 0, len(a) - if len(b) < length { - length = len(b) - } - for ; i < length; i++ { - if a[i] != b[i] { - break - } - } - return i -} - -// hasTerm returns whether a hex key has the terminator flag. -func hasTerm(s []byte) bool { - return len(s) > 0 && s[len(s)-1] == 16 -} diff --git a/trie/errors.go b/trie/errors.go deleted file mode 100644 index ac0086780..000000000 --- a/trie/errors.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -import ( - "fmt" - - "github.com/MinterTeam/minter-go-node/core/types" -) - -// MissingNodeError is returned by the trie functions (TryGet, TryUpdate, TryDelete) -// in the case where a trie node is not present in the local database. It contains -// information necessary for retrieving the missing node. -type MissingNodeError struct { - NodeHash types.Hash // hash of the missing node - Path []byte // hex-encoded path to the missing node -} - -func (err *MissingNodeError) Error() string { - return fmt.Sprintf("missing trie node %x (path %x)", err.NodeHash, err.Path) -} diff --git a/trie/hasher.go b/trie/hasher.go deleted file mode 100644 index 7c63a04e4..000000000 --- a/trie/hasher.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -import ( - "bytes" - "hash" - "sync" - - "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/crypto/sha3" - "github.com/MinterTeam/minter-go-node/rlp" -) - -type hasher struct { - tmp *bytes.Buffer - sha hash.Hash - cachegen uint16 - cachelimit uint16 - onleaf LeafCallback -} - -// hashers live in a global mintdb. -var hasherPool = sync.Pool{ - New: func() interface{} { - return &hasher{tmp: new(bytes.Buffer), sha: sha3.NewKeccak256()} - }, -} - -func newHasher(cachegen, cachelimit uint16, onleaf LeafCallback) *hasher { - h := hasherPool.Get().(*hasher) - h.cachegen, h.cachelimit, h.onleaf = cachegen, cachelimit, onleaf - return h -} - -func returnHasherToPool(h *hasher) { - hasherPool.Put(h) -} - -// hash collapses a node down into a hash node, also returning a copy of the -// original node initialized with the computed hash to replace the original one. -func (h *hasher) hash(n node, db *Database, force bool) (node, node, error) { - // If we're not storing the node, just hashing, use available cached data - if hash, dirty := n.cache(); hash != nil { - if db == nil { - return hash, n, nil - } - if n.canUnload(h.cachegen, h.cachelimit) { - // Unload the node from cache. All of its subnodes will have a lower or equal - // cache generation number. - cacheUnloadCounter.Inc(1) - return hash, hash, nil - } - if !dirty { - return hash, n, nil - } - } - // Trie not processed yet or needs storage, walk the children - collapsed, cached, err := h.hashChildren(n, db) - if err != nil { - return hashNode{}, n, err - } - hashed, err := h.store(collapsed, db, force) - if err != nil { - return hashNode{}, n, err - } - // Cache the hash of the node for later reuse and remove - // the dirty flag in commit mode. It's fine to assign these values directly - // without copying the node first because hashChildren copies it. - cachedHash, _ := hashed.(hashNode) - switch cn := cached.(type) { - case *shortNode: - cn.flags.hash = cachedHash - if db != nil { - cn.flags.dirty = false - } - case *fullNode: - cn.flags.hash = cachedHash - if db != nil { - cn.flags.dirty = false - } - } - return hashed, cached, nil -} - -// hashChildren replaces the children of a node with their hashes if the encoded -// size of the child is larger than a hash, returning the collapsed node as well -// as a replacement for the original node with the child hashes cached in. -func (h *hasher) hashChildren(original node, db *Database) (node, node, error) { - var err error - - switch n := original.(type) { - case *shortNode: - // Hash the short node's child, caching the newly hashed subtree - collapsed, cached := n.copy(), n.copy() - collapsed.Key = hexToCompact(n.Key) - cached.Key = types.CopyBytes(n.Key) - - if _, ok := n.Val.(valueNode); !ok { - collapsed.Val, cached.Val, err = h.hash(n.Val, db, false) - if err != nil { - return original, original, err - } - } - if collapsed.Val == nil { - collapsed.Val = valueNode(nil) // Ensure that nil children are encoded as empty strings. - } - return collapsed, cached, nil - - case *fullNode: - // Hash the full node's children, caching the newly hashed subtrees - collapsed, cached := n.copy(), n.copy() - - for i := 0; i < 16; i++ { - if n.Children[i] != nil { - collapsed.Children[i], cached.Children[i], err = h.hash(n.Children[i], db, false) - if err != nil { - return original, original, err - } - } else { - collapsed.Children[i] = valueNode(nil) // Ensure that nil children are encoded as empty strings. - } - } - cached.Children[16] = n.Children[16] - if collapsed.Children[16] == nil { - collapsed.Children[16] = valueNode(nil) - } - return collapsed, cached, nil - - default: - // Value and hash nodes don't have children so they're left as were - return n, original, nil - } -} - -// store hashes the node n and if we have a storage layer specified, it writes -// the key/value pair to it and tracks any node->child references as well as any -// node->external trie references. -func (h *hasher) store(n node, db *Database, force bool) (node, error) { - // Don't store hashes or empty nodes. - if _, isHash := n.(hashNode); n == nil || isHash { - return n, nil - } - // Generate the RLP encoding of the node - h.tmp.Reset() - if err := rlp.Encode(h.tmp, n); err != nil { - panic("encode error: " + err.Error()) - } - if h.tmp.Len() < 32 && !force { - return n, nil // Nodes smaller than 32 bytes are stored inside their parent - } - // Larger nodes are replaced by their hash and stored in the database. - hash, _ := n.cache() - if hash == nil { - h.sha.Reset() - h.sha.Write(h.tmp.Bytes()) - hash = hashNode(h.sha.Sum(nil)) - } - if db != nil { - // We are pooling the trie nodes into an intermediate memory cache - db.lock.Lock() - - hash := types.BytesToHash(hash) - db.insert(hash, h.tmp.Bytes()) - - // Track all direct parent->child node references - switch n := n.(type) { - case *shortNode: - if child, ok := n.Val.(hashNode); ok { - db.reference(types.BytesToHash(child), hash) - } - case *fullNode: - for i := 0; i < 16; i++ { - if child, ok := n.Children[i].(hashNode); ok { - db.reference(types.BytesToHash(child), hash) - } - } - } - db.lock.Unlock() - - // Track external references from account->storage trie - if h.onleaf != nil { - switch n := n.(type) { - case *shortNode: - if child, ok := n.Val.(valueNode); ok { - h.onleaf(child, hash) - } - case *fullNode: - for i := 0; i < 16; i++ { - if child, ok := n.Children[i].(valueNode); ok { - h.onleaf(child, hash) - } - } - } - } - } - return hash, nil -} diff --git a/trie/iterator.go b/trie/iterator.go deleted file mode 100644 index a1671f838..000000000 --- a/trie/iterator.go +++ /dev/null @@ -1,528 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -import ( - "bytes" - "container/heap" - "errors" - - "github.com/MinterTeam/minter-go-node/core/types" -) - -// Iterator is a key-value trie iterator that traverses a Trie. -type Iterator struct { - nodeIt NodeIterator - - Key []byte // Current data key on which the iterator is positioned on - Value []byte // Current data value on which the iterator is positioned on - Err error -} - -// NewIterator creates a new key-value iterator from a node iterator -func NewIterator(it NodeIterator) *Iterator { - return &Iterator{ - nodeIt: it, - } -} - -// Next moves the iterator forward one key-value entry. -func (it *Iterator) Next() bool { - for it.nodeIt.Next(true) { - if it.nodeIt.Leaf() { - it.Key = it.nodeIt.LeafKey() - it.Value = it.nodeIt.LeafBlob() - return true - } - } - it.Key = nil - it.Value = nil - it.Err = it.nodeIt.Error() - return false -} - -// NodeIterator is an iterator to traverse the trie pre-order. -type NodeIterator interface { - // Next moves the iterator to the next node. If the parameter is false, any child - // nodes will be skipped. - Next(bool) bool - // Error returns the error status of the iterator. - Error() error - - // Hash returns the hash of the current node. - Hash() types.Hash - // Parent returns the hash of the parent of the current node. The hash may be the one - // grandparent if the immediate parent is an internal node with no hash. - Parent() types.Hash - // Path returns the hex-encoded path to the current node. - // Callers must not retain references to the return value after calling Next. - // For leaf nodes, the last element of the path is the 'terminator symbol' 0x10. - Path() []byte - - // Leaf returns true iff the current node is a leaf node. - // LeafBlob, LeafKey return the contents and key of the leaf node. These - // method panic if the iterator is not positioned at a leaf. - // Callers must not retain references to their return value after calling Next - Leaf() bool - LeafBlob() []byte - LeafKey() []byte -} - -// nodeIteratorState represents the iteration state at one particular node of the -// trie, which can be resumed at a later invocation. -type nodeIteratorState struct { - hash types.Hash // Hash of the node being iterated (nil if not standalone) - node node // Trie node being iterated - parent types.Hash // Hash of the first full ancestor node (nil if current is the root) - index int // Child to be processed next - pathlen int // Length of the path to this node -} - -type nodeIterator struct { - trie *Trie // Trie being iterated - stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state - path []byte // Path to the current node - err error // Failure set in case of an internal error in the iterator -} - -// iteratorEnd is stored in nodeIterator.err when iteration is done. -var iteratorEnd = errors.New("end of iteration") - -// seekError is stored in nodeIterator.err if the initial seek has failed. -type seekError struct { - key []byte - err error -} - -func (e seekError) Error() string { - return "seek error: " + e.err.Error() -} - -func newNodeIterator(trie *Trie, start []byte) NodeIterator { - if trie.Hash() == emptyState { - return new(nodeIterator) - } - it := &nodeIterator{trie: trie} - it.err = it.seek(start) - return it -} - -func (it *nodeIterator) Hash() types.Hash { - if len(it.stack) == 0 { - return types.Hash{} - } - return it.stack[len(it.stack)-1].hash -} - -func (it *nodeIterator) Parent() types.Hash { - if len(it.stack) == 0 { - return types.Hash{} - } - return it.stack[len(it.stack)-1].parent -} - -func (it *nodeIterator) Leaf() bool { - return hasTerm(it.path) -} - -func (it *nodeIterator) LeafBlob() []byte { - if len(it.stack) > 0 { - if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { - return []byte(node) - } - } - panic("not at leaf") -} - -func (it *nodeIterator) LeafKey() []byte { - if len(it.stack) > 0 { - if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { - return hexToKeybytes(it.path) - } - } - panic("not at leaf") -} - -func (it *nodeIterator) Path() []byte { - return it.path -} - -func (it *nodeIterator) Error() error { - if it.err == iteratorEnd { - return nil - } - if seek, ok := it.err.(seekError); ok { - return seek.err - } - return it.err -} - -// Next moves the iterator to the next node, returning whether there are any -// further nodes. In case of an internal error this method returns false and -// sets the Error field to the encountered failure. If `descend` is false, -// skips iterating over any subnodes of the current node. -func (it *nodeIterator) Next(descend bool) bool { - if it.err == iteratorEnd { - return false - } - if seek, ok := it.err.(seekError); ok { - if it.err = it.seek(seek.key); it.err != nil { - return false - } - } - // Otherwise step forward with the iterator and report any errors. - state, parentIndex, path, err := it.peek(descend) - it.err = err - if it.err != nil { - return false - } - it.push(state, parentIndex, path) - return true -} - -func (it *nodeIterator) seek(prefix []byte) error { - // The path we're looking for is the hex encoded key without terminator. - key := keybytesToHex(prefix) - key = key[:len(key)-1] - // Move forward until we're just before the closest match to key. - for { - state, parentIndex, path, err := it.peek(bytes.HasPrefix(key, it.path)) - if err == iteratorEnd { - return iteratorEnd - } else if err != nil { - return seekError{prefix, err} - } else if bytes.Compare(path, key) >= 0 { - return nil - } - it.push(state, parentIndex, path) - } -} - -// peek creates the next state of the iterator. -func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, error) { - if len(it.stack) == 0 { - // Initialize the iterator if we've just started. - root := it.trie.Hash() - state := &nodeIteratorState{node: it.trie.root, index: -1} - if root != emptyRoot { - state.hash = root - } - err := state.resolve(it.trie, nil) - return state, nil, nil, err - } - if !descend { - // If we're skipping children, pop the current node first - it.pop() - } - - // Continue iteration to the next child - for len(it.stack) > 0 { - parent := it.stack[len(it.stack)-1] - ancestor := parent.hash - if (ancestor == types.Hash{}) { - ancestor = parent.parent - } - state, path, ok := it.nextChild(parent, ancestor) - if ok { - if err := state.resolve(it.trie, path); err != nil { - return parent, &parent.index, path, err - } - return state, &parent.index, path, nil - } - // No more child nodes, move back up. - it.pop() - } - return nil, nil, nil, iteratorEnd -} - -func (st *nodeIteratorState) resolve(tr *Trie, path []byte) error { - if hash, ok := st.node.(hashNode); ok { - resolved, err := tr.resolveHash(hash, path) - if err != nil { - return err - } - st.node = resolved - st.hash = types.BytesToHash(hash) - } - return nil -} - -func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor types.Hash) (*nodeIteratorState, []byte, bool) { - switch node := parent.node.(type) { - case *fullNode: - // Full node, move to the first non-nil child. - for i := parent.index + 1; i < len(node.Children); i++ { - child := node.Children[i] - if child != nil { - hash, _ := child.cache() - state := &nodeIteratorState{ - hash: types.BytesToHash(hash), - node: child, - parent: ancestor, - index: -1, - pathlen: len(it.path), - } - path := append(it.path, byte(i)) - parent.index = i - 1 - return state, path, true - } - } - case *shortNode: - // Short node, return the pointer singleton child - if parent.index < 0 { - hash, _ := node.Val.cache() - state := &nodeIteratorState{ - hash: types.BytesToHash(hash), - node: node.Val, - parent: ancestor, - index: -1, - pathlen: len(it.path), - } - path := append(it.path, node.Key...) - return state, path, true - } - } - return parent, it.path, false -} - -func (it *nodeIterator) push(state *nodeIteratorState, parentIndex *int, path []byte) { - it.path = path - it.stack = append(it.stack, state) - if parentIndex != nil { - *parentIndex += 1 - } -} - -func (it *nodeIterator) pop() { - parent := it.stack[len(it.stack)-1] - it.path = it.path[:parent.pathlen] - it.stack = it.stack[:len(it.stack)-1] -} - -func compareNodes(a, b NodeIterator) int { - if cmp := bytes.Compare(a.Path(), b.Path()); cmp != 0 { - return cmp - } - if a.Leaf() && !b.Leaf() { - return -1 - } else if b.Leaf() && !a.Leaf() { - return 1 - } - if cmp := bytes.Compare(a.Hash().Bytes(), b.Hash().Bytes()); cmp != 0 { - return cmp - } - if a.Leaf() && b.Leaf() { - return bytes.Compare(a.LeafBlob(), b.LeafBlob()) - } - return 0 -} - -type differenceIterator struct { - a, b NodeIterator // Nodes returned are those in b - a. - eof bool // Indicates a has run out of elements - count int // Number of nodes scanned on either trie -} - -// NewDifferenceIterator constructs a NodeIterator that iterates over elements in b that -// are not in a. Returns the iterator, and a pointer to an integer recording the number -// of nodes seen. -func NewDifferenceIterator(a, b NodeIterator) (NodeIterator, *int) { - a.Next(true) - it := &differenceIterator{ - a: a, - b: b, - } - return it, &it.count -} - -func (it *differenceIterator) Hash() types.Hash { - return it.b.Hash() -} - -func (it *differenceIterator) Parent() types.Hash { - return it.b.Parent() -} - -func (it *differenceIterator) Leaf() bool { - return it.b.Leaf() -} - -func (it *differenceIterator) LeafBlob() []byte { - return it.b.LeafBlob() -} - -func (it *differenceIterator) LeafKey() []byte { - return it.b.LeafKey() -} - -func (it *differenceIterator) Path() []byte { - return it.b.Path() -} - -func (it *differenceIterator) Next(bool) bool { - // Invariants: - // - We always advance at least one element in b. - // - At the start of this function, a's path is lexically greater than b's. - if !it.b.Next(true) { - return false - } - it.count += 1 - - if it.eof { - // a has reached eof, so we just return all elements from b - return true - } - - for { - switch compareNodes(it.a, it.b) { - case -1: - // b jumped past a; advance a - if !it.a.Next(true) { - it.eof = true - return true - } - it.count += 1 - case 1: - // b is before a - return true - case 0: - // a and b are identical; skip this whole subtree if the nodes have hashes - hasHash := it.a.Hash() == types.Hash{} - if !it.b.Next(hasHash) { - return false - } - it.count += 1 - if !it.a.Next(hasHash) { - it.eof = true - return true - } - it.count += 1 - } - } -} - -func (it *differenceIterator) Error() error { - if err := it.a.Error(); err != nil { - return err - } - return it.b.Error() -} - -type nodeIteratorHeap []NodeIterator - -func (h nodeIteratorHeap) Len() int { return len(h) } -func (h nodeIteratorHeap) Less(i, j int) bool { return compareNodes(h[i], h[j]) < 0 } -func (h nodeIteratorHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } -func (h *nodeIteratorHeap) Push(x interface{}) { *h = append(*h, x.(NodeIterator)) } -func (h *nodeIteratorHeap) Pop() interface{} { - n := len(*h) - x := (*h)[n-1] - *h = (*h)[0 : n-1] - return x -} - -type unionIterator struct { - items *nodeIteratorHeap // Nodes returned are the union of the ones in these iterators - count int // Number of nodes scanned across all tries -} - -// NewUnionIterator constructs a NodeIterator that iterates over elements in the union -// of the provided NodeIterators. Returns the iterator, and a pointer to an integer -// recording the number of nodes visited. -func NewUnionIterator(iters []NodeIterator) (NodeIterator, *int) { - h := make(nodeIteratorHeap, len(iters)) - copy(h, iters) - heap.Init(&h) - - ui := &unionIterator{items: &h} - return ui, &ui.count -} - -func (it *unionIterator) Hash() types.Hash { - return (*it.items)[0].Hash() -} - -func (it *unionIterator) Parent() types.Hash { - return (*it.items)[0].Parent() -} - -func (it *unionIterator) Leaf() bool { - return (*it.items)[0].Leaf() -} - -func (it *unionIterator) LeafBlob() []byte { - return (*it.items)[0].LeafBlob() -} - -func (it *unionIterator) LeafKey() []byte { - return (*it.items)[0].LeafKey() -} - -func (it *unionIterator) Path() []byte { - return (*it.items)[0].Path() -} - -// Next returns the next node in the union of tries being iterated over. -// -// It does this by maintaining a heap of iterators, sorted by the iteration -// order of their next elements, with one entry for each source trie. Each -// time Next() is called, it takes the least element from the heap to return, -// advancing any other iterators that also point to that same element. These -// iterators are called with descend=false, since we know that any nodes under -// these nodes will also be duplicates, found in the currently selected iterator. -// Whenever an iterator is advanced, it is pushed back into the heap if it still -// has elements remaining. -// -// In the case that descend=false - eg, we're asked to ignore all subnodes of the -// current node - we also advance any iterators in the heap that have the current -// path as a prefix. -func (it *unionIterator) Next(descend bool) bool { - if len(*it.items) == 0 { - return false - } - - // Get the next key from the union - least := heap.Pop(it.items).(NodeIterator) - - // Skip over other nodes as long as they're identical, or, if we're not descending, as - // long as they have the same prefix as the current node. - for len(*it.items) > 0 && ((!descend && bytes.HasPrefix((*it.items)[0].Path(), least.Path())) || compareNodes(least, (*it.items)[0]) == 0) { - skipped := heap.Pop(it.items).(NodeIterator) - // Skip the whole subtree if the nodes have hashes; otherwise just skip this node - if skipped.Next(skipped.Hash() == types.Hash{}) { - it.count += 1 - // If there are more elements, push the iterator back on the heap - heap.Push(it.items, skipped) - } - } - - if least.Next(descend) { - it.count += 1 - heap.Push(it.items, least) - } - - return len(*it.items) > 0 -} - -func (it *unionIterator) Error() error { - for i := 0; i < len(*it.items); i++ { - if err := (*it.items)[i].Error(); err != nil { - return err - } - } - return nil -} diff --git a/trie/node.go b/trie/node.go deleted file mode 100644 index 53ea8c83a..000000000 --- a/trie/node.go +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -import ( - "fmt" - "io" - "strings" - - "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/rlp" -) - -var indices = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "[17]"} - -type node interface { - fstring(string) string - cache() (hashNode, bool) - canUnload(cachegen, cachelimit uint16) bool -} - -type ( - fullNode struct { - Children [17]node // Actual trie node data to encode/decode (needs custom encoder) - flags nodeFlag - } - shortNode struct { - Key []byte - Val node - flags nodeFlag - } - hashNode []byte - valueNode []byte -) - -// EncodeRLP encodes a full node into the consensus RLP format. -func (n *fullNode) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, n.Children) -} - -func (n *fullNode) copy() *fullNode { copy := *n; return © } -func (n *shortNode) copy() *shortNode { copy := *n; return © } - -// nodeFlag contains caching-related metadata about a node. -type nodeFlag struct { - hash hashNode // cached hash of the node (may be nil) - gen uint16 // cache generation counter - dirty bool // whether the node has changes that must be written to the database -} - -// canUnload tells whether a node can be unloaded. -func (n *nodeFlag) canUnload(cachegen, cachelimit uint16) bool { - return !n.dirty && cachegen-n.gen >= cachelimit -} - -func (n *fullNode) canUnload(gen, limit uint16) bool { return n.flags.canUnload(gen, limit) } -func (n *shortNode) canUnload(gen, limit uint16) bool { return n.flags.canUnload(gen, limit) } -func (n hashNode) canUnload(uint16, uint16) bool { return false } -func (n valueNode) canUnload(uint16, uint16) bool { return false } - -func (n *fullNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } -func (n *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } -func (n hashNode) cache() (hashNode, bool) { return nil, true } -func (n valueNode) cache() (hashNode, bool) { return nil, true } - -// Pretty printing. -func (n *fullNode) String() string { return n.fstring("") } -func (n *shortNode) String() string { return n.fstring("") } -func (n hashNode) String() string { return n.fstring("") } -func (n valueNode) String() string { return n.fstring("") } - -func (n *fullNode) fstring(ind string) string { - resp := fmt.Sprintf("[\n%s ", ind) - for i, node := range n.Children { - if node == nil { - resp += fmt.Sprintf("%s: ", indices[i]) - } else { - resp += fmt.Sprintf("%s: %v", indices[i], node.fstring(ind+" ")) - } - } - return resp + fmt.Sprintf("\n%s] ", ind) -} -func (n *shortNode) fstring(ind string) string { - return fmt.Sprintf("{%x: %v} ", n.Key, n.Val.fstring(ind+" ")) -} -func (n hashNode) fstring(ind string) string { - return fmt.Sprintf("<%x> ", []byte(n)) -} -func (n valueNode) fstring(ind string) string { - return fmt.Sprintf("%x ", []byte(n)) -} - -func mustDecodeNode(hash, buf []byte, cachegen uint16) node { - n, err := decodeNode(hash, buf, cachegen) - if err != nil { - panic(fmt.Sprintf("node %x: %v", hash, err)) - } - return n -} - -// decodeNode parses the RLP encoding of a trie node. -func decodeNode(hash, buf []byte, cachegen uint16) (node, error) { - if len(buf) == 0 { - return nil, io.ErrUnexpectedEOF - } - elems, _, err := rlp.SplitList(buf) - if err != nil { - return nil, fmt.Errorf("decode error: %v", err) - } - switch c, _ := rlp.CountValues(elems); c { - case 2: - n, err := decodeShort(hash, buf, elems, cachegen) - return n, wrapError(err, "short") - case 17: - n, err := decodeFull(hash, buf, elems, cachegen) - return n, wrapError(err, "full") - default: - return nil, fmt.Errorf("invalid number of list elements: %v", c) - } -} - -func decodeShort(hash, buf, elems []byte, cachegen uint16) (node, error) { - kbuf, rest, err := rlp.SplitString(elems) - if err != nil { - return nil, err - } - flag := nodeFlag{hash: hash, gen: cachegen} - key := compactToHex(kbuf) - if hasTerm(key) { - // value node - val, _, err := rlp.SplitString(rest) - if err != nil { - return nil, fmt.Errorf("invalid value node: %v", err) - } - return &shortNode{key, append(valueNode{}, val...), flag}, nil - } - r, _, err := decodeRef(rest, cachegen) - if err != nil { - return nil, wrapError(err, "val") - } - return &shortNode{key, r, flag}, nil -} - -func decodeFull(hash, buf, elems []byte, cachegen uint16) (*fullNode, error) { - n := &fullNode{flags: nodeFlag{hash: hash, gen: cachegen}} - for i := 0; i < 16; i++ { - cld, rest, err := decodeRef(elems, cachegen) - if err != nil { - return n, wrapError(err, fmt.Sprintf("[%d]", i)) - } - n.Children[i], elems = cld, rest - } - val, _, err := rlp.SplitString(elems) - if err != nil { - return n, err - } - if len(val) > 0 { - n.Children[16] = append(valueNode{}, val...) - } - return n, nil -} - -const hashLen = len(types.Hash{}) - -func decodeRef(buf []byte, cachegen uint16) (node, []byte, error) { - kind, val, rest, err := rlp.Split(buf) - if err != nil { - return nil, buf, err - } - switch { - case kind == rlp.List: - // 'embedded' node reference. The encoding must be smaller - // than a hash in order to be valid. - if size := len(buf) - len(rest); size > hashLen { - err := fmt.Errorf("oversized embedded node (size is %d bytes, want size < %d)", size, hashLen) - return nil, buf, err - } - n, err := decodeNode(nil, buf, cachegen) - return n, rest, err - case kind == rlp.String && len(val) == 0: - // empty node - return nil, rest, nil - case kind == rlp.String && len(val) == 32: - return append(hashNode{}, val...), rest, nil - default: - return nil, nil, fmt.Errorf("invalid RLP string size %d (want 0 or 32)", len(val)) - } -} - -// wraps a decoding error with information about the path to the -// invalid child node (for debugging encoding issues). -type decodeError struct { - what error - stack []string -} - -func wrapError(err error, ctx string) error { - if err == nil { - return nil - } - if decErr, ok := err.(*decodeError); ok { - decErr.stack = append(decErr.stack, ctx) - return decErr - } - return &decodeError{err, []string{ctx}} -} - -func (err *decodeError) Error() string { - return fmt.Sprintf("%v (decode path: %s)", err.what, strings.Join(err.stack, "<-")) -} diff --git a/trie/proof.go b/trie/proof.go deleted file mode 100644 index e0ea5ddce..000000000 --- a/trie/proof.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -import ( - "bytes" - "fmt" - - "github.com/MinterTeam/minter-go-node/crypto" - // "github.com/ethereum/go-ethereum/log" - "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/mintdb" - "github.com/MinterTeam/minter-go-node/rlp" -) - -// Prove constructs a merkle proof for key. The result contains all encoded nodes -// on the path to the value at key. The value itself is also included in the last -// node and can be retrieved by verifying the proof. -// -// If the trie does not contain a value for key, the returned proof contains all -// nodes of the longest existing prefix of the key (at least the root node), ending -// with the node that proves the absence of the key. -func (t *Trie) Prove(key []byte, fromLevel uint, proofDb mintdb.Putter) error { - // Collect all nodes on the path to key. - key = keybytesToHex(key) - nodes := []node{} - tn := t.root - for len(key) > 0 && tn != nil { - switch n := tn.(type) { - case *shortNode: - if len(key) < len(n.Key) || !bytes.Equal(n.Key, key[:len(n.Key)]) { - // The trie doesn't contain the key. - tn = nil - } else { - tn = n.Val - key = key[len(n.Key):] - } - nodes = append(nodes, n) - case *fullNode: - tn = n.Children[key[0]] - key = key[1:] - nodes = append(nodes, n) - case hashNode: - var err error - tn, err = t.resolveHash(n, nil) - if err != nil { - // log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) - return err - } - default: - panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) - } - } - hasher := newHasher(0, 0, nil) - for i, n := range nodes { - // Don't bother checking for errors here since hasher panics - // if encoding doesn't work and we're not writing to any database. - n, _, _ = hasher.hashChildren(n, nil) - hn, _ := hasher.store(n, nil, false) - if hash, ok := hn.(hashNode); ok || i == 0 { - // If the node's database encoding is a hash (or is the - // root node), it becomes a proof element. - if fromLevel > 0 { - fromLevel-- - } else { - enc, _ := rlp.EncodeToBytes(n) - if !ok { - hash = crypto.Keccak256(enc) - } - proofDb.Put(hash, enc) - } - } - } - return nil -} - -// Prove constructs a merkle proof for key. The result contains all encoded nodes -// on the path to the value at key. The value itself is also included in the last -// node and can be retrieved by verifying the proof. -// -// If the trie does not contain a value for key, the returned proof contains all -// nodes of the longest existing prefix of the key (at least the root node), ending -// with the node that proves the absence of the key. -func (t *SecureTrie) Prove(key []byte, fromLevel uint, proofDb mintdb.Putter) error { - return t.trie.Prove(key, fromLevel, proofDb) -} - -// VerifyProof checks merkle proofs. The given proof must contain the value for -// key in a trie with the given root hash. VerifyProof returns an error if the -// proof contains invalid trie nodes or the wrong value. -func VerifyProof(rootHash types.Hash, key []byte, proofDb DatabaseReader) (value []byte, err error, nodes int) { - key = keybytesToHex(key) - wantHash := rootHash - for i := 0; ; i++ { - buf, _ := proofDb.Get(wantHash[:]) - if buf == nil { - return nil, fmt.Errorf("proof node %d (hash %064x) missing", i, wantHash), i - } - n, err := decodeNode(wantHash[:], buf, 0) - if err != nil { - return nil, fmt.Errorf("bad proof node %d: %v", i, err), i - } - keyrest, cld := get(n, key) - switch cld := cld.(type) { - case nil: - // The trie doesn't contain the key. - return nil, nil, i - case hashNode: - key = keyrest - copy(wantHash[:], cld) - case valueNode: - return cld, nil, i + 1 - } - } -} - -func get(tn node, key []byte) ([]byte, node) { - for { - switch n := tn.(type) { - case *shortNode: - if len(key) < len(n.Key) || !bytes.Equal(n.Key, key[:len(n.Key)]) { - return nil, nil - } - tn = n.Val - key = key[len(n.Key):] - case *fullNode: - tn = n.Children[key[0]] - key = key[1:] - case hashNode: - return key, n - case nil: - return key, nil - case valueNode: - return nil, n - default: - panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) - } - } -} diff --git a/trie/secure_trie.go b/trie/secure_trie.go deleted file mode 100644 index 78e3fa49a..000000000 --- a/trie/secure_trie.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -import ( - // "github.com/ethereum/go-ethereum/log" - "github.com/MinterTeam/minter-go-node/core/types" -) - -// SecureTrie wraps a trie with key hashing. In a secure trie, all -// access operations hash the key using keccak256. This prevents -// calling code from creating long chains of nodes that -// increase the access time. -// -// Contrary to a regular trie, a SecureTrie can only be created with -// New and must have an attached database. The database also stores -// the preimage of each key. -// -// SecureTrie is not safe for concurrent use. -type SecureTrie struct { - trie Trie - hashKeyBuf [types.HashLength]byte - secKeyCache map[string][]byte - secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch -} - -// NewSecure creates a trie with an existing root node from a backing database -// and optional intermediate in-memory node pool. -// -// If root is the zero hash or the sha3 hash of an empty string, the -// trie is initially empty. Otherwise, New will panic if mintdb is nil -// and returns MissingNodeError if the root node cannot be found. -// -// Accessing the trie loads nodes from the database or node pool on demand. -// Loaded nodes are kept around until their 'cache generation' expires. -// A new cache generation is created by each call to Commit. -// cachelimit sets the number of past cache generations to keep. -func NewSecure(root types.Hash, db *Database, cachelimit uint16) (*SecureTrie, error) { - if db == nil { - panic("trie.NewSecure called without a database") - } - trie, err := New(root, db) - if err != nil { - return nil, err - } - trie.SetCacheLimit(cachelimit) - return &SecureTrie{trie: *trie}, nil -} - -// Get returns the value for key stored in the trie. -// The value bytes must not be modified by the caller. -func (t *SecureTrie) Get(key []byte) []byte { - res, err := t.TryGet(key) - if err != nil { - // log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) - } - return res -} - -// TryGet returns the value for key stored in the trie. -// The value bytes must not be modified by the caller. -// If a node was not found in the database, a MissingNodeError is returned. -func (t *SecureTrie) TryGet(key []byte) ([]byte, error) { - return t.trie.TryGet(t.hashKey(key)) -} - -// Update associates key with value in the trie. Subsequent calls to -// Get will return value. If value has length zero, any existing value -// is deleted from the trie and calls to Get will return nil. -// -// The value bytes must not be modified by the caller while they are -// stored in the trie. -func (t *SecureTrie) Update(key, value []byte) { - if err := t.TryUpdate(key, value); err != nil { - // log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) - } -} - -// TryUpdate associates key with value in the trie. Subsequent calls to -// Get will return value. If value has length zero, any existing value -// is deleted from the trie and calls to Get will return nil. -// -// The value bytes must not be modified by the caller while they are -// stored in the trie. -// -// If a node was not found in the database, a MissingNodeError is returned. -func (t *SecureTrie) TryUpdate(key, value []byte) error { - hk := t.hashKey(key) - err := t.trie.TryUpdate(hk, value) - if err != nil { - return err - } - t.getSecKeyCache()[string(hk)] = types.CopyBytes(key) - return nil -} - -// Delete removes any existing value for key from the trie. -func (t *SecureTrie) Delete(key []byte) { - if err := t.TryDelete(key); err != nil { - // log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) - } -} - -// TryDelete removes any existing value for key from the trie. -// If a node was not found in the database, a MissingNodeError is returned. -func (t *SecureTrie) TryDelete(key []byte) error { - hk := t.hashKey(key) - delete(t.getSecKeyCache(), string(hk)) - return t.trie.TryDelete(hk) -} - -// GetKey returns the sha3 preimage of a hashed key that was -// previously used to store a value. -func (t *SecureTrie) GetKey(shaKey []byte) []byte { - if key, ok := t.getSecKeyCache()[string(shaKey)]; ok { - return key - } - key, _ := t.trie.db.preimage(types.BytesToHash(shaKey)) - return key -} - -// Commit writes all nodes and the secure hash pre-images to the trie's database. -// Nodes are stored with their sha3 hash as the key. -// -// Committing flushes nodes from memory. Subsequent Get calls will load nodes -// from the database. -func (t *SecureTrie) Commit(onleaf LeafCallback) (root types.Hash, err error) { - // Write all the pre-images to the actual disk database - if len(t.getSecKeyCache()) > 0 { - t.trie.db.lock.Lock() - for hk, key := range t.secKeyCache { - t.trie.db.insertPreimage(types.BytesToHash([]byte(hk)), key) - } - t.trie.db.lock.Unlock() - - t.secKeyCache = make(map[string][]byte) - } - // Commit the trie to its intermediate node database - return t.trie.Commit(onleaf) -} - -func (t *SecureTrie) Hash() types.Hash { - return t.trie.Hash() -} - -func (t *SecureTrie) Root() []byte { - return t.trie.Root() -} - -func (t *SecureTrie) Copy() *SecureTrie { - cpy := *t - return &cpy -} - -// NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration -// starts at the key after the given start key. -func (t *SecureTrie) NodeIterator(start []byte) NodeIterator { - return t.trie.NodeIterator(start) -} - -// hashKey returns the hash of key as an ephemeral buffer. -// The caller must not hold onto the return value because it will become -// invalid on the next call to hashKey or secKey. -func (t *SecureTrie) hashKey(key []byte) []byte { - h := newHasher(0, 0, nil) - h.sha.Reset() - h.sha.Write(key) - buf := h.sha.Sum(t.hashKeyBuf[:0]) - returnHasherToPool(h) - return buf -} - -// getSecKeyCache returns the current secure key cache, creating a new one if -// ownership changed (i.e. the current secure trie is a copy of another owning -// the actual cache). -func (t *SecureTrie) getSecKeyCache() map[string][]byte { - if t != t.secKeyCacheOwner { - t.secKeyCacheOwner = t - t.secKeyCache = make(map[string][]byte) - } - return t.secKeyCache -} diff --git a/trie/sync.go b/trie/sync.go deleted file mode 100644 index 978d336de..000000000 --- a/trie/sync.go +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -import ( - "errors" - "fmt" - - "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/mintdb" - "gopkg.in/karalabe/cookiejar.v2/collections/prque" -) - -// ErrNotRequested is returned by the trie sync when it's requested to process a -// node it did not request. -var ErrNotRequested = errors.New("not requested") - -// ErrAlreadyProcessed is returned by the trie sync when it's requested to process a -// node it already processed previously. -var ErrAlreadyProcessed = errors.New("already processed") - -// request represents a scheduled or already in-flight state retrieval request. -type request struct { - hash types.Hash // Hash of the node data content to retrieve - data []byte // Data content of the node, cached until all subtrees complete - raw bool // Whether this is a raw entry (code) or a trie node - - parents []*request // Parent state nodes referencing this entry (notify all upon completion) - depth int // Depth level within the trie the node is located to prioritise DFS - deps int // Number of dependencies before allowed to commit this node - - callback LeafCallback // Callback to invoke if a leaf node it reached on this branch -} - -// SyncResult is a simple list to return missing nodes along with their request -// hashes. -type SyncResult struct { - Hash types.Hash // Hash of the originally unknown trie node - Data []byte // Data content of the retrieved node -} - -// syncMemBatch is an in-memory buffer of successfully downloaded but not yet -// persisted data items. -type syncMemBatch struct { - batch map[types.Hash][]byte // In-memory membatch of recently completed items - order []types.Hash // Order of completion to prevent out-of-order data loss -} - -// newSyncMemBatch allocates a new memory-buffer for not-yet persisted trie nodes. -func newSyncMemBatch() *syncMemBatch { - return &syncMemBatch{ - batch: make(map[types.Hash][]byte), - order: make([]types.Hash, 0, 256), - } -} - -// TrieSync is the main state trie synchronisation scheduler, which provides yet -// unknown trie hashes to retrieve, accepts node data associated with said hashes -// and reconstructs the trie step by step until all is done. -type TrieSync struct { - database DatabaseReader // Persistent database to check for existing entries - membatch *syncMemBatch // Memory buffer to avoid frequest database writes - requests map[types.Hash]*request // Pending requests pertaining to a key hash - queue *prque.Prque // Priority queue with the pending requests -} - -// NewTrieSync creates a new trie data download scheduler. -func NewTrieSync(root types.Hash, database DatabaseReader, callback LeafCallback) *TrieSync { - ts := &TrieSync{ - database: database, - membatch: newSyncMemBatch(), - requests: make(map[types.Hash]*request), - queue: prque.New(), - } - ts.AddSubTrie(root, 0, types.Hash{}, callback) - return ts -} - -// AddSubTrie registers a new trie to the sync code, rooted at the designated parent. -func (s *TrieSync) AddSubTrie(root types.Hash, depth int, parent types.Hash, callback LeafCallback) { - // Short circuit if the trie is empty or already known - if root == emptyRoot { - return - } - if _, ok := s.membatch.batch[root]; ok { - return - } - key := root.Bytes() - blob, _ := s.database.Get(key) - if local, err := decodeNode(key, blob, 0); local != nil && err == nil { - return - } - // Assemble the new sub-trie sync request - req := &request{ - hash: root, - depth: depth, - callback: callback, - } - // If this sub-trie has a designated parent, link them together - if parent != (types.Hash{}) { - ancestor := s.requests[parent] - if ancestor == nil { - panic(fmt.Sprintf("sub-trie ancestor not found: %x", parent)) - } - ancestor.deps++ - req.parents = append(req.parents, ancestor) - } - s.schedule(req) -} - -// AddRawEntry schedules the direct retrieval of a state entry that should not be -// interpreted as a trie node, but rather accepted and stored into the database -// as is. This method's goal is to support misc state metadata retrievals (e.g. -// contract code). -func (s *TrieSync) AddRawEntry(hash types.Hash, depth int, parent types.Hash) { - // Short circuit if the entry is empty or already known - if hash == emptyState { - return - } - if _, ok := s.membatch.batch[hash]; ok { - return - } - if ok, _ := s.database.Has(hash.Bytes()); ok { - return - } - // Assemble the new sub-trie sync request - req := &request{ - hash: hash, - raw: true, - depth: depth, - } - // If this sub-trie has a designated parent, link them together - if parent != (types.Hash{}) { - ancestor := s.requests[parent] - if ancestor == nil { - panic(fmt.Sprintf("raw-entry ancestor not found: %x", parent)) - } - ancestor.deps++ - req.parents = append(req.parents, ancestor) - } - s.schedule(req) -} - -// Missing retrieves the known missing nodes from the trie for retrieval. -func (s *TrieSync) Missing(max int) []types.Hash { - requests := []types.Hash{} - for !s.queue.Empty() && (max == 0 || len(requests) < max) { - requests = append(requests, s.queue.PopItem().(types.Hash)) - } - return requests -} - -// Process injects a batch of retrieved trie nodes data, returning if something -// was committed to the database and also the index of an entry if processing of -// it failed. -func (s *TrieSync) Process(results []SyncResult) (bool, int, error) { - committed := false - - for i, item := range results { - // If the item was not requested, bail out - request := s.requests[item.Hash] - if request == nil { - return committed, i, ErrNotRequested - } - if request.data != nil { - return committed, i, ErrAlreadyProcessed - } - // If the item is a raw entry request, commit directly - if request.raw { - request.data = item.Data - s.commit(request) - committed = true - continue - } - // Decode the node data content and update the request - node, err := decodeNode(item.Hash[:], item.Data, 0) - if err != nil { - return committed, i, err - } - request.data = item.Data - - // Create and schedule a request for all the children nodes - requests, err := s.children(request, node) - if err != nil { - return committed, i, err - } - if len(requests) == 0 && request.deps == 0 { - s.commit(request) - committed = true - continue - } - request.deps += len(requests) - for _, child := range requests { - s.schedule(child) - } - } - return committed, 0, nil -} - -// Commit flushes the data stored in the internal membatch out to persistent -// storage, returning th enumber of items written and any occurred error. -func (s *TrieSync) Commit(dbw mintdb.Putter) (int, error) { - // Dump the membatch into a database dbw - for i, key := range s.membatch.order { - if err := dbw.Put(key[:], s.membatch.batch[key]); err != nil { - return i, err - } - } - written := len(s.membatch.order) - - // Drop the membatch data and return - s.membatch = newSyncMemBatch() - return written, nil -} - -// Pending returns the number of state entries currently pending for download. -func (s *TrieSync) Pending() int { - return len(s.requests) -} - -// schedule inserts a new state retrieval request into the fetch queue. If there -// is already a pending request for this node, the new request will be discarded -// and only a parent reference added to the old one. -func (s *TrieSync) schedule(req *request) { - // If we're already requesting this node, add a new reference and stop - if old, ok := s.requests[req.hash]; ok { - old.parents = append(old.parents, req.parents...) - return - } - // Schedule the request for future retrieval - s.queue.Push(req.hash, float32(req.depth)) - s.requests[req.hash] = req -} - -// children retrieves all the missing children of a state trie entry for future -// retrieval scheduling. -func (s *TrieSync) children(req *request, object node) ([]*request, error) { - // Gather all the children of the node, irrelevant whether known or not - type child struct { - node node - depth int - } - children := []child{} - - switch node := (object).(type) { - case *shortNode: - children = []child{{ - node: node.Val, - depth: req.depth + len(node.Key), - }} - case *fullNode: - for i := 0; i < 17; i++ { - if node.Children[i] != nil { - children = append(children, child{ - node: node.Children[i], - depth: req.depth + 1, - }) - } - } - default: - panic(fmt.Sprintf("unknown node: %+v", node)) - } - // Iterate over the children, and request all unknown ones - requests := make([]*request, 0, len(children)) - for _, child := range children { - // Notify any external watcher of a new key/value node - if req.callback != nil { - if node, ok := (child.node).(valueNode); ok { - if err := req.callback(node, req.hash); err != nil { - return nil, err - } - } - } - // If the child references another node, resolve or schedule - if node, ok := (child.node).(hashNode); ok { - // Try to resolve the node from the local database - hash := types.BytesToHash(node) - if _, ok := s.membatch.batch[hash]; ok { - continue - } - if ok, _ := s.database.Has(node); ok { - continue - } - // Locally unknown node, schedule for retrieval - requests = append(requests, &request{ - hash: hash, - parents: []*request{req}, - depth: child.depth, - callback: req.callback, - }) - } - } - return requests, nil -} - -// commit finalizes a retrieval request and stores it into the membatch. If any -// of the referencing parent requests complete due to this commit, they are also -// committed themselves. -func (s *TrieSync) commit(req *request) (err error) { - // Write the node content to the membatch - s.membatch.batch[req.hash] = req.data - s.membatch.order = append(s.membatch.order, req.hash) - - delete(s.requests, req.hash) - - // Check all parents for completion - for _, parent := range req.parents { - parent.deps-- - if parent.deps == 0 { - if err := s.commit(parent); err != nil { - return err - } - } - } - return nil -} diff --git a/trie/trie.go b/trie/trie.go deleted file mode 100644 index 807db004b..000000000 --- a/trie/trie.go +++ /dev/null @@ -1,478 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package trie implements Merkle Patricia Tries. -package trie - -import ( - "bytes" - "fmt" - - // "github.com/ethereum/go-ethereum/log" - "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/crypto" - "github.com/rcrowley/go-metrics" -) - -var ( - // emptyRoot is the known root hash of an empty trie. - emptyRoot = types.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") - - // emptyState is the known hash of an empty state trie entry. - emptyState = crypto.Keccak256Hash(nil) -) - -var ( - cacheMissCounter = metrics.NewRegisteredCounter("trie/cachemiss", nil) - cacheUnloadCounter = metrics.NewRegisteredCounter("trie/cacheunload", nil) -) - -// CacheMisses retrieves a global counter measuring the number of cache misses -// the trie had since process startup. This isn't useful for anything apart from -// trie debugging purposes. -func CacheMisses() int64 { - return cacheMissCounter.Count() -} - -// CacheUnloads retrieves a global counter measuring the number of cache unloads -// the trie did since process startup. This isn't useful for anything apart from -// trie debugging purposes. -func CacheUnloads() int64 { - return cacheUnloadCounter.Count() -} - -// LeafCallback is a callback type invoked when a trie operation reaches a leaf -// node. It's used by state sync and commit to allow handling external references -// between account and storage tries. -type LeafCallback func(leaf []byte, parent types.Hash) error - -// Trie is a Merkle Patricia Trie. -// The zero value is an empty trie with no database. -// Use New to create a trie that sits on top of a database. -// -// Trie is not safe for concurrent use. -type Trie struct { - db *Database - root node - originalRoot types.Hash - - // Cache generation values. - // cachegen increases by one with each commit operation. - // new nodes are tagged with the current generation and unloaded - // when their generation is older than than cachegen-cachelimit. - cachegen, cachelimit uint16 -} - -// SetCacheLimit sets the number of 'cache generations' to keep. -// A cache generation is created by a call to Commit. -func (t *Trie) SetCacheLimit(l uint16) { - t.cachelimit = l -} - -// newFlag returns the cache flag value for a newly created node. -func (t *Trie) newFlag() nodeFlag { - return nodeFlag{dirty: true, gen: t.cachegen} -} - -// New creates a trie with an existing root node from mintdb. -// -// If root is the zero hash or the sha3 hash of an empty string, the -// trie is initially empty and does not require a database. Otherwise, -// New will panic if mintdb is nil and returns a MissingNodeError if root does -// not exist in the database. Accessing the trie loads nodes from mintdb on demand. -func New(root types.Hash, db *Database) (*Trie, error) { - if db == nil { - panic("trie.New called without a database") - } - trie := &Trie{ - db: db, - originalRoot: root, - } - if (root != types.Hash{}) && root != emptyRoot { - rootnode, err := trie.resolveHash(root[:], nil) - if err != nil { - return nil, err - } - trie.root = rootnode - } - return trie, nil -} - -// NodeIterator returns an iterator that returns nodes of the trie. Iteration starts at -// the key after the given start key. -func (t *Trie) NodeIterator(start []byte) NodeIterator { - return newNodeIterator(t, start) -} - -// Get returns the value for key stored in the trie. -// The value bytes must not be modified by the caller. -func (t *Trie) Get(key []byte) []byte { - res, err := t.TryGet(key) - if err != nil { - // log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) - } - return res -} - -// TryGet returns the value for key stored in the trie. -// The value bytes must not be modified by the caller. -// If a node was not found in the database, a MissingNodeError is returned. -func (t *Trie) TryGet(key []byte) ([]byte, error) { - key = keybytesToHex(key) - value, newroot, didResolve, err := t.tryGet(t.root, key, 0) - if err == nil && didResolve { - t.root = newroot - } - return value, err -} - -func (t *Trie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode node, didResolve bool, err error) { - switch n := (origNode).(type) { - case nil: - return nil, nil, false, nil - case valueNode: - return n, n, false, nil - case *shortNode: - if len(key)-pos < len(n.Key) || !bytes.Equal(n.Key, key[pos:pos+len(n.Key)]) { - // key not found in trie - return nil, n, false, nil - } - value, newnode, didResolve, err = t.tryGet(n.Val, key, pos+len(n.Key)) - if err == nil && didResolve { - n = n.copy() - n.Val = newnode - n.flags.gen = t.cachegen - } - return value, n, didResolve, err - case *fullNode: - value, newnode, didResolve, err = t.tryGet(n.Children[key[pos]], key, pos+1) - if err == nil && didResolve { - n = n.copy() - n.flags.gen = t.cachegen - n.Children[key[pos]] = newnode - } - return value, n, didResolve, err - case hashNode: - child, err := t.resolveHash(n, key[:pos]) - if err != nil { - return nil, n, true, err - } - value, newnode, _, err := t.tryGet(child, key, pos) - return value, newnode, true, err - default: - panic(fmt.Sprintf("%T: invalid node: %v", origNode, origNode)) - } -} - -// Update associates key with value in the trie. Subsequent calls to -// Get will return value. If value has length zero, any existing value -// is deleted from the trie and calls to Get will return nil. -// -// The value bytes must not be modified by the caller while they are -// stored in the trie. -func (t *Trie) Update(key, value []byte) { - if err := t.TryUpdate(key, value); err != nil { - // log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) - } -} - -// TryUpdate associates key with value in the trie. Subsequent calls to -// Get will return value. If value has length zero, any existing value -// is deleted from the trie and calls to Get will return nil. -// -// The value bytes must not be modified by the caller while they are -// stored in the trie. -// -// If a node was not found in the database, a MissingNodeError is returned. -func (t *Trie) TryUpdate(key, value []byte) error { - k := keybytesToHex(key) - if len(value) != 0 { - _, n, err := t.insert(t.root, nil, k, valueNode(value)) - if err != nil { - return err - } - t.root = n - } else { - _, n, err := t.delete(t.root, nil, k) - if err != nil { - return err - } - t.root = n - } - return nil -} - -func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error) { - if len(key) == 0 { - if v, ok := n.(valueNode); ok { - return !bytes.Equal(v, value.(valueNode)), value, nil - } - return true, value, nil - } - switch n := n.(type) { - case *shortNode: - matchlen := prefixLen(key, n.Key) - // If the whole key matches, keep this short node as is - // and only update the value. - if matchlen == len(n.Key) { - dirty, nn, err := t.insert(n.Val, append(prefix, key[:matchlen]...), key[matchlen:], value) - if !dirty || err != nil { - return false, n, err - } - return true, &shortNode{n.Key, nn, t.newFlag()}, nil - } - // Otherwise branch out at the index where they differ. - branch := &fullNode{flags: t.newFlag()} - var err error - _, branch.Children[n.Key[matchlen]], err = t.insert(nil, append(prefix, n.Key[:matchlen+1]...), n.Key[matchlen+1:], n.Val) - if err != nil { - return false, nil, err - } - _, branch.Children[key[matchlen]], err = t.insert(nil, append(prefix, key[:matchlen+1]...), key[matchlen+1:], value) - if err != nil { - return false, nil, err - } - // Replace this shortNode with the branch if it occurs at index 0. - if matchlen == 0 { - return true, branch, nil - } - // Otherwise, replace it with a short node leading up to the branch. - return true, &shortNode{key[:matchlen], branch, t.newFlag()}, nil - - case *fullNode: - dirty, nn, err := t.insert(n.Children[key[0]], append(prefix, key[0]), key[1:], value) - if !dirty || err != nil { - return false, n, err - } - n = n.copy() - n.flags = t.newFlag() - n.Children[key[0]] = nn - return true, n, nil - - case nil: - return true, &shortNode{key, value, t.newFlag()}, nil - - case hashNode: - // We've hit a part of the trie that isn't loaded yet. Load - // the node and insert into it. This leaves all child nodes on - // the path to the value in the trie. - rn, err := t.resolveHash(n, prefix) - if err != nil { - return false, nil, err - } - dirty, nn, err := t.insert(rn, prefix, key, value) - if !dirty || err != nil { - return false, rn, err - } - return true, nn, nil - - default: - panic(fmt.Sprintf("%T: invalid node: %v", n, n)) - } -} - -// Delete removes any existing value for key from the trie. -func (t *Trie) Delete(key []byte) { - if err := t.TryDelete(key); err != nil { - // log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) - } -} - -// TryDelete removes any existing value for key from the trie. -// If a node was not found in the database, a MissingNodeError is returned. -func (t *Trie) TryDelete(key []byte) error { - k := keybytesToHex(key) - _, n, err := t.delete(t.root, nil, k) - if err != nil { - return err - } - t.root = n - return nil -} - -// delete returns the new root of the trie with key deleted. -// It reduces the trie to minimal form by simplifying -// nodes on the way up after deleting recursively. -func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { - switch n := n.(type) { - case *shortNode: - matchlen := prefixLen(key, n.Key) - if matchlen < len(n.Key) { - return false, n, nil // don't replace n on mismatch - } - if matchlen == len(key) { - return true, nil, nil // remove n entirely for whole matches - } - // The key is longer than n.Key. Remove the remaining suffix - // from the subtrie. Child can never be nil here since the - // subtrie must contain at least two other values with keys - // longer than n.Key. - dirty, child, err := t.delete(n.Val, append(prefix, key[:len(n.Key)]...), key[len(n.Key):]) - if !dirty || err != nil { - return false, n, err - } - switch child := child.(type) { - case *shortNode: - // Deleting from the subtrie reduced it to another - // short node. Merge the nodes to avoid creating a - // shortNode{..., shortNode{...}}. Use concat (which - // always creates a new slice) instead of append to - // avoid modifying n.Key since it might be shared with - // other nodes. - return true, &shortNode{concat(n.Key, child.Key...), child.Val, t.newFlag()}, nil - default: - return true, &shortNode{n.Key, child, t.newFlag()}, nil - } - - case *fullNode: - dirty, nn, err := t.delete(n.Children[key[0]], append(prefix, key[0]), key[1:]) - if !dirty || err != nil { - return false, n, err - } - n = n.copy() - n.flags = t.newFlag() - n.Children[key[0]] = nn - - // Check how many non-nil entries are left after deleting and - // reduce the full node to a short node if only one entry is - // left. Since n must've contained at least two children - // before deletion (otherwise it would not be a full node) n - // can never be reduced to nil. - // - // When the loop is done, pos contains the index of the single - // value that is left in n or -2 if n contains at least two - // values. - pos := -1 - for i, cld := range n.Children { - if cld != nil { - if pos == -1 { - pos = i - } else { - pos = -2 - break - } - } - } - if pos >= 0 { - if pos != 16 { - // If the remaining entry is a short node, it replaces - // n and its key gets the missing nibble tacked to the - // front. This avoids creating an invalid - // shortNode{..., shortNode{...}}. Since the entry - // might not be loaded yet, resolve it just for this - // check. - cnode, err := t.resolve(n.Children[pos], prefix) - if err != nil { - return false, nil, err - } - if cnode, ok := cnode.(*shortNode); ok { - k := append([]byte{byte(pos)}, cnode.Key...) - return true, &shortNode{k, cnode.Val, t.newFlag()}, nil - } - } - // Otherwise, n is replaced by a one-nibble short node - // containing the child. - return true, &shortNode{[]byte{byte(pos)}, n.Children[pos], t.newFlag()}, nil - } - // n still contains at least two values and cannot be reduced. - return true, n, nil - - case valueNode: - return true, nil, nil - - case nil: - return false, nil, nil - - case hashNode: - // We've hit a part of the trie that isn't loaded yet. Load - // the node and delete from it. This leaves all child nodes on - // the path to the value in the trie. - rn, err := t.resolveHash(n, prefix) - if err != nil { - return false, nil, err - } - dirty, nn, err := t.delete(rn, prefix, key) - if !dirty || err != nil { - return false, rn, err - } - return true, nn, nil - - default: - panic(fmt.Sprintf("%T: invalid node: %v (%v)", n, n, key)) - } -} - -func concat(s1 []byte, s2 ...byte) []byte { - r := make([]byte, len(s1)+len(s2)) - copy(r, s1) - copy(r[len(s1):], s2) - return r -} - -func (t *Trie) resolve(n node, prefix []byte) (node, error) { - if n, ok := n.(hashNode); ok { - return t.resolveHash(n, prefix) - } - return n, nil -} - -func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) { - cacheMissCounter.Inc(1) - - hash := types.BytesToHash(n) - - enc, err := t.db.Node(hash) - if err != nil || enc == nil { - return nil, &MissingNodeError{NodeHash: hash, Path: prefix} - } - return mustDecodeNode(n, enc, t.cachegen), nil -} - -// Root returns the root hash of the trie. -// Deprecated: use Hash instead. -func (t *Trie) Root() []byte { return t.Hash().Bytes() } - -// Hash returns the root hash of the trie. It does not write to the -// database and can be used even if the trie doesn't have one. -func (t *Trie) Hash() types.Hash { - hash, cached, _ := t.hashRoot(nil, nil) - t.root = cached - return types.BytesToHash(hash.(hashNode)) -} - -// Commit writes all nodes to the trie's memory database, tracking the internal -// and external (for account tries) references. -func (t *Trie) Commit(onleaf LeafCallback) (root types.Hash, err error) { - if t.db == nil { - panic("commit called on trie with nil database") - } - hash, cached, err := t.hashRoot(t.db, onleaf) - if err != nil { - return types.Hash{}, err - } - t.root = cached - t.cachegen++ - return types.BytesToHash(hash.(hashNode)), nil -} - -func (t *Trie) hashRoot(db *Database, onleaf LeafCallback) (node, node, error) { - if t.root == nil { - return hashNode(emptyRoot.Bytes()), nil, nil - } - h := newHasher(t.cachegen, t.cachelimit, onleaf) - defer returnHasherToPool(h) - return h.hash(t.root, db, true) -} From f44ee04b5a68cd6b5a0f0513341ec3efa2ca817c Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 14:04:42 +0300 Subject: [PATCH 02/29] change trie to iavl --- Gopkg.lock | 3 +- core/minter/minter.go | 89 +++++-------- core/state/state_object.go | 95 -------------- core/state/statedb.go | 14 +- eventsdb/eventsdb.go | 25 ++-- genesis/genesis.go | 10 +- mintdb/.gitignore | 12 -- mintdb/database.go | 263 ------------------------------------- mintdb/interface.go | 46 ------- 9 files changed, 52 insertions(+), 505 deletions(-) delete mode 100644 mintdb/.gitignore delete mode 100644 mintdb/database.go delete mode 100644 mintdb/interface.go diff --git a/Gopkg.lock b/Gopkg.lock index 8bfeb8213..09f4102c3 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -224,7 +224,6 @@ revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a" [[projects]] - branch = "master" name = "github.com/rcrowley/go-metrics" packages = ["."] revision = "e2704e165165ec55d062f5919b4b29494e9fa790" @@ -478,6 +477,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "7486c859e8417d585f25054ac2b5adfc43dc7c13d99a7c88ad60d4bafdd5c20d" + inputs-digest = "26b32a8b58c383d7115a417db7eb0e0284ee7de7555ef7b097bcfffd52709320" solver-name = "gps-cdcl" solver-version = 1 diff --git a/core/minter/minter.go b/core/minter/minter.go index 8aa4185be..4d386ef7b 100644 --- a/core/minter/minter.go +++ b/core/minter/minter.go @@ -14,10 +14,9 @@ import ( "github.com/MinterTeam/minter-go-node/genesis" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/log" - "github.com/MinterTeam/minter-go-node/mintdb" - "github.com/syndtr/goleveldb/leveldb" "github.com/tendermint/go-amino" abciTypes "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/node" rpc "github.com/tendermint/tendermint/rpc/client" "math/big" @@ -27,10 +26,12 @@ import ( type Blockchain struct { abciTypes.BaseApplication - db *mintdb.LDBDatabase + stateDB db.DB + appDB db.DB stateDeliver *state.StateDB stateCheck *state.StateDB rootHash types.Hash + dbVersion int64 height uint64 rewards *big.Int validatorsStatuses map[[20]byte]int8 @@ -42,9 +43,6 @@ type Blockchain struct { const ( ValidatorPresent = 1 ValidatorAbsent = 2 - - stateTableId = "state" - appTableId = "app" ) var ( @@ -53,15 +51,15 @@ var ( ) func NewMinterBlockchain() *Blockchain { - - db, err := mintdb.NewLDBDatabase(utils.GetMinterHome()+"/data", 1024, 512) + ldb, err := db.NewGoLevelDB("minter", utils.GetMinterHome()+"/data") if err != nil { panic(err) } blockchain = &Blockchain{ - db: db, + stateDB: db.NewPrefixDB(ldb, []byte("s")), + appDB: db.NewPrefixDB(ldb, []byte("a")), BaseCoin: types.GetBaseCoin(), } @@ -309,34 +307,27 @@ func (app *Blockchain) CheckTx(rawTx []byte) abciTypes.ResponseCheckTx { func (app *Blockchain) Commit() abciTypes.ResponseCommit { - hash, err := app.stateDeliver.Commit(false) + hash, version, err := app.stateDeliver.Commit(false) if err != nil { panic(err) } - err = app.stateDeliver.Database().TrieDB().Commit(hash, true) - if err != nil { panic(err) } - // todo: make provider - appTable := mintdb.NewTable(app.db, appTableId) - err = appTable.Put([]byte("root"), hash.Bytes()) - - if err != nil { - panic(err) - } + app.appDB.Set([]byte("root"), hash.Bytes()) // todo: make provider height := make([]byte, 8) binary.BigEndian.PutUint64(height, app.height) - err = appTable.Put([]byte("height"), height) + app.appDB.Set([]byte("height"), height) - if err != nil { - panic(err) - } + // todo: make provider + versionBytes := make([]byte, 8) + binary.BigEndian.PutUint64(versionBytes, uint64(version)) + app.appDB.Set([]byte("version"), versionBytes) // TODO: clear candidates list @@ -353,34 +344,42 @@ func (app *Blockchain) Query(reqQuery abciTypes.RequestQuery) abciTypes.Response } func (app *Blockchain) Stop() { - app.db.Close() + app.appDB.Close() + app.stateDB.Close() } func (app *Blockchain) updateCurrentRootHash() { - appTable := mintdb.NewTable(app.db, appTableId) // todo: make provider - result, _ := appTable.Get([]byte("root")) + result := app.appDB.Get([]byte("root")) app.rootHash = types.BytesToHash(result) // todo: make provider - result, err := appTable.Get([]byte("height")) - if err == nil { + result = app.appDB.Get([]byte("height")) + if result != nil { app.height = binary.BigEndian.Uint64(result) } else { app.height = 0 } + + // todo: make provider + result = app.appDB.Get([]byte("version")) + if result != nil { + app.dbVersion = int64(binary.BigEndian.Uint64(result)) + } else { + app.dbVersion = 0 + } } func (app *Blockchain) updateCurrentState() { var err error - app.stateDeliver, err = state.New(app.rootHash, state.NewDatabase(mintdb.NewTable(app.db, stateTableId))) + app.stateDeliver, err = state.New(app.dbVersion, app.stateDB) if err != nil { panic(err) } - app.stateCheck, err = state.New(app.rootHash, state.NewDatabase(mintdb.NewTable(app.db, stateTableId))) + app.stateCheck, err = state.New(app.dbVersion, app.stateDB) if err != nil { panic(err) @@ -392,19 +391,7 @@ func (app *Blockchain) CurrentState() *state.StateDB { } func (app *Blockchain) GetStateForHeight(height int) (*state.StateDB, error) { - h := int64(height) - result, err := app.tendermintRPC.Block(&h) - - if err != nil { - return nil, err - } - - var stateHash types.Hash - - copy(stateHash[:], result.Block.AppHash.Bytes()) - - stateTable := mintdb.NewTable(app.db, stateTableId) - return state.New(stateHash, state.NewDatabase(stateTable)) + return state.New(int64(height), app.stateDB) } func (app *Blockchain) Height() uint64 { @@ -412,13 +399,8 @@ func (app *Blockchain) Height() uint64 { } func (app *Blockchain) getCurrentValidators() abciTypes.ValidatorUpdates { - appTable := mintdb.NewTable(app.db, appTableId) - result, err := appTable.Get([]byte("validators")) - - if err != nil && err != leveldb.ErrNotFound { - panic(err) - } + result := app.appDB.Get([]byte("validators")) if len(result) == 0 { return abciTypes.ValidatorUpdates{} @@ -426,7 +408,7 @@ func (app *Blockchain) getCurrentValidators() abciTypes.ValidatorUpdates { var vals abciTypes.ValidatorUpdates - err = cdc.UnmarshalBinary(result, &vals) + err := cdc.UnmarshalBinary(result, &vals) if err != nil { panic(err) @@ -442,12 +424,7 @@ func (app *Blockchain) saveCurrentValidators(vals abciTypes.ValidatorUpdates) { panic(err) } - appTable := mintdb.NewTable(app.db, appTableId) - err = appTable.Put([]byte("validators"), data) - - if err != nil { - panic(err) - } + app.appDB.Set([]byte("validators"), data) } func GetBlockchain() *Blockchain { diff --git a/core/state/state_object.go b/core/state/state_object.go index 18116a436..ca986b728 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -17,7 +17,6 @@ package state import ( - "bytes" "fmt" "io" "math/big" @@ -72,9 +71,6 @@ type stateObject struct { // by StateDB.Commit. dbErr error - // Write caches. - trie Trie // storage trie, which becomes non-nil on first access - cachedStorage Storage // Storage entry cache to avoid duplicate reads dirtyStorage Storage // Storage entries that need to be flushed to disk @@ -200,94 +196,6 @@ func (c *stateObject) touch() { c.touched = true } -func (c *stateObject) getTrie(db Database) Trie { - if c.trie == nil { - var err error - c.trie, err = db.OpenStorageTrie(c.addrHash, c.data.Root) - if err != nil { - c.trie, _ = db.OpenStorageTrie(c.addrHash, types.Hash{}) - c.setError(fmt.Errorf("can't create storage trie: %v", err)) - } - } - return c.trie -} - -// GetState returns a value in account storage. -func (self *stateObject) GetState(db Database, key types.Hash) types.Hash { - value, exists := self.cachedStorage[key] - if exists { - return value - } - // Load from DB in case it is missing. - enc, err := self.getTrie(db).TryGet(key[:]) - if err != nil { - self.setError(err) - return types.Hash{} - } - if len(enc) > 0 { - _, content, _, err := rlp.Split(enc) - if err != nil { - self.setError(err) - } - value.SetBytes(content) - } - if (value != types.Hash{}) { - self.cachedStorage[key] = value - } - return value -} - -// SetState updates a value in account storage. -func (self *stateObject) SetState(db Database, key, value types.Hash) { - self.setState(key, value) -} - -func (self *stateObject) setState(key, value types.Hash) { - self.cachedStorage[key] = value - self.dirtyStorage[key] = value - - if self.onDirty != nil { - self.onDirty(self.Address()) - self.onDirty = nil - } -} - -// updateTrie writes cached storage modifications into the object's storage trie. -func (self *stateObject) updateTrie(db Database) Trie { - tr := self.getTrie(db) - for key, value := range self.dirtyStorage { - delete(self.dirtyStorage, key) - if (value == types.Hash{}) { - self.setError(tr.TryDelete(key[:])) - continue - } - // Encoding []byte cannot fail, ok to ignore the error. - v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00")) - self.setError(tr.TryUpdate(key[:], v)) - } - return tr -} - -// UpdateRoot sets the trie root to the current root hash of -func (self *stateObject) updateRoot(db Database) { - self.updateTrie(db) - self.data.Root = self.trie.Hash() -} - -// CommitTrie the storage trie of the object to dwb. -// This updates the trie root. -func (self *stateObject) CommitTrie(db Database) error { - self.updateTrie(db) - if self.dbErr != nil { - return self.dbErr - } - root, err := self.trie.Commit(nil) - if err == nil { - self.data.Root = root - } - return err -} - // AddBalance removes amount from c's balance. // It is used to add funds to the destination account of a transfer. func (c *stateObject) AddBalance(coinSymbol types.CoinSymbol, amount *big.Int) { @@ -335,9 +243,6 @@ func (c *stateObject) ReturnGas(gas *big.Int) {} func (self *stateObject) deepCopy(db *StateDB, onDirty func(addr types.Address)) *stateObject { stateObject := newObject(db, self.address, self.data, onDirty) - if self.trie != nil { - stateObject.trie = db.db.CopyTrie(self.trie) - } stateObject.dirtyStorage = self.dirtyStorage.Copy() stateObject.cachedStorage = self.dirtyStorage.Copy() stateObject.suicided = self.suicided diff --git a/core/state/statedb.go b/core/state/statedb.go index ed57fe3df..a76b445e9 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -59,7 +59,7 @@ var ( // * Coins // * Accounts type StateDB struct { - db dbm.DB + db dbm.DB iavl *iavl.MutableTree // This map holds 'live' objects, which will get modified while processing a state transition. @@ -622,7 +622,7 @@ func (s *StateDB) CreateCandidate( } // Commit writes the state to the underlying in-memory trie database. -func (s *StateDB) Commit(deleteEmptyObjects bool) (root types.Hash, height int64, err error) { +func (s *StateDB) Commit(deleteEmptyObjects bool) (root types.Hash, version int64, err error) { // Commit objects to the trie. for addr, stateObject := range s.stateObjects { @@ -633,10 +633,6 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root types.Hash, height int64 // and just mark it for deletion in the trie. s.deleteStateObject(stateObject) case isDirty: - // Write any storage changes in the state object to its storage trie. - if err := stateObject.CommitTrie(s.db); err != nil { - return types.Hash{}, 0, err - } // Update the object in the main account trie. s.updateStateObject(stateObject) } @@ -680,11 +676,9 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root types.Hash, height int64 s.stateValidatorsDirty = false } - // Write trie changes. - - hash, height, err := s.iavl.SaveVersion() + hash, version, err := s.iavl.SaveVersion() - return types.BytesToHash(hash), height, err + return types.BytesToHash(hash), version, err } func (s *StateDB) CoinExists(symbol types.CoinSymbol) bool { diff --git a/eventsdb/eventsdb.go b/eventsdb/eventsdb.go index fc7995d10..e13961bfb 100644 --- a/eventsdb/eventsdb.go +++ b/eventsdb/eventsdb.go @@ -4,9 +4,8 @@ import ( "encoding/binary" "github.com/MinterTeam/minter-go-node/cmd/utils" "github.com/MinterTeam/minter-go-node/config" - "github.com/MinterTeam/minter-go-node/mintdb" - "github.com/syndtr/goleveldb/leveldb" "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/libs/db" ) var cdc = amino.NewCodec() @@ -21,7 +20,7 @@ func init() { func GetCurrent() *EventsDB { if edb == nil { - eventsDB, err := mintdb.NewLDBDatabase(utils.GetMinterHome()+"/events", 128, 128) + eventsDB, err := db.NewGoLevelDB("events", utils.GetMinterHome()+"/data") if err != nil { panic(err) @@ -34,11 +33,11 @@ func GetCurrent() *EventsDB { } type EventsDB struct { - db *mintdb.LDBDatabase + db *db.GoLevelDB cache map[int64]Events } -func NewEventsDB(db *mintdb.LDBDatabase) *EventsDB { +func NewEventsDB(db *db.GoLevelDB) *EventsDB { return &EventsDB{ db: db, cache: map[int64]Events{}, @@ -74,7 +73,9 @@ func (db *EventsDB) FlushEvents(height int64) error { delete(db.cache, height) - return db.db.Put(key, bytes) + db.db.Set(key, bytes) + + return nil } func (db *EventsDB) SetEvents(height int64, events Events) { @@ -89,18 +90,10 @@ func (db *EventsDB) GetEvents(height int64) Events { key := getKeyForHeight(height) - data, err := db.db.Get(key) - - if err != nil { - if err == leveldb.ErrNotFound { - return Events{} - } - - panic(err) - } + data := db.db.Get(key) var decoded Events - err = cdc.UnmarshalBinary(data, &decoded) + err := cdc.UnmarshalBinary(data, &decoded) if err != nil { panic(err) diff --git a/genesis/genesis.go b/genesis/genesis.go index 91f473f39..fd763e0c9 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -14,15 +14,15 @@ import ( ) var ( - Network = "minter-test-network-21" + Network = "minter-test-network-22-local" ) func GetTestnetGenesis() (*tmtypes.GenesisDoc, error) { validatorsPubKeys := []string{ - "SuHuc+YTbIWwypM6mhNHdYozSIXxCzI4OYpnrC6xU7g=", - "c42kG6ant9abcpSvoVi4nFobQQy/DCRDyFxf4krR3Rw=", - "bxbB/yGm+5RqrtD0wfzKJyty/ZBJiPkdOIMoK4rjG6I=", - "nhPy9UaN14KzFkRPvWZZXhPbp9e9Pvob7NULQgRfWMY=", + "f60aeE+af6WDOk4Rv1GA9eCQpc8nMtwsFwnkvk34KAk=", + //"c42kG6ant9abcpSvoVi4nFobQQy/DCRDyFxf4krR3Rw=", + //"bxbB/yGm+5RqrtD0wfzKJyty/ZBJiPkdOIMoK4rjG6I=", + //"nhPy9UaN14KzFkRPvWZZXhPbp9e9Pvob7NULQgRfWMY=", } validators := make([]tmtypes.GenesisValidator, len(validatorsPubKeys)) diff --git a/mintdb/.gitignore b/mintdb/.gitignore deleted file mode 100644 index f725d58d1..000000000 --- a/mintdb/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. -# -# If you find yourself ignoring temporary files generated by your text editor -# or operating system, you probably want to add a global ignore instead: -# git config --global core.excludesfile ~/.gitignore_global - -/tmp -*/**/*un~ -*un~ -.DS_Store -*/**/.DS_Store - diff --git a/mintdb/database.go b/mintdb/database.go deleted file mode 100644 index 172d370c8..000000000 --- a/mintdb/database.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package mintdb - -import ( - "github.com/MinterTeam/minter-go-node/log" - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/errors" - "github.com/syndtr/goleveldb/leveldb/filter" - "github.com/syndtr/goleveldb/leveldb/iterator" - "github.com/syndtr/goleveldb/leveldb/opt" - "sync" - "time" - - gometrics "github.com/rcrowley/go-metrics" -) - -var OpenFileLimit = 64 - -type LDBDatabase struct { - fn string // filename for reporting - db *leveldb.DB // LevelDB instance - - getTimer gometrics.Timer // Timer for measuring the database get request counts and latencies - putTimer gometrics.Timer // Timer for measuring the database put request counts and latencies - delTimer gometrics.Timer // Timer for measuring the database delete request counts and latencies - missMeter gometrics.Meter // Meter for measuring the missed database get requests - readMeter gometrics.Meter // Meter for measuring the database get request data usage - writeMeter gometrics.Meter // Meter for measuring the database put request data usage - compTimeMeter gometrics.Meter // Meter for measuring the total time spent in database compaction - compReadMeter gometrics.Meter // Meter for measuring the data read during compaction - compWriteMeter gometrics.Meter // Meter for measuring the data written during compaction - - quitLock sync.Mutex // Mutex protecting the quit channel access - quitChan chan chan error // Quit channel to stop the metrics collection before closing the database - - // log log.Logger // Contextual logger tracking the database path -} - -// NewLDBDatabase returns a LevelDB wrapped object. -func NewLDBDatabase(file string, cache int, handles int) (*LDBDatabase, error) { - // logger := log.New("database", file) - - // Ensure we have some minimal caching and file guarantees - if cache < 16 { - cache = 16 - } - if handles < 16 { - handles = 16 - } - // logger.Info("Allocated cache and file handles", "cache", cache, "handles", handles) - - // Open the mintdb and recover any potential corruptions - db, err := leveldb.OpenFile(file, &opt.Options{ - OpenFilesCacheCapacity: handles, - BlockCacheCapacity: cache / 2 * opt.MiB, - WriteBuffer: cache / 4 * opt.MiB, // Two of these are used internally - Filter: filter.NewBloomFilter(10), - }) - if _, corrupted := err.(*errors.ErrCorrupted); corrupted { - db, err = leveldb.RecoverFile(file, nil) - } - // (Re)check for errors and abort if opening of the mintdb failed - if err != nil { - return nil, err - } - return &LDBDatabase{ - fn: file, - db: db, - // log: logger, - }, nil -} - -// Path returns the path to the database directory. -func (db *LDBDatabase) Path() string { - return db.fn -} - -// Put puts the given key / value to the queue -func (db *LDBDatabase) Put(key []byte, value []byte) error { - // Measure the database put latency, if requested - if db.putTimer != nil { - defer db.putTimer.UpdateSince(time.Now()) - } - // Generate the data to write to disk, update the meter and write - //value = rle.Compress(value) - - if db.writeMeter != nil { - db.writeMeter.Mark(int64(len(value))) - } - return db.db.Put(key, value, nil) -} - -func (db *LDBDatabase) Has(key []byte) (bool, error) { - return db.db.Has(key, nil) -} - -// Get returns the given key if it's present. -func (db *LDBDatabase) Get(key []byte) ([]byte, error) { - // Measure the database get latency, if requested - if db.getTimer != nil { - defer db.getTimer.UpdateSince(time.Now()) - } - // Retrieve the key and increment the miss counter if not found - dat, err := db.db.Get(key, nil) - if err != nil { - if db.missMeter != nil { - db.missMeter.Mark(1) - } - return nil, err - } - // Otherwise update the actually retrieved amount of data - if db.readMeter != nil { - db.readMeter.Mark(int64(len(dat))) - } - return dat, nil - //return rle.Decompress(dat) -} - -// Delete deletes the key from the queue and database -func (db *LDBDatabase) Delete(key []byte) error { - // Measure the database delete latency, if requested - if db.delTimer != nil { - defer db.delTimer.UpdateSince(time.Now()) - } - // Execute the actual operation - return db.db.Delete(key, nil) -} - -func (db *LDBDatabase) NewIterator() iterator.Iterator { - return db.db.NewIterator(nil, nil) -} - -func (db *LDBDatabase) Close() { - // Stop the metrics collection to avoid internal database races - db.quitLock.Lock() - defer db.quitLock.Unlock() - - if db.quitChan != nil { - errc := make(chan error) - db.quitChan <- errc - if err := <-errc; err != nil { - log.Error("Metrics collection failed", "err", err) - } - } - err := db.db.Close() - if err == nil { - log.Info("Database closed") - } else { - log.Error("Failed to close database", "err", err) - } -} - -func (db *LDBDatabase) LDB() *leveldb.DB { - return db.db -} - -func (db *LDBDatabase) NewBatch() Batch { - return &ldbBatch{db: db.db, b: new(leveldb.Batch)} -} - -type ldbBatch struct { - db *leveldb.DB - 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) Write() error { - return b.db.Write(b.b, nil) -} - -func (b *ldbBatch) ValueSize() int { - return b.size -} - -func (b *ldbBatch) Reset() { - b.b.Reset() - b.size = 0 -} - -type table struct { - db Database - prefix string -} - -// NewTable returns a Database object that prefixes all keys with a given -// string. -func NewTable(db Database, prefix string) Database { - return &table{ - db: db, - prefix: prefix, - } -} - -func (dt *table) Put(key []byte, value []byte) error { - return dt.db.Put(append([]byte(dt.prefix), key...), value) -} - -func (dt *table) Has(key []byte) (bool, error) { - return dt.db.Has(append([]byte(dt.prefix), key...)) -} - -func (dt *table) Get(key []byte) ([]byte, error) { - return dt.db.Get(append([]byte(dt.prefix), key...)) -} - -func (dt *table) Delete(key []byte) error { - return dt.db.Delete(append([]byte(dt.prefix), key...)) -} - -func (dt *table) Close() { - // Do nothing; don't close the underlying DB. -} - -type tableBatch struct { - batch Batch - prefix string -} - -// NewTableBatch returns a Batch object which prefixes all keys with a given string. -func NewTableBatch(db Database, prefix string) Batch { - return &tableBatch{db.NewBatch(), prefix} -} - -func (dt *table) NewBatch() Batch { - return &tableBatch{dt.db.NewBatch(), dt.prefix} -} - -func (tb *tableBatch) Put(key, value []byte) error { - return tb.batch.Put(append([]byte(tb.prefix), key...), value) -} - -func (tb *tableBatch) Write() error { - return tb.batch.Write() -} - -func (tb *tableBatch) ValueSize() int { - return tb.batch.ValueSize() -} - -func (tb *tableBatch) Reset() { - tb.batch.Reset() -} diff --git a/mintdb/interface.go b/mintdb/interface.go deleted file mode 100644 index 7560f449d..000000000 --- a/mintdb/interface.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package mintdb - -// Code using batches should try to add this much data to the batch. -// The value was determined empirically. -const IdealBatchSize = 100 * 1024 - -// Putter wraps the database write operation supported by both batches and regular databases. -type Putter interface { - Put(key []byte, value []byte) error -} - -// Database wraps all database operations. All methods are safe for concurrent use. -type Database interface { - Putter - Get(key []byte) ([]byte, error) - Has(key []byte) (bool, error) - Delete(key []byte) error - Close() - NewBatch() Batch -} - -// Batch is a write-only database that commits changes to its host database -// when Write is called. Batch cannot be used concurrently. -type Batch interface { - Putter - ValueSize() int // amount of data in the batch - Write() error - // Reset resets the batch for reuse - Reset() -} From 158b932ddd236c80c19d95d2871edbc297334546 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 14:28:34 +0300 Subject: [PATCH 03/29] refactoring --- core/minter/minter.go | 20 +++----------------- core/state/statedb.go | 4 ---- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/core/minter/minter.go b/core/minter/minter.go index 4d386ef7b..008c586c3 100644 --- a/core/minter/minter.go +++ b/core/minter/minter.go @@ -31,7 +31,6 @@ type Blockchain struct { stateDeliver *state.StateDB stateCheck *state.StateDB rootHash types.Hash - dbVersion int64 height uint64 rewards *big.Int validatorsStatuses map[[20]byte]int8 @@ -307,7 +306,7 @@ func (app *Blockchain) CheckTx(rawTx []byte) abciTypes.ResponseCheckTx { func (app *Blockchain) Commit() abciTypes.ResponseCommit { - hash, version, err := app.stateDeliver.Commit(false) + hash, _, err := app.stateDeliver.Commit(false) if err != nil { panic(err) @@ -324,11 +323,6 @@ func (app *Blockchain) Commit() abciTypes.ResponseCommit { binary.BigEndian.PutUint64(height, app.height) app.appDB.Set([]byte("height"), height) - // todo: make provider - versionBytes := make([]byte, 8) - binary.BigEndian.PutUint64(versionBytes, uint64(version)) - app.appDB.Set([]byte("version"), versionBytes) - // TODO: clear candidates list app.updateCurrentRootHash() @@ -361,25 +355,17 @@ func (app *Blockchain) updateCurrentRootHash() { } else { app.height = 0 } - - // todo: make provider - result = app.appDB.Get([]byte("version")) - if result != nil { - app.dbVersion = int64(binary.BigEndian.Uint64(result)) - } else { - app.dbVersion = 0 - } } func (app *Blockchain) updateCurrentState() { var err error - app.stateDeliver, err = state.New(app.dbVersion, app.stateDB) + app.stateDeliver, err = state.New(int64(app.height), app.stateDB) if err != nil { panic(err) } - app.stateCheck, err = state.New(app.dbVersion, app.stateDB) + app.stateCheck, err = state.New(int64(app.height), app.stateDB) if err != nil { panic(err) diff --git a/core/state/statedb.go b/core/state/statedb.go index a76b445e9..e40c0e481 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -26,7 +26,6 @@ import ( "sync" "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/rlp" "bytes" @@ -40,9 +39,6 @@ import ( const UnbondPeriod = 518400 var ( - // emptyState is the known hash of an empty state trie entry. - emptyState = crypto.Keccak256Hash(nil) - ValidatorMaxAbsentTimes = uint(12) addressPrefix = []byte("a") From ac833d82073cd9c962d3e05db21e5b5722ae9453 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 14:34:04 +0300 Subject: [PATCH 04/29] update version --- CHANGELOG.md | 7 +++++++ docker-compose.yml | 2 +- version/version.go | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a25c79cf1..68753ea7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.4.0 +*Sept 18th, 2018* + +BREAKING CHANGES + +- [core] Switch Ethereum Patricia Tree to IAVL + ## 0.3.8 *Sept 17th, 2018* diff --git a/docker-compose.yml b/docker-compose.yml index ff7e8d6fb..37deb027a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.4" services: minter: - image: minterteam/minter:0.3.8 + image: minterteam/minter:0.4.0 volumes: - ~/.minter:/minter ports: diff --git a/version/version.go b/version/version.go index 04cc839e4..bbf5dd817 100755 --- a/version/version.go +++ b/version/version.go @@ -3,13 +3,13 @@ package version // Version components const ( Maj = "0" - Min = "3" - Fix = "8" + Min = "4" + Fix = "0" ) var ( // Must be a string because scripts like dist.sh read this file. - Version = "0.3.8" + Version = "0.4.0" // GitCommit is the current HEAD set using ldflags. GitCommit string From 583ab3e9c7353e5910ecbc2b6de02eacd1eb325a Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 15:20:15 +0300 Subject: [PATCH 05/29] Add validator mode --- CHANGELOG.md | 4 ++++ cmd/minter/main.go | 6 ++++-- config/config.go | 12 ++++++++++-- config/toml.go | 4 ++-- core/state/statedb.go | 11 +++++++++++ eventsdb/eventsdb.go | 2 +- 6 files changed, 32 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68753ea7d..5c24db00b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ BREAKING CHANGES - [core] Switch Ethereum Patricia Tree to IAVL +IMPROVEMENT + +- [config] Add validator mode + ## 0.3.8 *Sept 17th, 2018* diff --git a/cmd/minter/main.go b/cmd/minter/main.go index 356d5ac97..c99028c26 100644 --- a/cmd/minter/main.go +++ b/cmd/minter/main.go @@ -38,8 +38,10 @@ func main() { app.RunRPC(node) - go api.RunApi(app, node) - go gui.Run(cfg.GUIListenAddress) + if !cfg.ValidatorMode { + go api.RunApi(app, node) + go gui.Run(cfg.GUIListenAddress) + } // Wait forever common.TrapSignal(func() { diff --git a/config/config.go b/config/config.go index eca3adc2c..b09a9cbec 100644 --- a/config/config.go +++ b/config/config.go @@ -88,6 +88,14 @@ func GetConfig() *Config { panic(err) } + if cfg.ValidatorMode { + cfg.TxIndex.IndexAllTags = false + cfg.TxIndex.IndexTags = "" + + cfg.RPC.ListenAddress = "" + cfg.RPC.GRPCListenAddress = "" + } + cfg.SetRoot(utils.GetMinterHome()) EnsureRoot(utils.GetMinterHome()) @@ -223,7 +231,7 @@ type BaseConfig struct { // Address to listen for API connections APIListenAddress string `mapstructure:"api_listen_addr"` - EnableEvents bool `mapstructure:"enable_events"` + ValidatorMode bool `mapstructure:"validator_mode"` } // DefaultBaseConfig returns a default base configuration for a Tendermint node @@ -243,7 +251,7 @@ func DefaultBaseConfig() BaseConfig { DBPath: "data", GUIListenAddress: ":3000", APIListenAddress: ":8841", - EnableEvents: false, + ValidatorMode: false, } } diff --git a/config/toml.go b/config/toml.go index 8330ed51f..0aefee27f 100644 --- a/config/toml.go +++ b/config/toml.go @@ -73,8 +73,8 @@ gui_listen_addr = "{{ .BaseConfig.GUIListenAddress }}" # Address to listen for API connections api_listen_addr = "{{ .BaseConfig.APIListenAddress }}" -# Enable events for API. Slows down node. -enable_events = {{ .BaseConfig.EnableEvents }} +# Sets node to be in validator mode. Disables API, events, history of blocks, indexes, etc. +validator_mode = {{ .BaseConfig.ValidatorMode }} # If this node is many blocks behind the tip of the chain, FastSync # allows them to catchup quickly by downloading blocks in parallel diff --git a/core/state/statedb.go b/core/state/statedb.go index e40c0e481..f19ef502d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -19,6 +19,7 @@ package state import ( "fmt" + "github.com/MinterTeam/minter-go-node/config" "github.com/MinterTeam/minter-go-node/eventsdb" "github.com/danil-lashin/iavl" dbm "github.com/tendermint/tendermint/libs/db" @@ -47,6 +48,8 @@ var ( usedCheckPrefix = []byte("u") candidatesKey = []byte("t") validatorsKey = []byte("v") + + cfg = config.GetConfig() ) // StateDBs within the ethereum protocol are used to store anything @@ -674,6 +677,14 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root types.Hash, version int6 hash, version, err := s.iavl.SaveVersion() + if cfg.ValidatorMode && version > 1 { + err = s.iavl.DeleteVersion(version - 1) + + if err != nil { + panic(err) + } + } + return types.BytesToHash(hash), version, err } diff --git a/eventsdb/eventsdb.go b/eventsdb/eventsdb.go index e13961bfb..971e4d75c 100644 --- a/eventsdb/eventsdb.go +++ b/eventsdb/eventsdb.go @@ -10,7 +10,7 @@ import ( var cdc = amino.NewCodec() -var eventsEnabled = config.GetConfig().EnableEvents +var eventsEnabled = !config.GetConfig().ValidatorMode var edb *EventsDB From f181e5dc94361c3c0f6c745d03a353be7e668f38 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 15:23:38 +0300 Subject: [PATCH 06/29] fix --- eventsdb/eventsdb.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/eventsdb/eventsdb.go b/eventsdb/eventsdb.go index 971e4d75c..448f91e3a 100644 --- a/eventsdb/eventsdb.go +++ b/eventsdb/eventsdb.go @@ -92,6 +92,10 @@ func (db *EventsDB) GetEvents(height int64) Events { data := db.db.Get(key) + if len(data) == 0 { + return Events{} + } + var decoded Events err := cdc.UnmarshalBinary(data, &decoded) From cf3838d0d4bfb995744586ce24d2d1f7a5b2bd40 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 15:27:16 +0300 Subject: [PATCH 07/29] include events by default --- CHANGELOG.md | 1 + api/block.go | 24 +++++++++++------------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c24db00b..47eef9344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ BREAKING CHANGES IMPROVEMENT - [config] Add validator mode +- [api] Include events by default ## 0.3.8 *Sept 17th, 2018* diff --git a/api/block.go b/api/block.go index 09890beff..acb776b77 100644 --- a/api/block.go +++ b/api/block.go @@ -50,7 +50,6 @@ func Block(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) height, _ := strconv.ParseInt(vars["height"], 10, 64) - includeEvents, _ := strconv.ParseBool(r.URL.Query().Get("withEvents")) block, err := client.Block(&height) blockResults, err := client.BlockResults(&height) @@ -105,23 +104,22 @@ func Block(w http.ResponseWriter, r *http.Request) { var eventsRaw []byte - if includeEvents { - events := eventsdb.GetCurrent().GetEvents(height) + events := eventsdb.GetCurrent().GetEvents(height) - if len(events) > 0 { - eventsRaw, err = cdc.MarshalJSON(events) + if len(events) > 0 { + eventsRaw, err = cdc.MarshalJSON(events) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(Response{ - Code: 0, - Log: err.Error(), - }) - return - } + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(Response{ + Code: 0, + Log: err.Error(), + }) + return } } + response := BlockResponse{ Hash: block.Block.Hash(), Height: block.Block.Height, From 5419618187b0bbeb4785d06d86bbf62856f90feb Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 15:38:36 +0300 Subject: [PATCH 08/29] Add validator status --- CHANGELOG.md | 1 + api/block.go | 1 - gui/gui-packr.go | 2 +- gui/html/index.html | 38 +++++++++++++++++++++++++++++++++++--- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47eef9344..cd587717f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ IMPROVEMENT - [config] Add validator mode - [api] Include events by default +- [gui] Add validator status ## 0.3.8 *Sept 17th, 2018* diff --git a/api/block.go b/api/block.go index acb776b77..870f07eff 100644 --- a/api/block.go +++ b/api/block.go @@ -119,7 +119,6 @@ func Block(w http.ResponseWriter, r *http.Request) { } } - response := BlockResponse{ Hash: block.Block.Hash(), Height: block.Block.Height, diff --git a/gui/gui-packr.go b/gui/gui-packr.go index 695c89c17..b81f76175 100644 --- a/gui/gui-packr.go +++ b/gui/gui-packr.go @@ -7,5 +7,5 @@ import "github.com/gobuffalo/packr" // You can use the "packr clean" command to clean up this, // and any other packr generated files. func init() { - packr.PackJSONBytes("./html", "index.html", "\"PGh0bWw+CjxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiCiAgICAgICAgICBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIHVzZXItc2NhbGFibGU9bm8sIGluaXRpYWwtc2NhbGU9MS4wLCBtYXhpbXVtLXNjYWxlPTEuMCwgbWluaW11bS1zY2FsZT0xLjAiPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJpZT1lZGdlIj4KICAgIDx0aXRsZT5NaW50ZXIgTm9kZSBHVUk8L3RpdGxlPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL3N0YWNrcGF0aC5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC80LjEuMC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiCiAgICAgICAgICBpbnRlZ3JpdHk9InNoYTM4NC05Z1ZRNGRZRnd3V1NqSURabkxFV254Q2plU1dGcGhKaXdHUFhyMWpkZEloT2VnaXUxRndPNXFSR3ZGWE9kSlo0IiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly91c2UuZm9udGF3ZXNvbWUuY29tL3JlbGVhc2VzL3Y1LjEuMS9jc3MvYWxsLmNzcyIgaW50ZWdyaXR5PSJzaGEzODQtTzh3aFMzZmhHMk9uQTVLYXMwWTlsM2NmcG1ZamFwakkwRTR0aGVINGl1TUQrcExoYmY2SkkwaklNZlljSzN5WiIgY3Jvc3NvcmlnaW49ImFub255bW91cyI+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS92dWUiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2F4aW9zLzAuMTguMC9heGlvcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2NyeXB0by1qcy8zLjEuOS0xL2NyeXB0by1qcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHN0eWxlPgoKICAgICAgICAuY2FyZCB7CiAgICAgICAgICAgIG1hcmdpbi1ib3R0b206IDIwcHg7CiAgICAgICAgfQoKICAgICAgICBodG1sLGJvZHksLmJvZHksI2FwcCB7CiAgICAgICAgICAgIG1pbi1oZWlnaHQ6IDEwMCU7CiAgICAgICAgfQogICAgICAgIAogICAgICAgIC5ib2R5IHsKICAgICAgICAgICAgcGFkZGluZy10b3A6IDE1cHg7CiAgICAgICAgfQoKICAgICAgICAudGFibGUgewogICAgICAgICAgICBtYXJnaW4tYm90dG9tOiAwOwogICAgICAgICAgICB0YWJsZS1sYXlvdXQ6IGZpeGVkOwogICAgICAgIH0KCiAgICAgICAgLmNhcmQtaGVhZGVyIHsKICAgICAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICAgICAgfQoKICAgICAgICAuY2FyZC1oZWFkZXIgewogICAgICAgICAgICBwYWRkaW5nLWxlZnQ6IDEycHg7CiAgICAgICAgfQoKICAgICAgICAuaCB7CiAgICAgICAgICAgIHdpZHRoOiAyMDBweDsKICAgICAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2YzZjNmMzsKICAgICAgICAgICAgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2NjYzsKICAgICAgICB9CgogICAgICAgIC5iZy1zdWNjZXNzLCAuYmctZGFuZ2VyIHsKICAgICAgICAgICAgY29sb3I6IHdoaXRlOwogICAgICAgIH0KCiAgICAgICAgLmJnLWRhbmdlciB7CiAgICAgICAgICAgIGJvcmRlci1jb2xvcjogI2RjMzU0NSAhaW1wb3J0YW50OwogICAgICAgIH0KCiAgICAgICAgLmJnLXN1Y2Nlc3MgewogICAgICAgICAgICBib3JkZXItY29sb3I6ICMyOGE3NDUgIWltcG9ydGFudDsKICAgICAgICB9CgogICAgICAgIC5mYS1jaGVjayB7CiAgICAgICAgICAgIGNvbG9yOiBncmVlbjsKICAgICAgICB9CgogICAgICAgIC5mYS1leGNsYW1hdGlvbi1jaXJjbGUgewogICAgICAgICAgICBjb2xvcjogcmVkOwogICAgICAgIH0KICAgIDwvc3R5bGU+CjwvaGVhZD4KPGJvZHkgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICMzNDNhNDAxYSI+CjxkaXYgaWQ9ImFwcCI+CiAgICA8bmF2IGNsYXNzPSJuYXZiYXIgbmF2YmFyLWV4cGFuZC1sZyBuYXZiYXItZGFyayBiZy1kYXJrIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIiPgogICAgICAgICAgICA8c3BhbiBjbGFzcz0ibmF2YmFyLWJyYW5kIG1iLTAgaDEiPjxpIGNsYXNzPSJmYXMgZmEtdGVybWluYWwiPjwvaT4gJm5ic3A7IE1pbnRlciBGdWxsIE5vZGUgU3RhdHVzPC9zcGFuPgogICAgICAgIDwvZGl2PgogICAgPC9uYXY+CiAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIgYm9keSIgdi1pZj0iZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWRhbmdlciIgcm9sZT0iYWxlcnQiPgogICAgICAgICAgICA8aDQgY2xhc3M9ImFsZXJ0LWhlYWRpbmciPkVycm9yIHdoaWxlIGNvbm5lY3RpbmcgdG8gbG9jYWwgbm9kZTwvaDQ+CiAgICAgICAgICAgIDxwIGNsYXNzPSJtYi0wIj57eyBlcnJvciB9fTwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0iY29udGFpbmVyIGJvZHkgYmctd2hpdGUiIHYtaWY9InN0YXR1cyAmJiAhZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9InJvdyI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIE5vZGUgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk1vbmlrZXI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubW9uaWtlciB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Tm9kZSBJRDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby5pZCB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TGlzdGVuIEFkZHI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubGlzdGVuX2FkZHIgfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk5ldHdvcmsgSUQ8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubmV0d29yayB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TWludGVyIFZlcnNpb248L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZlcnNpb24gfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPlRlbmRlcm1pbnQgVmVyc2lvbjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby52ZXJzaW9uIH19PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIiB2LWlmPSJuZXRfaW5mbyI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZC1oZWFkZXIiPgogICAgICAgICAgICAgICAgICAgICAgICBOZXQgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIExpc3RlbmluZzwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PGkgOmNsYXNzPSJ7J2ZhLWNoZWNrJzogbmV0X2luZm8ubGlzdGVuaW5nfSIgY2xhc3M9ImZhcyI+PC9pPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Q29ubmVjdGVkIFBlZXJzPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD57eyBuZXRfaW5mby5uX3BlZXJzIH19IDxpIDpjbGFzcz0ieydmYS1leGNsYW1hdGlvbi1jaXJjbGUnOiBuZXRfaW5mby5uX3BlZXJzIDwgMX0iIGNsYXNzPSJmYXMiPjwvaT48L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIFN5bmNpbmcgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIFN5bmNlZDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0ic3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCI+Tm88L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0iIXN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXAiPlllczwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aSA6Y2xhc3M9InsnZmEtY2hlY2snOiAhc3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCwgJ2ZhLWV4Y2xhbWF0aW9uLWNpcmNsZSc6IHN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXB9IiBjbGFzcz0iZmFzIj48L2k+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2sgSGVpZ2h0PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAje3sgc3RhdHVzLnN5bmNfaW5mby5sYXRlc3RfYmxvY2tfaGVpZ2h0IH19IDxzcGFuIHYtaWY9Im1hc3RlclN0YXR1cyIgY2xhc3M9InRleHQtbXV0ZWQiPm9mIHt7IG1hc3RlclN0YXR1cy5sYXRlc3RfYmxvY2tfaGVpZ2h0IH19PC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2sgVGltZTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7eyBzdGF0dXMuc3luY19pbmZvLmxhdGVzdF9ibG9ja190aW1lIH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQtaGVhZGVyIj4KICAgICAgICAgICAgICAgICAgICAgICAgVmFsaWRhdG9yIEluZm8KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8dGFibGUgY2xhc3M9InRhYmxlIGNhcmQtYm9keSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPlB1YmxpYyBLZXk8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPk1we3sgYmFzZTY0VG9IZXgoc3RhdHVzLnZhbGlkYXRvcl9pbmZvLnB1Yl9rZXkudmFsdWUpIH19PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPlZvdGluZyBQb3dlcjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgbmljZU51bShzdGF0dXMudmFsaWRhdG9yX2luZm8udm90aW5nX3Bvd2VyKSB9fSA8c3BhbiBjbGFzcz0idGV4dC1tdXRlZCI+b2YgMTAwLDAwMCwwMDA8L3NwYW4+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgogICAgPC9kaXY+CjwvZGl2Pgo8c2NyaXB0PgogICAgbmV3IFZ1ZSh7CiAgICAgICAgZWw6ICcjYXBwJywKICAgICAgICBkYXRhOiB7CiAgICAgICAgICAgIG1hc3RlclN0YXR1czogbnVsbCwKICAgICAgICAgICAgc3RhdHVzOiBudWxsLAogICAgICAgICAgICB2ZXJzaW9uOiBudWxsLAogICAgICAgICAgICBuZXRfaW5mbzogbnVsbCwKICAgICAgICAgICAgZXJyb3I6IG51bGwKICAgICAgICB9LAogICAgICAgIG1vdW50ZWQoKSB7CiAgICAgICAgICAgIHRoaXMucmVmcmVzaCgpCiAgICAgICAgfSwKICAgICAgICBtZXRob2RzOiB7CiAgICAgICAgICAgIG5pY2VOdW0obnVtKSB7CiAgICAgICAgICAgICAgICByZXR1cm4gbnVtLnRvU3RyaW5nKCkucmVwbGFjZSgvXEIoPz0oXGR7M30pKyg/IVxkKSkvZywgIiwiKQogICAgICAgICAgICB9LAogICAgICAgICAgICBiYXNlNjRUb0hleChiYXNlNjQpIHsKICAgICAgICAgICAgICAgIHJldHVybiBDcnlwdG9KUy5lbmMuQmFzZTY0LnBhcnNlKGJhc2U2NCkudG9TdHJpbmcoKQogICAgICAgICAgICB9LAogICAgICAgICAgICByZWZyZXNoKCkgewogICAgICAgICAgICAgICAgYXhpb3MuYWxsKFsKICAgICAgICAgICAgICAgICAgICBheGlvcy5nZXQoIi8vIiArIHdpbmRvdy5sb2NhdGlvbi5ob3N0bmFtZSArICc6ODg0MS9hcGkvc3RhdHVzJyksCiAgICAgICAgICAgICAgICAgICAgYXhpb3MuZ2V0KCIvLyIgKyB3aW5kb3cubG9jYXRpb24uaG9zdG5hbWUgKyAnOjg4NDEvYXBpL25ldF9pbmZvJykKICAgICAgICAgICAgICAgIF0pLnRoZW4oYXhpb3Muc3ByZWFkKGZ1bmN0aW9uIChzdGF0dXMsIG5ldF9pbmZvKSB7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5lcnJvciA9IG51bGwKCiAgICAgICAgICAgICAgICAgICAgdGhpcy5zdGF0dXMgPSBzdGF0dXMuZGF0YS5yZXN1bHQudG1fc3RhdHVzCiAgICAgICAgICAgICAgICAgICAgdGhpcy52ZXJzaW9uID0gc3RhdHVzLmRhdGEucmVzdWx0LnZlcnNpb24KICAgICAgICAgICAgICAgICAgICB0aGlzLm5ldF9pbmZvID0gbmV0X2luZm8uZGF0YS5yZXN1bHQKCiAgICAgICAgICAgICAgICAgICAgc2V0VGltZW91dCh0aGlzLnJlZnJlc2gsIDUwMCkKICAgICAgICAgICAgICAgIH0uYmluZCh0aGlzKSkpLmNhdGNoKGZ1bmN0aW9uIChyZWFzb24pIHsKICAgICAgICAgICAgICAgICAgICB0aGlzLmVycm9yID0gcmVhc29uLnRvU3RyaW5nKCk7CiAgICAgICAgICAgICAgICAgICAgc2V0VGltZW91dCh0aGlzLnJlZnJlc2gsIDUwMCkKICAgICAgICAgICAgICAgIH0uYmluZCh0aGlzKSkKCiAgICAgICAgICAgICAgICBheGlvcy5nZXQoImh0dHBzOi8vbWludGVyLW5vZGUtMS50ZXN0bmV0Lm1pbnRlci5uZXR3b3JrL2FwaS9zdGF0dXMiKS50aGVuKGZ1bmN0aW9uIChtYXN0ZXJTdGF0dXMpIHsKICAgICAgICAgICAgICAgICAgICB0aGlzLm1hc3RlclN0YXR1cyA9IG1hc3RlclN0YXR1cy5kYXRhLnJlc3VsdAogICAgICAgICAgICAgICAgfS5iaW5kKHRoaXMpKQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfSkKPC9zY3JpcHQ+CjwvYm9keT4KPC9odG1sPg==\"") + packr.PackJSONBytes("./html", "index.html", "\"PGh0bWw+CjxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiCiAgICAgICAgICBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIHVzZXItc2NhbGFibGU9bm8sIGluaXRpYWwtc2NhbGU9MS4wLCBtYXhpbXVtLXNjYWxlPTEuMCwgbWluaW11bS1zY2FsZT0xLjAiPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJpZT1lZGdlIj4KICAgIDx0aXRsZT5NaW50ZXIgTm9kZSBHVUk8L3RpdGxlPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL3N0YWNrcGF0aC5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC80LjEuMC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiCiAgICAgICAgICBpbnRlZ3JpdHk9InNoYTM4NC05Z1ZRNGRZRnd3V1NqSURabkxFV254Q2plU1dGcGhKaXdHUFhyMWpkZEloT2VnaXUxRndPNXFSR3ZGWE9kSlo0IiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly91c2UuZm9udGF3ZXNvbWUuY29tL3JlbGVhc2VzL3Y1LjEuMS9jc3MvYWxsLmNzcyIgaW50ZWdyaXR5PSJzaGEzODQtTzh3aFMzZmhHMk9uQTVLYXMwWTlsM2NmcG1ZamFwakkwRTR0aGVINGl1TUQrcExoYmY2SkkwaklNZlljSzN5WiIgY3Jvc3NvcmlnaW49ImFub255bW91cyI+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS92dWUiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2F4aW9zLzAuMTguMC9heGlvcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2NyeXB0by1qcy8zLjEuOS0xL2NyeXB0by1qcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHN0eWxlPgoKICAgICAgICAuY2FyZCB7CiAgICAgICAgICAgIG1hcmdpbi1ib3R0b206IDIwcHg7CiAgICAgICAgfQoKICAgICAgICBodG1sLGJvZHksLmJvZHksI2FwcCB7CiAgICAgICAgICAgIG1pbi1oZWlnaHQ6IDEwMCU7CiAgICAgICAgfQogICAgICAgIAogICAgICAgIC5ib2R5IHsKICAgICAgICAgICAgcGFkZGluZy10b3A6IDE1cHg7CiAgICAgICAgfQoKICAgICAgICAudGFibGUgewogICAgICAgICAgICBtYXJnaW4tYm90dG9tOiAwOwogICAgICAgICAgICB0YWJsZS1sYXlvdXQ6IGZpeGVkOwogICAgICAgIH0KCiAgICAgICAgLmNhcmQtaGVhZGVyIHsKICAgICAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICAgICAgfQoKICAgICAgICAuY2FyZC1oZWFkZXIgewogICAgICAgICAgICBwYWRkaW5nLWxlZnQ6IDEycHg7CiAgICAgICAgfQoKICAgICAgICAuaCB7CiAgICAgICAgICAgIHdpZHRoOiAyMDBweDsKICAgICAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2YzZjNmMzsKICAgICAgICAgICAgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2NjYzsKICAgICAgICB9CgogICAgICAgIC5iZy1zdWNjZXNzLCAuYmctZGFuZ2VyIHsKICAgICAgICAgICAgY29sb3I6IHdoaXRlOwogICAgICAgIH0KCiAgICAgICAgLmJnLWRhbmdlciB7CiAgICAgICAgICAgIGJvcmRlci1jb2xvcjogI2RjMzU0NSAhaW1wb3J0YW50OwogICAgICAgIH0KCiAgICAgICAgLmJnLXN1Y2Nlc3MgewogICAgICAgICAgICBib3JkZXItY29sb3I6ICMyOGE3NDUgIWltcG9ydGFudDsKICAgICAgICB9CgogICAgICAgIC5mYS1jaGVjayB7CiAgICAgICAgICAgIGNvbG9yOiBncmVlbjsKICAgICAgICB9CgogICAgICAgIC5mYS1leGNsYW1hdGlvbi1jaXJjbGUgewogICAgICAgICAgICBjb2xvcjogcmVkOwogICAgICAgIH0KICAgIDwvc3R5bGU+CjwvaGVhZD4KPGJvZHkgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICMzNDNhNDAxYSI+CjxkaXYgaWQ9ImFwcCI+CiAgICA8bmF2IGNsYXNzPSJuYXZiYXIgbmF2YmFyLWV4cGFuZC1sZyBuYXZiYXItZGFyayBiZy1kYXJrIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIiPgogICAgICAgICAgICA8c3BhbiBjbGFzcz0ibmF2YmFyLWJyYW5kIG1iLTAgaDEiPjxpIGNsYXNzPSJmYXMgZmEtdGVybWluYWwiPjwvaT4gJm5ic3A7IE1pbnRlciBGdWxsIE5vZGUgU3RhdHVzPC9zcGFuPgogICAgICAgIDwvZGl2PgogICAgPC9uYXY+CiAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIgYm9keSIgdi1pZj0iZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWRhbmdlciIgcm9sZT0iYWxlcnQiPgogICAgICAgICAgICA8aDQgY2xhc3M9ImFsZXJ0LWhlYWRpbmciPkVycm9yIHdoaWxlIGNvbm5lY3RpbmcgdG8gbG9jYWwgbm9kZTwvaDQ+CiAgICAgICAgICAgIDxwIGNsYXNzPSJtYi0wIj57eyBlcnJvciB9fTwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0iY29udGFpbmVyIGJvZHkgYmctd2hpdGUiIHYtaWY9InN0YXR1cyAmJiAhZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9InJvdyI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIE5vZGUgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk1vbmlrZXI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubW9uaWtlciB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Tm9kZSBJRDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby5pZCB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TGlzdGVuIEFkZHI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubGlzdGVuX2FkZHIgfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk5ldHdvcmsgSUQ8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubmV0d29yayB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TWludGVyIFZlcnNpb248L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZlcnNpb24gfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPlRlbmRlcm1pbnQgVmVyc2lvbjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby52ZXJzaW9uIH19PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIiB2LWlmPSJuZXRfaW5mbyI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZC1oZWFkZXIiPgogICAgICAgICAgICAgICAgICAgICAgICBOZXQgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIExpc3RlbmluZzwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PGkgOmNsYXNzPSJ7J2ZhLWNoZWNrJzogbmV0X2luZm8ubGlzdGVuaW5nfSIgY2xhc3M9ImZhcyI+PC9pPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Q29ubmVjdGVkIFBlZXJzPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD57eyBuZXRfaW5mby5uX3BlZXJzIH19IDxpIDpjbGFzcz0ieydmYS1leGNsYW1hdGlvbi1jaXJjbGUnOiBuZXRfaW5mby5uX3BlZXJzIDwgMX0iIGNsYXNzPSJmYXMiPjwvaT48L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIFN5bmNpbmcgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIFN5bmNlZDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0ic3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCI+Tm88L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0iIXN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXAiPlllczwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aSA6Y2xhc3M9InsnZmEtY2hlY2snOiAhc3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCwgJ2ZhLWV4Y2xhbWF0aW9uLWNpcmNsZSc6IHN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXB9IiBjbGFzcz0iZmFzIj48L2k+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2sgSGVpZ2h0PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAje3sgc3RhdHVzLnN5bmNfaW5mby5sYXRlc3RfYmxvY2tfaGVpZ2h0IH19IDxzcGFuIHYtaWY9Im1hc3RlclN0YXR1cyIgY2xhc3M9InRleHQtbXV0ZWQiPm9mIHt7IG1hc3RlclN0YXR1cy5sYXRlc3RfYmxvY2tfaGVpZ2h0IH19PC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2sgVGltZTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7eyBzdGF0dXMuc3luY19pbmZvLmxhdGVzdF9ibG9ja190aW1lIH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQtaGVhZGVyIj4KICAgICAgICAgICAgICAgICAgICAgICAgVmFsaWRhdG9yIEluZm8KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8dGFibGUgY2xhc3M9InRhYmxlIGNhcmQtYm9keSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPlB1YmxpYyBLZXk8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZhbGlkYXRvclB1YktleSB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD5TdGF0dXM8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZhbGlkYXRvclN0YXR1cyB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD5Wb3RpbmcgUG93ZXI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IG5pY2VOdW0oc3RhdHVzLnZhbGlkYXRvcl9pbmZvLnZvdGluZ19wb3dlcikgfX0gPHNwYW4gY2xhc3M9InRleHQtbXV0ZWQiPm9mIDEwMCwwMDAsMDAwPC9zcGFuPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2Pgo8L2Rpdj4KPHNjcmlwdD4KICAgIG5ldyBWdWUoewogICAgICAgIGVsOiAnI2FwcCcsCiAgICAgICAgZGF0YTogewogICAgICAgICAgICBtYXN0ZXJTdGF0dXM6IG51bGwsCiAgICAgICAgICAgIHN0YXR1czogbnVsbCwKICAgICAgICAgICAgdmVyc2lvbjogbnVsbCwKICAgICAgICAgICAgbmV0X2luZm86IG51bGwsCiAgICAgICAgICAgIGVycm9yOiBudWxsLAogICAgICAgICAgICB2YWxpZGF0b3JQdWJLZXk6ICcuLi4nLAogICAgICAgICAgICB2YWxpZGF0b3JTdGF0dXM6ICcuLi4nCiAgICAgICAgfSwKICAgICAgICBtb3VudGVkKCkgewogICAgICAgICAgICB0aGlzLnJlZnJlc2goKQogICAgICAgIH0sCiAgICAgICAgbWV0aG9kczogewogICAgICAgICAgICBuaWNlTnVtKG51bSkgewogICAgICAgICAgICAgICAgcmV0dXJuIG51bS50b1N0cmluZygpLnJlcGxhY2UoL1xCKD89KFxkezN9KSsoPyFcZCkpL2csICIsIikKICAgICAgICAgICAgfSwKICAgICAgICAgICAgYmFzZTY0VG9IZXgoYmFzZTY0KSB7CiAgICAgICAgICAgICAgICByZXR1cm4gQ3J5cHRvSlMuZW5jLkJhc2U2NC5wYXJzZShiYXNlNjQpLnRvU3RyaW5nKCkKICAgICAgICAgICAgfSwKICAgICAgICAgICAgcmVmcmVzaCgpIHsKICAgICAgICAgICAgICAgIGF4aW9zLmFsbChbCiAgICAgICAgICAgICAgICAgICAgYXhpb3MuZ2V0KCIvLyIgKyB3aW5kb3cubG9jYXRpb24uaG9zdG5hbWUgKyAnOjg4NDEvYXBpL3N0YXR1cycpLAogICAgICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiLy8iICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgJzo4ODQxL2FwaS9uZXRfaW5mbycpLAogICAgICAgICAgICAgICAgXSkudGhlbihheGlvcy5zcHJlYWQoZnVuY3Rpb24gKHN0YXR1cywgbmV0X2luZm8pIHsKICAgICAgICAgICAgICAgICAgICB0aGlzLmVycm9yID0gbnVsbAoKICAgICAgICAgICAgICAgICAgICB0aGlzLnN0YXR1cyA9IHN0YXR1cy5kYXRhLnJlc3VsdC50bV9zdGF0dXMKICAgICAgICAgICAgICAgICAgICB0aGlzLnZlcnNpb24gPSBzdGF0dXMuZGF0YS5yZXN1bHQudmVyc2lvbgogICAgICAgICAgICAgICAgICAgIHRoaXMubmV0X2luZm8gPSBuZXRfaW5mby5kYXRhLnJlc3VsdAoKICAgICAgICAgICAgICAgICAgICB0aGlzLnZhbGlkYXRvclB1YktleSA9ICdNcCcgKyB0aGlzLmJhc2U2NFRvSGV4KHN0YXR1cy5kYXRhLnJlc3VsdC50bV9zdGF0dXMudmFsaWRhdG9yX2luZm8ucHViX2tleS52YWx1ZSkKCiAgICAgICAgICAgICAgICAgICAgYXhpb3MuYWxsKFsKICAgICAgICAgICAgICAgICAgICAgICAgYXhpb3MuZ2V0KCIvLyIgKyB3aW5kb3cubG9jYXRpb24uaG9zdG5hbWUgKyAnOjg4NDEvYXBpL3ZhbGlkYXRvcnMnKSwKICAgICAgICAgICAgICAgICAgICAgICAgYXhpb3MuZ2V0KCIvLyIgKyB3aW5kb3cubG9jYXRpb24uaG9zdG5hbWUgKyAnOjg4NDEvYXBpL2NhbmRpZGF0ZS8nICsgdGhpcy52YWxpZGF0b3JQdWJLZXkpLAogICAgICAgICAgICAgICAgICAgIF0pLnRoZW4oYXhpb3Muc3ByZWFkKGZ1bmN0aW9uICh2YWxpZGF0b3JzLCBjYW5kaWRhdGUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGNhbmRpZGF0ZS5kYXRhLmNvZGUgIT09IDApIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudmFsaWRhdG9yU3RhdHVzID0gJ05vdCBkZWNsYXJlZCc7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4KICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHZhbGlkYXRvcnMuZGF0YS5yZXN1bHQuZmluZChmdW5jdGlvbih2YWwpIHsgcmV0dXJuIHZhbC5jYW5kaWRhdGUucHViX2tleSA9PT0gdGhpcy52YWxpZGF0b3JQdWJLZXkgfS5iaW5kKHRoaXMpKSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy52YWxpZGF0b3JTdGF0dXMgPSAnVmFsaWRhdGluZyc7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4KICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGNhbmRpZGF0ZS5kYXRhLnJlc3VsdC5zdGF0dXMgPT09IDIpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudmFsaWRhdG9yU3RhdHVzID0gJ0NhbmRpZGF0ZScKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybgogICAgICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnZhbGlkYXRvclN0YXR1cyA9ICdEb3duJzsKICAgICAgICAgICAgICAgICAgICB9LmJpbmQodGhpcykpKTsKCiAgICAgICAgICAgICAgICAgICAgc2V0VGltZW91dCh0aGlzLnJlZnJlc2gsIDUwMCkKCgogICAgICAgICAgICAgICAgfS5iaW5kKHRoaXMpKSkuY2F0Y2goZnVuY3Rpb24gKHJlYXNvbikgewogICAgICAgICAgICAgICAgICAgIHRoaXMuZXJyb3IgPSByZWFzb24udG9TdHJpbmcoKTsKICAgICAgICAgICAgICAgICAgICBzZXRUaW1lb3V0KHRoaXMucmVmcmVzaCwgNTAwKQogICAgICAgICAgICAgICAgfS5iaW5kKHRoaXMpKQoKICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiaHR0cHM6Ly9taW50ZXItbm9kZS0xLnRlc3RuZXQubWludGVyLm5ldHdvcmsvYXBpL3N0YXR1cyIpLnRoZW4oZnVuY3Rpb24gKG1hc3RlclN0YXR1cykgewogICAgICAgICAgICAgICAgICAgIHRoaXMubWFzdGVyU3RhdHVzID0gbWFzdGVyU3RhdHVzLmRhdGEucmVzdWx0CiAgICAgICAgICAgICAgICB9LmJpbmQodGhpcykpCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICB9KQo8L3NjcmlwdD4KPC9ib2R5Pgo8L2h0bWw+\"") } diff --git a/gui/html/index.html b/gui/html/index.html index e9b5a824e..773d5caf6 100644 --- a/gui/html/index.html +++ b/gui/html/index.html @@ -169,7 +169,11 @@

Error while connecting to local node

Public Key - Mp{{ base64ToHex(status.validator_info.pub_key.value) }} + {{ validatorPubKey }} + + + Status + {{ validatorStatus }} Voting Power @@ -190,7 +194,9 @@

Error while connecting to local node

status: null, version: null, net_info: null, - error: null + error: null, + validatorPubKey: '...', + validatorStatus: '...' }, mounted() { this.refresh() @@ -205,7 +211,7 @@

Error while connecting to local node

refresh() { axios.all([ axios.get("//" + window.location.hostname + ':8841/api/status'), - axios.get("//" + window.location.hostname + ':8841/api/net_info') + axios.get("//" + window.location.hostname + ':8841/api/net_info'), ]).then(axios.spread(function (status, net_info) { this.error = null @@ -213,7 +219,33 @@

Error while connecting to local node

this.version = status.data.result.version this.net_info = net_info.data.result + this.validatorPubKey = 'Mp' + this.base64ToHex(status.data.result.tm_status.validator_info.pub_key.value) + + axios.all([ + axios.get("//" + window.location.hostname + ':8841/api/validators'), + axios.get("//" + window.location.hostname + ':8841/api/candidate/' + this.validatorPubKey), + ]).then(axios.spread(function (validators, candidate) { + if (candidate.data.code !== 0) { + this.validatorStatus = 'Not declared'; + return + } + + if (validators.data.result.find(function(val) { return val.candidate.pub_key === this.validatorPubKey }.bind(this))) { + this.validatorStatus = 'Validating'; + return + } + + if (candidate.data.result.status === 2) { + this.validatorStatus = 'Candidate' + return + } + + this.validatorStatus = 'Down'; + }.bind(this))); + setTimeout(this.refresh, 500) + + }.bind(this))).catch(function (reason) { this.error = reason.toString(); setTimeout(this.refresh, 500) From 155e45686acf6985666c63faa6337c03d9c110f4 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 15:45:09 +0300 Subject: [PATCH 09/29] refactor --- api/balance_watcher.go | 4 +-- core/state/state_object.go | 57 ++++---------------------------------- 2 files changed, 7 insertions(+), 54 deletions(-) diff --git a/api/balance_watcher.go b/api/balance_watcher.go index e13acfac0..1dbff7601 100644 --- a/api/balance_watcher.go +++ b/api/balance_watcher.go @@ -42,8 +42,8 @@ func GetBalanceWatcher(w http.ResponseWriter, r *http.Request) { } func handleBalanceChanges() { - for { - handleBalanceChange(<-state.BalanceChangeChan) + for change := range state.BalanceChangeChan { + handleBalanceChange(change) } } diff --git a/core/state/state_object.go b/core/state/state_object.go index ca986b728..cf1d655c9 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -17,7 +17,6 @@ package state import ( - "fmt" "io" "math/big" @@ -27,31 +26,6 @@ import ( "sort" ) -type Code []byte - -func (self Code) String() string { - return string(self) //strings.Join(Disassemble(self), " ") -} - -type Storage map[types.Hash]types.Hash - -func (self Storage) String() (str string) { - for key, value := range self { - str += fmt.Sprintf("%X : %X\n", key, value) - } - - return -} - -func (self Storage) Copy() Storage { - cpy := make(Storage) - for key, value := range self { - cpy[key] = value - } - - return cpy -} - // stateObject represents an Ethereum account which is being modified. // // The usage pattern is as follows: @@ -64,16 +38,6 @@ type stateObject struct { data Account db *StateDB - // DB error. - // State objects are used by the consensus core and VM which are - // unable to deal with database-level errors. Any error that occurs - // during a database read is memoized here and will eventually be returned - // by StateDB.Commit. - dbErr error - - cachedStorage Storage // Storage entry cache to avoid duplicate reads - dirtyStorage Storage // Storage entries that need to be flushed to disk - // Cache flags. // When an object is marked suicided it will be delete from the trie // during the "update" phase of the state transition. @@ -158,13 +122,11 @@ func newObject(db *StateDB, address types.Address, data Account, onDirty func(ad } return &stateObject{ - db: db, - address: address, - addrHash: crypto.Keccak256Hash(address[:]), - data: data, - cachedStorage: make(Storage), - dirtyStorage: make(Storage), - onDirty: onDirty, + db: db, + address: address, + addrHash: crypto.Keccak256Hash(address[:]), + data: data, + onDirty: onDirty, } } @@ -173,13 +135,6 @@ func (c *stateObject) EncodeRLP(w io.Writer) error { return rlp.Encode(w, c.data) } -// setError remembers the first non-nil error it is called with. -func (self *stateObject) setError(err error) { - if self.dbErr == nil { - self.dbErr = err - } -} - func (self *stateObject) markSuicided() { self.suicided = true if self.onDirty != nil { @@ -243,8 +198,6 @@ func (c *stateObject) ReturnGas(gas *big.Int) {} func (self *stateObject) deepCopy(db *StateDB, onDirty func(addr types.Address)) *stateObject { stateObject := newObject(db, self.address, self.data, onDirty) - stateObject.dirtyStorage = self.dirtyStorage.Copy() - stateObject.cachedStorage = self.dirtyStorage.Copy() stateObject.suicided = self.suicided stateObject.deleted = self.deleted return stateObject From db8e46bbfe65f07e47af7695ea15c3703ac9c940 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 15:48:04 +0300 Subject: [PATCH 10/29] Update config and docs --- config/toml.go | 22 ---------------------- docs/validators.rst | 8 -------- 2 files changed, 30 deletions(-) diff --git a/config/toml.go b/config/toml.go index 0aefee27f..118d26700 100644 --- a/config/toml.go +++ b/config/toml.go @@ -201,28 +201,6 @@ size = {{ .Mempool.Size }} # size of the cache (used to filter transactions we saw earlier) cache_size = {{ .Mempool.CacheSize }} -##### transactions indexer configuration options ##### -[tx_index] - -# What indexer to use for transactions -# -# Options: -# 1) "null" (default) -# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). -indexer = "{{ .TxIndex.Indexer }}" - -# Comma-separated list of tags to index (by default the only tag is tx hash) -# -# It's recommended to index only a subset of tags due to possible memory -# bloat. This is, of course, depends on the indexer's DB and the volume of -# transactions. -index_tags = "{{ .TxIndex.IndexTags }}" - -# When set to true, tells indexer to index all tags. Note this may be not -# desirable (see the comment above). IndexTags has a precedence over -# IndexAllTags (i.e. when given both, IndexTags will be indexed). -index_all_tags = {{ .TxIndex.IndexAllTags }} - ##### instrumentation configuration options ##### [instrumentation] diff --git a/docs/validators.rst b/docs/validators.rst index 4d71a451c..c5f147e8e 100755 --- a/docs/validators.rst +++ b/docs/validators.rst @@ -167,10 +167,6 @@ Validators nodes should edit their ``config.toml``: # Set true to enable the peer-exchange reactor pex = false - # Disable transaction indexer for better performance - indexer = "null" - index_all_tags = false - Sentry Nodes should edit their ``config.toml``: :: @@ -179,8 +175,4 @@ Sentry Nodes should edit their ``config.toml``: private_peer_ids = "ipaddress of validator nodes" -Also you can disable Minter API on Validator node to improve performance: - -:: - minter --disable-api From 64d9b721fbf586ab38e036027371d5d33b3866b4 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 15:49:46 +0300 Subject: [PATCH 11/29] Fix docs --- docs/validators.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/validators.rst b/docs/validators.rst index c5f147e8e..9a633a373 100755 --- a/docs/validators.rst +++ b/docs/validators.rst @@ -21,7 +21,7 @@ delegated to them) can be slashed. The penalty depends on the severity of the vi Requirements ^^^^^^^^^^^^ -Minimal requirements for running Validator's Node are: +Minimal requirements for running Validator's Node in testnet are: - 4GB RAM - 200GB SSD From 660cb56ce909edad3c3615d50054885b446b9f5d Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 16:02:22 +0300 Subject: [PATCH 12/29] Trim hash --- core/minter/minter.go | 10 +++++----- core/state/statedb.go | 4 ++-- genesis/genesis.go | 10 ++-------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/core/minter/minter.go b/core/minter/minter.go index 008c586c3..ab8087e40 100644 --- a/core/minter/minter.go +++ b/core/minter/minter.go @@ -30,7 +30,7 @@ type Blockchain struct { appDB db.DB stateDeliver *state.StateDB stateCheck *state.StateDB - rootHash types.Hash + rootHash [16]byte height uint64 rewards *big.Int validatorsStatuses map[[20]byte]int8 @@ -272,7 +272,7 @@ func (app *Blockchain) EndBlock(req abciTypes.RequestEndBlock) abciTypes.Respons func (app *Blockchain) Info(req abciTypes.RequestInfo) (resInfo abciTypes.ResponseInfo) { return abciTypes.ResponseInfo{ LastBlockHeight: int64(app.height), - LastBlockAppHash: app.rootHash.Bytes(), + LastBlockAppHash: app.rootHash[:], } } @@ -316,7 +316,7 @@ func (app *Blockchain) Commit() abciTypes.ResponseCommit { panic(err) } - app.appDB.Set([]byte("root"), hash.Bytes()) + app.appDB.Set([]byte("root"), hash) // todo: make provider height := make([]byte, 8) @@ -329,7 +329,7 @@ func (app *Blockchain) Commit() abciTypes.ResponseCommit { app.updateCurrentState() return abciTypes.ResponseCommit{ - Data: app.rootHash.Bytes(), + Data: app.rootHash[:], } } @@ -346,7 +346,7 @@ func (app *Blockchain) updateCurrentRootHash() { // todo: make provider result := app.appDB.Get([]byte("root")) - app.rootHash = types.BytesToHash(result) + copy(app.rootHash[:], result) // todo: make provider result = app.appDB.Get([]byte("height")) diff --git a/core/state/statedb.go b/core/state/statedb.go index f19ef502d..55e80f9cd 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -621,7 +621,7 @@ func (s *StateDB) CreateCandidate( } // Commit writes the state to the underlying in-memory trie database. -func (s *StateDB) Commit(deleteEmptyObjects bool) (root types.Hash, version int64, err error) { +func (s *StateDB) Commit(deleteEmptyObjects bool) (root []byte, version int64, err error) { // Commit objects to the trie. for addr, stateObject := range s.stateObjects { @@ -685,7 +685,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root types.Hash, version int6 } } - return types.BytesToHash(hash), version, err + return hash, version, err } func (s *StateDB) CoinExists(symbol types.CoinSymbol) bool { diff --git a/genesis/genesis.go b/genesis/genesis.go index fd763e0c9..755f7e991 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -2,12 +2,10 @@ package genesis import ( "encoding/base64" - "encoding/hex" "encoding/json" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/helpers" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/libs/common" tmtypes "github.com/tendermint/tendermint/types" "math/big" "time" @@ -37,11 +35,7 @@ func GetTestnetGenesis() (*tmtypes.GenesisDoc, error) { } } - appHash, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") - - if err != nil { - return nil, err - } + appHash := [16]byte{} appState := AppState{ FirstValidatorAddress: types.HexToAddress("Mxee81347211c72524338f9680072af90744333146"), @@ -72,7 +66,7 @@ func GetTestnetGenesis() (*tmtypes.GenesisDoc, error) { GenesisTime: time.Date(2018, 9, 8, 0, 0, 0, 0, time.UTC), ConsensusParams: nil, Validators: validators, - AppHash: common.HexBytes(appHash), + AppHash: appHash[:], AppState: json.RawMessage(appStateJSON), } From 9b701eac0d0360ed95d08503a43c0e873945295b Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 16:06:48 +0300 Subject: [PATCH 13/29] Clean up gopkg.toml --- Gopkg.toml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Gopkg.toml b/Gopkg.toml index 3e72185da..c7e3ab0af 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -41,18 +41,10 @@ name = "github.com/gorilla/websocket" version = "=1.2.0" -[[constraint]] - branch = "master" - name = "github.com/rcrowley/go-metrics" - [[constraint]] name = "github.com/rs/cors" version = "=1.4.0" -[[constraint]] - branch = "master" - name = "github.com/syndtr/goleveldb" - [[constraint]] name = "github.com/danil-lashin/iavl" version = "=v0.11.1" From 4f92afc52330666a9edc93b0c60510e0f18dfc57 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 16:07:48 +0300 Subject: [PATCH 14/29] Update genesis --- genesis/genesis.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/genesis/genesis.go b/genesis/genesis.go index 755f7e991..18a5a2b3e 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -12,15 +12,15 @@ import ( ) var ( - Network = "minter-test-network-22-local" + Network = "minter-test-network-22" ) func GetTestnetGenesis() (*tmtypes.GenesisDoc, error) { validatorsPubKeys := []string{ - "f60aeE+af6WDOk4Rv1GA9eCQpc8nMtwsFwnkvk34KAk=", - //"c42kG6ant9abcpSvoVi4nFobQQy/DCRDyFxf4krR3Rw=", - //"bxbB/yGm+5RqrtD0wfzKJyty/ZBJiPkdOIMoK4rjG6I=", - //"nhPy9UaN14KzFkRPvWZZXhPbp9e9Pvob7NULQgRfWMY=", + "SuHuc+YTbIWwypM6mhNHdYozSIXxCzI4OYpnrC6xU7g=", + "c42kG6ant9abcpSvoVi4nFobQQy/DCRDyFxf4krR3Rw=", + "bxbB/yGm+5RqrtD0wfzKJyty/ZBJiPkdOIMoK4rjG6I=", + "nhPy9UaN14KzFkRPvWZZXhPbp9e9Pvob7NULQgRfWMY=", } validators := make([]tmtypes.GenesisValidator, len(validatorsPubKeys)) From 43cd4354ed044fd01f4ba8be25990335033e28e2 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 16:36:12 +0300 Subject: [PATCH 15/29] Change consensus TimeoutCommit to 4.5 sec, TimeoutPropose to 2 sec --- CHANGELOG.md | 1 + config/config.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd587717f..fb3d53b8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ BREAKING CHANGES - [core] Switch Ethereum Patricia Tree to IAVL +- [core] Change consensus TimeoutCommit to 4.5 sec, TimeoutPropose to 2 sec IMPROVEMENT diff --git a/config/config.go b/config/config.go index b09a9cbec..99c5db167 100644 --- a/config/config.go +++ b/config/config.go @@ -60,13 +60,13 @@ func DefaultConfig() *Config { cfg.Mempool.RecheckEmpty = true cfg.Consensus.WalPath = "tmdata/cs.wal/wal" - cfg.Consensus.TimeoutPropose = 3000 + cfg.Consensus.TimeoutPropose = 2000 cfg.Consensus.TimeoutProposeDelta = 500 cfg.Consensus.TimeoutPrevote = 1000 cfg.Consensus.TimeoutPrevoteDelta = 500 cfg.Consensus.TimeoutPrecommit = 1000 cfg.Consensus.TimeoutPrecommitDelta = 500 - cfg.Consensus.TimeoutCommit = 5000 + cfg.Consensus.TimeoutCommit = 4500 cfg.PrivValidator = "config/priv_validator.json" From a68131daedf1eeb77710957643dcec8e02ce03cc Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 16:57:54 +0300 Subject: [PATCH 16/29] Now validator punished if it misses 12 of 24 last blocks --- CHANGELOG.md | 1 + api/validators.go | 4 ++-- core/minter/minter.go | 1 - core/state/state_validator.go | 15 ++++++++++++++- core/state/statedb.go | 29 +++++++---------------------- 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb3d53b8f..1b392274f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ BREAKING CHANGES - [core] Switch Ethereum Patricia Tree to IAVL - [core] Change consensus TimeoutCommit to 4.5 sec, TimeoutPropose to 2 sec +- [core] Now validator punished if it misses 12 of 24 last blocks IMPROVEMENT diff --git a/api/validators.go b/api/validators.go index 2a10aa097..8b1c49f7a 100644 --- a/api/validators.go +++ b/api/validators.go @@ -28,14 +28,14 @@ type Candidate struct { type Validator struct { AccumReward string `json:"accumulated_reward"` - AbsentTimes uint `json:"absent_times"` + AbsentTimes int `json:"absent_times"` Candidate Candidate `json:"candidate"` } func makeResponseValidator(v state.Validator, state *state.StateDB) Validator { return Validator{ AccumReward: v.AccumReward.String(), - AbsentTimes: v.AbsentTimes, + AbsentTimes: v.CountAbsentTimes(), Candidate: makeResponseCandidate(*state.GetStateCandidate(v.PubKey)), } } diff --git a/core/minter/minter.go b/core/minter/minter.go index ab8087e40..febd37e02 100644 --- a/core/minter/minter.go +++ b/core/minter/minter.go @@ -122,7 +122,6 @@ func (app *Blockchain) BeginBlock(req abciTypes.RequestBeginBlock) abciTypes.Res copy(address[:], v.Validator.Address) if v.SignedLastBlock { - app.stateDeliver.SetValidatorPresent(address) app.validatorsStatuses[address] = ValidatorPresent } else { app.stateDeliver.SetValidatorAbsent(req.Header.Height, address) diff --git a/core/state/state_validator.go b/core/state/state_validator.go index 526d269f8..e854353b8 100644 --- a/core/state/state_validator.go +++ b/core/state/state_validator.go @@ -17,6 +17,7 @@ package state import ( + "github.com/tendermint/tendermint/libs/common" "io" "fmt" @@ -59,12 +60,24 @@ type Validator struct { PubKey types.Pubkey Commission uint AccumReward *big.Int - AbsentTimes uint + AbsentTimes *common.BitArray tmAddress *[20]byte toDrop bool } +func (validator *Validator) CountAbsentTimes() int { + count := 0 + + for i := 0; i < 24; i++ { + if validator.AbsentTimes.GetIndex(i) { + count++ + } + } + + return count +} + func (validator *Validator) IsToDrop() bool { return validator.toDrop } diff --git a/core/state/statedb.go b/core/state/statedb.go index 55e80f9cd..0d753d1b2 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -22,6 +22,7 @@ import ( "github.com/MinterTeam/minter-go-node/config" "github.com/MinterTeam/minter-go-node/eventsdb" "github.com/danil-lashin/iavl" + "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" "math/big" "sync" @@ -40,7 +41,7 @@ import ( const UnbondPeriod = 518400 var ( - ValidatorMaxAbsentTimes = uint(12) + ValidatorMaxAbsentTimes = 12 addressPrefix = []byte("a") coinPrefix = []byte("c") @@ -574,7 +575,7 @@ func (s *StateDB) CreateValidator( PubKey: pubkey, Commission: commission, AccumReward: big.NewInt(0), - AbsentTimes: 0, + AbsentTimes: common.NewBitArray(24), }) s.MarkStateValidatorsDirty() @@ -1042,11 +1043,11 @@ func (s *StateDB) SetValidatorAbsent(height int64, address [20]byte) { return } - validator.AbsentTimes = validator.AbsentTimes + 1 + validator.AbsentTimes.SetIndex(int(height%24), true) - if validator.AbsentTimes > ValidatorMaxAbsentTimes { + if validator.CountAbsentTimes() > ValidatorMaxAbsentTimes { candidate.Status = CandidateStatusOffline - validator.AbsentTimes = 0 + validator.AbsentTimes = common.NewBitArray(24) validator.toDrop = true totalStake := big.NewInt(0) @@ -1097,8 +1098,6 @@ func (s *StateDB) PunishByzantineValidator(currentBlock uint64, address [20]byte for i := range validators.data { validator := &validators.data[i] if validator.GetAddress() == address { - validator.AbsentTimes = validator.AbsentTimes + 1 - candidates := s.getStateCandidates() var candidate *Candidate @@ -1154,20 +1153,6 @@ func (s *StateDB) PunishFrozenFundsWithAddress(fromBlock uint64, toBlock uint64, } } -func (s *StateDB) SetValidatorPresent(address [20]byte) { - validators := s.getStateValidators() - - for i := range validators.data { - validator := &validators.data[i] - if validator.GetAddress() == address { - validator.AbsentTimes = 0 - } - } - - s.setStateValidators(validators) - s.MarkStateValidatorsDirty() -} - func (s *StateDB) SetNewValidators(candidates []Candidate) { oldVals := s.getStateValidators() @@ -1175,7 +1160,7 @@ func (s *StateDB) SetNewValidators(candidates []Candidate) { for _, candidate := range candidates { accumReward := big.NewInt(0) - absentTimes := uint(0) + absentTimes := common.NewBitArray(24) for _, oldVal := range oldVals.data { if oldVal.GetAddress() == candidate.GetAddress() { From b8c2d9a933ad01c657611dcb15a0f01dcd26df8f Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 17:16:34 +0300 Subject: [PATCH 17/29] fix --- core/minter/minter.go | 1 + core/state/bitarray.go | 373 ++++++++++++++++++++++++++++++++++ core/state/state_validator.go | 5 +- core/state/statedb.go | 23 ++- genesis/genesis.go | 11 +- 5 files changed, 400 insertions(+), 13 deletions(-) create mode 100644 core/state/bitarray.go diff --git a/core/minter/minter.go b/core/minter/minter.go index febd37e02..5c2c11e5e 100644 --- a/core/minter/minter.go +++ b/core/minter/minter.go @@ -122,6 +122,7 @@ func (app *Blockchain) BeginBlock(req abciTypes.RequestBeginBlock) abciTypes.Res copy(address[:], v.Validator.Address) if v.SignedLastBlock { + app.stateDeliver.SetValidatorPresent(req.Header.Height, address) app.validatorsStatuses[address] = ValidatorPresent } else { app.stateDeliver.SetValidatorAbsent(req.Header.Height, address) diff --git a/core/state/bitarray.go b/core/state/bitarray.go new file mode 100644 index 000000000..3532d663a --- /dev/null +++ b/core/state/bitarray.go @@ -0,0 +1,373 @@ +package state + +import ( + "encoding/binary" + "fmt" + "github.com/tendermint/tendermint/libs/common" + "regexp" + "strings" + "sync" +) + +// BitArray is a thread-safe implementation of a bit array. +type BitArray struct { + mtx sync.Mutex + Bits uint `json:"bits"` // NOTE: persisted via reflect, must be exported + Elems []uint64 `json:"elems"` // NOTE: persisted via reflect, must be exported +} + +// NewBitArray returns a new bit array. +// It returns nil if the number of bits is zero. +func NewBitArray(bits uint) *BitArray { + if bits <= 0 { + return nil + } + return &BitArray{ + Bits: bits, + Elems: make([]uint64, (bits+63)/64), + } +} + +// Size returns the number of bits in the bitarray +func (bA *BitArray) Size() uint { + if bA == nil { + return 0 + } + return bA.Bits +} + +// GetIndex returns the bit at index i within the bit array. +// The behavior is undefined if i >= bA.Bits +func (bA *BitArray) GetIndex(i uint) bool { + if bA == nil { + return false + } + bA.mtx.Lock() + defer bA.mtx.Unlock() + return bA.getIndex(i) +} + +func (bA *BitArray) getIndex(i uint) bool { + if i >= bA.Bits { + return false + } + return bA.Elems[i/64]&(uint64(1)< 0 +} + +// SetIndex sets the bit at index i within the bit array. +// The behavior is undefined if i >= bA.Bits +func (bA *BitArray) SetIndex(i uint, v bool) bool { + if bA == nil { + return false + } + bA.mtx.Lock() + defer bA.mtx.Unlock() + return bA.setIndex(i, v) +} + +func (bA *BitArray) setIndex(i uint, v bool) bool { + if i >= bA.Bits { + return false + } + if v { + bA.Elems[i/64] |= (uint64(1) << uint(i%64)) + } else { + bA.Elems[i/64] &= ^(uint64(1) << uint(i%64)) + } + return true +} + +// Copy returns a copy of the provided bit array. +func (bA *BitArray) Copy() *BitArray { + if bA == nil { + return nil + } + bA.mtx.Lock() + defer bA.mtx.Unlock() + return bA.copy() +} + +func (bA *BitArray) copy() *BitArray { + c := make([]uint64, len(bA.Elems)) + copy(c, bA.Elems) + return &BitArray{ + Bits: bA.Bits, + Elems: c, + } +} + +func (bA *BitArray) copyBits(bits uint) *BitArray { + c := make([]uint64, (bits+63)/64) + copy(c, bA.Elems) + return &BitArray{ + Bits: bits, + Elems: c, + } +} + +// Or returns a bit array resulting from a bitwise OR of the two bit arrays. +// If the two bit-arrys have different lengths, Or right-pads the smaller of the two bit-arrays with zeroes. +// Thus the size of the return value is the maximum of the two provided bit arrays. +func (bA *BitArray) Or(o *BitArray) *BitArray { + if bA == nil && o == nil { + return nil + } + if bA == nil && o != nil { + return o.Copy() + } + if o == nil { + return bA.Copy() + } + bA.mtx.Lock() + o.mtx.Lock() + defer func() { + bA.mtx.Unlock() + o.mtx.Unlock() + }() + c := bA.copyBits(uint(common.MaxInt(int(bA.Bits), int(o.Bits)))) + for i := 0; i < len(c.Elems); i++ { + c.Elems[i] |= o.Elems[i] + } + return c +} + +// And returns a bit array resulting from a bitwise AND of the two bit arrays. +// If the two bit-arrys have different lengths, this truncates the larger of the two bit-arrays from the right. +// Thus the size of the return value is the minimum of the two provided bit arrays. +func (bA *BitArray) And(o *BitArray) *BitArray { + if bA == nil || o == nil { + return nil + } + bA.mtx.Lock() + o.mtx.Lock() + defer func() { + bA.mtx.Unlock() + o.mtx.Unlock() + }() + return bA.and(o) +} + +func (bA *BitArray) and(o *BitArray) *BitArray { + c := bA.copyBits(uint(common.MinInt(int(bA.Bits), int(o.Bits)))) + for i := 0; i < len(c.Elems); i++ { + c.Elems[i] &= o.Elems[i] + } + return c +} + +// Not returns a bit array resulting from a bitwise Not of the provided bit array. +func (bA *BitArray) Not() *BitArray { + if bA == nil { + return nil // Degenerate + } + bA.mtx.Lock() + defer bA.mtx.Unlock() + return bA.not() +} + +func (bA *BitArray) not() *BitArray { + c := bA.copy() + for i := 0; i < len(c.Elems); i++ { + c.Elems[i] = ^c.Elems[i] + } + return c +} + +// Sub subtracts the two bit-arrays bitwise, without carrying the bits. +// This is essentially bA.And(o.Not()). +// If bA is longer than o, o is right padded with zeroes. +func (bA *BitArray) Sub(o *BitArray) *BitArray { + if bA == nil || o == nil { + // TODO: Decide if we should do 1's complement here? + return nil + } + bA.mtx.Lock() + o.mtx.Lock() + defer func() { + bA.mtx.Unlock() + o.mtx.Unlock() + }() + if bA.Bits > o.Bits { + c := bA.copy() + for i := 0; i < len(o.Elems)-1; i++ { + c.Elems[i] &= ^c.Elems[i] + } + i := uint(len(o.Elems) - 1) + if i >= 0 { + for idx := i * 64; idx < o.Bits; idx++ { + c.setIndex(idx, c.getIndex(idx) && !o.getIndex(idx)) + } + } + return c + } + return bA.and(o.not()) // Note degenerate case where o == nil +} + +// IsEmpty returns true iff all bits in the bit array are 0 +func (bA *BitArray) IsEmpty() bool { + if bA == nil { + return true // should this be opposite? + } + bA.mtx.Lock() + defer bA.mtx.Unlock() + for _, e := range bA.Elems { + if e > 0 { + return false + } + } + return true +} + +// IsFull returns true iff all bits in the bit array are 1. +func (bA *BitArray) IsFull() bool { + if bA == nil { + return true + } + bA.mtx.Lock() + defer bA.mtx.Unlock() + + // Check all elements except the last + for _, elem := range bA.Elems[:len(bA.Elems)-1] { + if (^elem) != 0 { + return false + } + } + + // Check that the last element has (lastElemBits) 1's + lastElemBits := (bA.Bits+63)%64 + 1 + lastElem := bA.Elems[len(bA.Elems)-1] + return (lastElem+1)&((uint64(1)<}, +// where is a sequence of 'x' (1) and '_' (0). +// The includes spaces and newlines to help people. +// For a simple sequence of 'x' and '_' characters with no spaces or newlines, +// see the MarshalJSON() method. +// Example: "BA{_x_}" or "nil-BitArray" for nil. +func (bA *BitArray) String() string { + return bA.StringIndented("") +} + +// StringIndented returns the same thing as String(), but applies the indent +// at every 10th bit, and twice at every 50th bit. +func (bA *BitArray) StringIndented(indent string) string { + if bA == nil { + return "nil-BitArray" + } + bA.mtx.Lock() + defer bA.mtx.Unlock() + return bA.stringIndented(indent) +} + +func (bA *BitArray) stringIndented(indent string) string { + lines := []string{} + bits := "" + for i := uint(0); i < bA.Bits; i++ { + if bA.getIndex(i) { + bits += "x" + } else { + bits += "_" + } + if i%100 == 99 { + lines = append(lines, bits) + bits = "" + } + if i%10 == 9 { + bits += indent + } + if i%50 == 49 { + bits += indent + } + } + if len(bits) > 0 { + lines = append(lines, bits) + } + return fmt.Sprintf("BA{%v:%v}", bA.Bits, strings.Join(lines, indent)) +} + +// Bytes returns the byte representation of the bits within the bitarray. +func (bA *BitArray) Bytes() []byte { + bA.mtx.Lock() + defer bA.mtx.Unlock() + + numBytes := (bA.Bits + 7) / 8 + bytes := make([]byte, numBytes) + for i := 0; i < len(bA.Elems); i++ { + elemBytes := [8]byte{} + binary.LittleEndian.PutUint64(elemBytes[:], bA.Elems[i]) + copy(bytes[i*8:], elemBytes[:]) + } + return bytes +} + +// Update sets the bA's bits to be that of the other bit array. +// The copying begins from the begin of both bit arrays. +func (bA *BitArray) Update(o *BitArray) { + if bA == nil || o == nil { + return + } + bA.mtx.Lock() + o.mtx.Lock() + defer func() { + bA.mtx.Unlock() + o.mtx.Unlock() + }() + + copy(bA.Elems, o.Elems) +} + +// MarshalJSON implements json.Marshaler interface by marshaling bit array +// using a custom format: a string of '-' or 'x' where 'x' denotes the 1 bit. +func (bA *BitArray) MarshalJSON() ([]byte, error) { + if bA == nil { + return []byte("null"), nil + } + + bA.mtx.Lock() + defer bA.mtx.Unlock() + + bits := `"` + for i := uint(0); i < bA.Bits; i++ { + if bA.getIndex(i) { + bits += `x` + } else { + bits += `_` + } + } + bits += `"` + return []byte(bits), nil +} + +var bitArrayJSONRegexp = regexp.MustCompile(`\A"([_x]*)"\z`) + +// UnmarshalJSON implements json.Unmarshaler interface by unmarshaling a custom +// JSON description. +func (bA *BitArray) UnmarshalJSON(bz []byte) error { + b := string(bz) + if b == "null" { + // This is required e.g. for encoding/json when decoding + // into a pointer with pre-allocated BitArray. + bA.Bits = 0 + bA.Elems = nil + return nil + } + + // Validate 'b'. + match := bitArrayJSONRegexp.FindStringSubmatch(b) + if match == nil { + return fmt.Errorf("BitArray in JSON should be a string of format %q but got %s", bitArrayJSONRegexp.String(), b) + } + bits := match[1] + + // Construct new BitArray and copy over. + numBits := uint(len(bits)) + bA2 := NewBitArray(numBits) + for i := uint(0); i < numBits; i++ { + if bits[i] == 'x' { + bA2.SetIndex(i, true) + } + } + *bA = *bA2 + return nil +} diff --git a/core/state/state_validator.go b/core/state/state_validator.go index e854353b8..97c97b179 100644 --- a/core/state/state_validator.go +++ b/core/state/state_validator.go @@ -17,7 +17,6 @@ package state import ( - "github.com/tendermint/tendermint/libs/common" "io" "fmt" @@ -60,7 +59,7 @@ type Validator struct { PubKey types.Pubkey Commission uint AccumReward *big.Int - AbsentTimes *common.BitArray + AbsentTimes *BitArray tmAddress *[20]byte toDrop bool @@ -69,7 +68,7 @@ type Validator struct { func (validator *Validator) CountAbsentTimes() int { count := 0 - for i := 0; i < 24; i++ { + for i := uint(0); i < 24; i++ { if validator.AbsentTimes.GetIndex(i) { count++ } diff --git a/core/state/statedb.go b/core/state/statedb.go index 0d753d1b2..6375bc241 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -22,7 +22,6 @@ import ( "github.com/MinterTeam/minter-go-node/config" "github.com/MinterTeam/minter-go-node/eventsdb" "github.com/danil-lashin/iavl" - "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" "math/big" "sync" @@ -575,7 +574,7 @@ func (s *StateDB) CreateValidator( PubKey: pubkey, Commission: commission, AccumReward: big.NewInt(0), - AbsentTimes: common.NewBitArray(24), + AbsentTimes: NewBitArray(24), }) s.MarkStateValidatorsDirty() @@ -1020,6 +1019,20 @@ func (s *StateDB) SetCandidateOffline(pubkey []byte) { s.MarkStateCandidateDirty() } +func (s *StateDB) SetValidatorPresent(height int64, address [20]byte) { + validators := s.getStateValidators() +cd + for i := range validators.data { + validator := &validators.data[i] + if validator.GetAddress() == address { + validator.AbsentTimes.SetIndex(uint(height%24), false) + } + } + + s.setStateValidators(validators) + s.MarkStateValidatorsDirty() +} + func (s *StateDB) SetValidatorAbsent(height int64, address [20]byte) { edb := eventsdb.GetCurrent() @@ -1043,11 +1056,11 @@ func (s *StateDB) SetValidatorAbsent(height int64, address [20]byte) { return } - validator.AbsentTimes.SetIndex(int(height%24), true) + validator.AbsentTimes.SetIndex(uint(height%24), true) if validator.CountAbsentTimes() > ValidatorMaxAbsentTimes { candidate.Status = CandidateStatusOffline - validator.AbsentTimes = common.NewBitArray(24) + validator.AbsentTimes = NewBitArray(24) validator.toDrop = true totalStake := big.NewInt(0) @@ -1160,7 +1173,7 @@ func (s *StateDB) SetNewValidators(candidates []Candidate) { for _, candidate := range candidates { accumReward := big.NewInt(0) - absentTimes := common.NewBitArray(24) + absentTimes := NewBitArray(24) for _, oldVal := range oldVals.data { if oldVal.GetAddress() == candidate.GetAddress() { diff --git a/genesis/genesis.go b/genesis/genesis.go index 18a5a2b3e..abb3795bd 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -12,15 +12,16 @@ import ( ) var ( - Network = "minter-test-network-22" + Network = "minter-test-network-22-local" ) func GetTestnetGenesis() (*tmtypes.GenesisDoc, error) { validatorsPubKeys := []string{ - "SuHuc+YTbIWwypM6mhNHdYozSIXxCzI4OYpnrC6xU7g=", - "c42kG6ant9abcpSvoVi4nFobQQy/DCRDyFxf4krR3Rw=", - "bxbB/yGm+5RqrtD0wfzKJyty/ZBJiPkdOIMoK4rjG6I=", - "nhPy9UaN14KzFkRPvWZZXhPbp9e9Pvob7NULQgRfWMY=", + "f60aeE+af6WDOk4Rv1GA9eCQpc8nMtwsFwnkvk34KAk=", + //"SuHuc+YTbIWwypM6mhNHdYozSIXxCzI4OYpnrC6xU7g=", + //"c42kG6ant9abcpSvoVi4nFobQQy/DCRDyFxf4krR3Rw=", + //"bxbB/yGm+5RqrtD0wfzKJyty/ZBJiPkdOIMoK4rjG6I=", + //"nhPy9UaN14KzFkRPvWZZXhPbp9e9Pvob7NULQgRfWMY=", } validators := make([]tmtypes.GenesisValidator, len(validatorsPubKeys)) From 3931df7d6e6ec3d82fefe304069488ebca84be09 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 17:18:01 +0300 Subject: [PATCH 18/29] fix --- core/state/statedb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 6375bc241..67180aaca 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1021,7 +1021,7 @@ func (s *StateDB) SetCandidateOffline(pubkey []byte) { func (s *StateDB) SetValidatorPresent(height int64, address [20]byte) { validators := s.getStateValidators() -cd + for i := range validators.data { validator := &validators.data[i] if validator.GetAddress() == address { From 8e7ba2502d447bf3c1cf48d8da09e89c83e97f90 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 17:32:56 +0300 Subject: [PATCH 19/29] Add node's total stake #99 --- gui/gui-packr.go | 2 +- gui/html/index.html | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/gui/gui-packr.go b/gui/gui-packr.go index b81f76175..0ab93fc5f 100644 --- a/gui/gui-packr.go +++ b/gui/gui-packr.go @@ -7,5 +7,5 @@ import "github.com/gobuffalo/packr" // You can use the "packr clean" command to clean up this, // and any other packr generated files. func init() { - packr.PackJSONBytes("./html", "index.html", "\"PGh0bWw+CjxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiCiAgICAgICAgICBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIHVzZXItc2NhbGFibGU9bm8sIGluaXRpYWwtc2NhbGU9MS4wLCBtYXhpbXVtLXNjYWxlPTEuMCwgbWluaW11bS1zY2FsZT0xLjAiPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJpZT1lZGdlIj4KICAgIDx0aXRsZT5NaW50ZXIgTm9kZSBHVUk8L3RpdGxlPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL3N0YWNrcGF0aC5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC80LjEuMC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiCiAgICAgICAgICBpbnRlZ3JpdHk9InNoYTM4NC05Z1ZRNGRZRnd3V1NqSURabkxFV254Q2plU1dGcGhKaXdHUFhyMWpkZEloT2VnaXUxRndPNXFSR3ZGWE9kSlo0IiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly91c2UuZm9udGF3ZXNvbWUuY29tL3JlbGVhc2VzL3Y1LjEuMS9jc3MvYWxsLmNzcyIgaW50ZWdyaXR5PSJzaGEzODQtTzh3aFMzZmhHMk9uQTVLYXMwWTlsM2NmcG1ZamFwakkwRTR0aGVINGl1TUQrcExoYmY2SkkwaklNZlljSzN5WiIgY3Jvc3NvcmlnaW49ImFub255bW91cyI+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS92dWUiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2F4aW9zLzAuMTguMC9heGlvcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2NyeXB0by1qcy8zLjEuOS0xL2NyeXB0by1qcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHN0eWxlPgoKICAgICAgICAuY2FyZCB7CiAgICAgICAgICAgIG1hcmdpbi1ib3R0b206IDIwcHg7CiAgICAgICAgfQoKICAgICAgICBodG1sLGJvZHksLmJvZHksI2FwcCB7CiAgICAgICAgICAgIG1pbi1oZWlnaHQ6IDEwMCU7CiAgICAgICAgfQogICAgICAgIAogICAgICAgIC5ib2R5IHsKICAgICAgICAgICAgcGFkZGluZy10b3A6IDE1cHg7CiAgICAgICAgfQoKICAgICAgICAudGFibGUgewogICAgICAgICAgICBtYXJnaW4tYm90dG9tOiAwOwogICAgICAgICAgICB0YWJsZS1sYXlvdXQ6IGZpeGVkOwogICAgICAgIH0KCiAgICAgICAgLmNhcmQtaGVhZGVyIHsKICAgICAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICAgICAgfQoKICAgICAgICAuY2FyZC1oZWFkZXIgewogICAgICAgICAgICBwYWRkaW5nLWxlZnQ6IDEycHg7CiAgICAgICAgfQoKICAgICAgICAuaCB7CiAgICAgICAgICAgIHdpZHRoOiAyMDBweDsKICAgICAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2YzZjNmMzsKICAgICAgICAgICAgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2NjYzsKICAgICAgICB9CgogICAgICAgIC5iZy1zdWNjZXNzLCAuYmctZGFuZ2VyIHsKICAgICAgICAgICAgY29sb3I6IHdoaXRlOwogICAgICAgIH0KCiAgICAgICAgLmJnLWRhbmdlciB7CiAgICAgICAgICAgIGJvcmRlci1jb2xvcjogI2RjMzU0NSAhaW1wb3J0YW50OwogICAgICAgIH0KCiAgICAgICAgLmJnLXN1Y2Nlc3MgewogICAgICAgICAgICBib3JkZXItY29sb3I6ICMyOGE3NDUgIWltcG9ydGFudDsKICAgICAgICB9CgogICAgICAgIC5mYS1jaGVjayB7CiAgICAgICAgICAgIGNvbG9yOiBncmVlbjsKICAgICAgICB9CgogICAgICAgIC5mYS1leGNsYW1hdGlvbi1jaXJjbGUgewogICAgICAgICAgICBjb2xvcjogcmVkOwogICAgICAgIH0KICAgIDwvc3R5bGU+CjwvaGVhZD4KPGJvZHkgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICMzNDNhNDAxYSI+CjxkaXYgaWQ9ImFwcCI+CiAgICA8bmF2IGNsYXNzPSJuYXZiYXIgbmF2YmFyLWV4cGFuZC1sZyBuYXZiYXItZGFyayBiZy1kYXJrIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIiPgogICAgICAgICAgICA8c3BhbiBjbGFzcz0ibmF2YmFyLWJyYW5kIG1iLTAgaDEiPjxpIGNsYXNzPSJmYXMgZmEtdGVybWluYWwiPjwvaT4gJm5ic3A7IE1pbnRlciBGdWxsIE5vZGUgU3RhdHVzPC9zcGFuPgogICAgICAgIDwvZGl2PgogICAgPC9uYXY+CiAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIgYm9keSIgdi1pZj0iZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWRhbmdlciIgcm9sZT0iYWxlcnQiPgogICAgICAgICAgICA8aDQgY2xhc3M9ImFsZXJ0LWhlYWRpbmciPkVycm9yIHdoaWxlIGNvbm5lY3RpbmcgdG8gbG9jYWwgbm9kZTwvaDQ+CiAgICAgICAgICAgIDxwIGNsYXNzPSJtYi0wIj57eyBlcnJvciB9fTwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0iY29udGFpbmVyIGJvZHkgYmctd2hpdGUiIHYtaWY9InN0YXR1cyAmJiAhZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9InJvdyI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIE5vZGUgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk1vbmlrZXI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubW9uaWtlciB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Tm9kZSBJRDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby5pZCB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TGlzdGVuIEFkZHI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubGlzdGVuX2FkZHIgfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk5ldHdvcmsgSUQ8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubmV0d29yayB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TWludGVyIFZlcnNpb248L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZlcnNpb24gfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPlRlbmRlcm1pbnQgVmVyc2lvbjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby52ZXJzaW9uIH19PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIiB2LWlmPSJuZXRfaW5mbyI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZC1oZWFkZXIiPgogICAgICAgICAgICAgICAgICAgICAgICBOZXQgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIExpc3RlbmluZzwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PGkgOmNsYXNzPSJ7J2ZhLWNoZWNrJzogbmV0X2luZm8ubGlzdGVuaW5nfSIgY2xhc3M9ImZhcyI+PC9pPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Q29ubmVjdGVkIFBlZXJzPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD57eyBuZXRfaW5mby5uX3BlZXJzIH19IDxpIDpjbGFzcz0ieydmYS1leGNsYW1hdGlvbi1jaXJjbGUnOiBuZXRfaW5mby5uX3BlZXJzIDwgMX0iIGNsYXNzPSJmYXMiPjwvaT48L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIFN5bmNpbmcgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIFN5bmNlZDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0ic3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCI+Tm88L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0iIXN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXAiPlllczwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aSA6Y2xhc3M9InsnZmEtY2hlY2snOiAhc3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCwgJ2ZhLWV4Y2xhbWF0aW9uLWNpcmNsZSc6IHN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXB9IiBjbGFzcz0iZmFzIj48L2k+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2sgSGVpZ2h0PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAje3sgc3RhdHVzLnN5bmNfaW5mby5sYXRlc3RfYmxvY2tfaGVpZ2h0IH19IDxzcGFuIHYtaWY9Im1hc3RlclN0YXR1cyIgY2xhc3M9InRleHQtbXV0ZWQiPm9mIHt7IG1hc3RlclN0YXR1cy5sYXRlc3RfYmxvY2tfaGVpZ2h0IH19PC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2sgVGltZTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7eyBzdGF0dXMuc3luY19pbmZvLmxhdGVzdF9ibG9ja190aW1lIH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQtaGVhZGVyIj4KICAgICAgICAgICAgICAgICAgICAgICAgVmFsaWRhdG9yIEluZm8KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8dGFibGUgY2xhc3M9InRhYmxlIGNhcmQtYm9keSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPlB1YmxpYyBLZXk8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZhbGlkYXRvclB1YktleSB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD5TdGF0dXM8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZhbGlkYXRvclN0YXR1cyB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD5Wb3RpbmcgUG93ZXI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IG5pY2VOdW0oc3RhdHVzLnZhbGlkYXRvcl9pbmZvLnZvdGluZ19wb3dlcikgfX0gPHNwYW4gY2xhc3M9InRleHQtbXV0ZWQiPm9mIDEwMCwwMDAsMDAwPC9zcGFuPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2Pgo8L2Rpdj4KPHNjcmlwdD4KICAgIG5ldyBWdWUoewogICAgICAgIGVsOiAnI2FwcCcsCiAgICAgICAgZGF0YTogewogICAgICAgICAgICBtYXN0ZXJTdGF0dXM6IG51bGwsCiAgICAgICAgICAgIHN0YXR1czogbnVsbCwKICAgICAgICAgICAgdmVyc2lvbjogbnVsbCwKICAgICAgICAgICAgbmV0X2luZm86IG51bGwsCiAgICAgICAgICAgIGVycm9yOiBudWxsLAogICAgICAgICAgICB2YWxpZGF0b3JQdWJLZXk6ICcuLi4nLAogICAgICAgICAgICB2YWxpZGF0b3JTdGF0dXM6ICcuLi4nCiAgICAgICAgfSwKICAgICAgICBtb3VudGVkKCkgewogICAgICAgICAgICB0aGlzLnJlZnJlc2goKQogICAgICAgIH0sCiAgICAgICAgbWV0aG9kczogewogICAgICAgICAgICBuaWNlTnVtKG51bSkgewogICAgICAgICAgICAgICAgcmV0dXJuIG51bS50b1N0cmluZygpLnJlcGxhY2UoL1xCKD89KFxkezN9KSsoPyFcZCkpL2csICIsIikKICAgICAgICAgICAgfSwKICAgICAgICAgICAgYmFzZTY0VG9IZXgoYmFzZTY0KSB7CiAgICAgICAgICAgICAgICByZXR1cm4gQ3J5cHRvSlMuZW5jLkJhc2U2NC5wYXJzZShiYXNlNjQpLnRvU3RyaW5nKCkKICAgICAgICAgICAgfSwKICAgICAgICAgICAgcmVmcmVzaCgpIHsKICAgICAgICAgICAgICAgIGF4aW9zLmFsbChbCiAgICAgICAgICAgICAgICAgICAgYXhpb3MuZ2V0KCIvLyIgKyB3aW5kb3cubG9jYXRpb24uaG9zdG5hbWUgKyAnOjg4NDEvYXBpL3N0YXR1cycpLAogICAgICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiLy8iICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgJzo4ODQxL2FwaS9uZXRfaW5mbycpLAogICAgICAgICAgICAgICAgXSkudGhlbihheGlvcy5zcHJlYWQoZnVuY3Rpb24gKHN0YXR1cywgbmV0X2luZm8pIHsKICAgICAgICAgICAgICAgICAgICB0aGlzLmVycm9yID0gbnVsbAoKICAgICAgICAgICAgICAgICAgICB0aGlzLnN0YXR1cyA9IHN0YXR1cy5kYXRhLnJlc3VsdC50bV9zdGF0dXMKICAgICAgICAgICAgICAgICAgICB0aGlzLnZlcnNpb24gPSBzdGF0dXMuZGF0YS5yZXN1bHQudmVyc2lvbgogICAgICAgICAgICAgICAgICAgIHRoaXMubmV0X2luZm8gPSBuZXRfaW5mby5kYXRhLnJlc3VsdAoKICAgICAgICAgICAgICAgICAgICB0aGlzLnZhbGlkYXRvclB1YktleSA9ICdNcCcgKyB0aGlzLmJhc2U2NFRvSGV4KHN0YXR1cy5kYXRhLnJlc3VsdC50bV9zdGF0dXMudmFsaWRhdG9yX2luZm8ucHViX2tleS52YWx1ZSkKCiAgICAgICAgICAgICAgICAgICAgYXhpb3MuYWxsKFsKICAgICAgICAgICAgICAgICAgICAgICAgYXhpb3MuZ2V0KCIvLyIgKyB3aW5kb3cubG9jYXRpb24uaG9zdG5hbWUgKyAnOjg4NDEvYXBpL3ZhbGlkYXRvcnMnKSwKICAgICAgICAgICAgICAgICAgICAgICAgYXhpb3MuZ2V0KCIvLyIgKyB3aW5kb3cubG9jYXRpb24uaG9zdG5hbWUgKyAnOjg4NDEvYXBpL2NhbmRpZGF0ZS8nICsgdGhpcy52YWxpZGF0b3JQdWJLZXkpLAogICAgICAgICAgICAgICAgICAgIF0pLnRoZW4oYXhpb3Muc3ByZWFkKGZ1bmN0aW9uICh2YWxpZGF0b3JzLCBjYW5kaWRhdGUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGNhbmRpZGF0ZS5kYXRhLmNvZGUgIT09IDApIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudmFsaWRhdG9yU3RhdHVzID0gJ05vdCBkZWNsYXJlZCc7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4KICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHZhbGlkYXRvcnMuZGF0YS5yZXN1bHQuZmluZChmdW5jdGlvbih2YWwpIHsgcmV0dXJuIHZhbC5jYW5kaWRhdGUucHViX2tleSA9PT0gdGhpcy52YWxpZGF0b3JQdWJLZXkgfS5iaW5kKHRoaXMpKSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy52YWxpZGF0b3JTdGF0dXMgPSAnVmFsaWRhdGluZyc7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4KICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGNhbmRpZGF0ZS5kYXRhLnJlc3VsdC5zdGF0dXMgPT09IDIpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudmFsaWRhdG9yU3RhdHVzID0gJ0NhbmRpZGF0ZScKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybgogICAgICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnZhbGlkYXRvclN0YXR1cyA9ICdEb3duJzsKICAgICAgICAgICAgICAgICAgICB9LmJpbmQodGhpcykpKTsKCiAgICAgICAgICAgICAgICAgICAgc2V0VGltZW91dCh0aGlzLnJlZnJlc2gsIDUwMCkKCgogICAgICAgICAgICAgICAgfS5iaW5kKHRoaXMpKSkuY2F0Y2goZnVuY3Rpb24gKHJlYXNvbikgewogICAgICAgICAgICAgICAgICAgIHRoaXMuZXJyb3IgPSByZWFzb24udG9TdHJpbmcoKTsKICAgICAgICAgICAgICAgICAgICBzZXRUaW1lb3V0KHRoaXMucmVmcmVzaCwgNTAwKQogICAgICAgICAgICAgICAgfS5iaW5kKHRoaXMpKQoKICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiaHR0cHM6Ly9taW50ZXItbm9kZS0xLnRlc3RuZXQubWludGVyLm5ldHdvcmsvYXBpL3N0YXR1cyIpLnRoZW4oZnVuY3Rpb24gKG1hc3RlclN0YXR1cykgewogICAgICAgICAgICAgICAgICAgIHRoaXMubWFzdGVyU3RhdHVzID0gbWFzdGVyU3RhdHVzLmRhdGEucmVzdWx0CiAgICAgICAgICAgICAgICB9LmJpbmQodGhpcykpCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICB9KQo8L3NjcmlwdD4KPC9ib2R5Pgo8L2h0bWw+\"") + packr.PackJSONBytes("./html", "index.html", "\"PGh0bWw+CjxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiCiAgICAgICAgICBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIHVzZXItc2NhbGFibGU9bm8sIGluaXRpYWwtc2NhbGU9MS4wLCBtYXhpbXVtLXNjYWxlPTEuMCwgbWluaW11bS1zY2FsZT0xLjAiPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJpZT1lZGdlIj4KICAgIDx0aXRsZT5NaW50ZXIgTm9kZSBHVUk8L3RpdGxlPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL3N0YWNrcGF0aC5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC80LjEuMC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiCiAgICAgICAgICBpbnRlZ3JpdHk9InNoYTM4NC05Z1ZRNGRZRnd3V1NqSURabkxFV254Q2plU1dGcGhKaXdHUFhyMWpkZEloT2VnaXUxRndPNXFSR3ZGWE9kSlo0IiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly91c2UuZm9udGF3ZXNvbWUuY29tL3JlbGVhc2VzL3Y1LjEuMS9jc3MvYWxsLmNzcyIgaW50ZWdyaXR5PSJzaGEzODQtTzh3aFMzZmhHMk9uQTVLYXMwWTlsM2NmcG1ZamFwakkwRTR0aGVINGl1TUQrcExoYmY2SkkwaklNZlljSzN5WiIgY3Jvc3NvcmlnaW49ImFub255bW91cyI+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS92dWUiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2F4aW9zLzAuMTguMC9heGlvcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2NyeXB0by1qcy8zLjEuOS0xL2NyeXB0by1qcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHN0eWxlPgoKICAgICAgICAuY2FyZCB7CiAgICAgICAgICAgIG1hcmdpbi1ib3R0b206IDIwcHg7CiAgICAgICAgfQoKICAgICAgICBodG1sLGJvZHksLmJvZHksI2FwcCB7CiAgICAgICAgICAgIG1pbi1oZWlnaHQ6IDEwMCU7CiAgICAgICAgfQogICAgICAgIAogICAgICAgIC5ib2R5IHsKICAgICAgICAgICAgcGFkZGluZy10b3A6IDE1cHg7CiAgICAgICAgfQoKICAgICAgICAudGFibGUgewogICAgICAgICAgICBtYXJnaW4tYm90dG9tOiAwOwogICAgICAgICAgICB0YWJsZS1sYXlvdXQ6IGZpeGVkOwogICAgICAgIH0KCiAgICAgICAgLmNhcmQtaGVhZGVyIHsKICAgICAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICAgICAgfQoKICAgICAgICAuY2FyZC1oZWFkZXIgewogICAgICAgICAgICBwYWRkaW5nLWxlZnQ6IDEycHg7CiAgICAgICAgfQoKICAgICAgICAuaCB7CiAgICAgICAgICAgIHdpZHRoOiAyMDBweDsKICAgICAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2YzZjNmMzsKICAgICAgICAgICAgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2NjYzsKICAgICAgICB9CgogICAgICAgIC5iZy1zdWNjZXNzLCAuYmctZGFuZ2VyIHsKICAgICAgICAgICAgY29sb3I6IHdoaXRlOwogICAgICAgIH0KCiAgICAgICAgLmJnLWRhbmdlciB7CiAgICAgICAgICAgIGJvcmRlci1jb2xvcjogI2RjMzU0NSAhaW1wb3J0YW50OwogICAgICAgIH0KCiAgICAgICAgLmJnLXN1Y2Nlc3MgewogICAgICAgICAgICBib3JkZXItY29sb3I6ICMyOGE3NDUgIWltcG9ydGFudDsKICAgICAgICB9CgogICAgICAgIC5mYS1jaGVjayB7CiAgICAgICAgICAgIGNvbG9yOiBncmVlbjsKICAgICAgICB9CgogICAgICAgIC5mYS1leGNsYW1hdGlvbi1jaXJjbGUgewogICAgICAgICAgICBjb2xvcjogcmVkOwogICAgICAgIH0KICAgIDwvc3R5bGU+CjwvaGVhZD4KPGJvZHkgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICMzNDNhNDAxYSI+CjxkaXYgaWQ9ImFwcCI+CiAgICA8bmF2IGNsYXNzPSJuYXZiYXIgbmF2YmFyLWV4cGFuZC1sZyBuYXZiYXItZGFyayBiZy1kYXJrIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIiPgogICAgICAgICAgICA8c3BhbiBjbGFzcz0ibmF2YmFyLWJyYW5kIG1iLTAgaDEiPjxpIGNsYXNzPSJmYXMgZmEtdGVybWluYWwiPjwvaT4gJm5ic3A7IE1pbnRlciBGdWxsIE5vZGUgU3RhdHVzPC9zcGFuPgogICAgICAgIDwvZGl2PgogICAgPC9uYXY+CiAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIgYm9keSIgdi1pZj0iZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWRhbmdlciIgcm9sZT0iYWxlcnQiPgogICAgICAgICAgICA8aDQgY2xhc3M9ImFsZXJ0LWhlYWRpbmciPkVycm9yIHdoaWxlIGNvbm5lY3RpbmcgdG8gbG9jYWwgbm9kZTwvaDQ+CiAgICAgICAgICAgIDxwIGNsYXNzPSJtYi0wIj57eyBlcnJvciB9fTwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0iY29udGFpbmVyIGJvZHkgYmctd2hpdGUiIHYtaWY9InN0YXR1cyAmJiAhZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9InJvdyI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIE5vZGUgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk1vbmlrZXI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubW9uaWtlciB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Tm9kZSBJRDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby5pZCB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TGlzdGVuIEFkZHI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubGlzdGVuX2FkZHIgfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk5ldHdvcmsgSUQ8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubmV0d29yayB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TWludGVyIFZlcnNpb248L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZlcnNpb24gfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPlRlbmRlcm1pbnQgVmVyc2lvbjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby52ZXJzaW9uIH19PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIiB2LWlmPSJuZXRfaW5mbyI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZC1oZWFkZXIiPgogICAgICAgICAgICAgICAgICAgICAgICBOZXQgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIExpc3RlbmluZzwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PGkgOmNsYXNzPSJ7J2ZhLWNoZWNrJzogbmV0X2luZm8ubGlzdGVuaW5nfSIgY2xhc3M9ImZhcyI+PC9pPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Q29ubmVjdGVkIFBlZXJzPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD57eyBuZXRfaW5mby5uX3BlZXJzIH19IDxpIDpjbGFzcz0ieydmYS1leGNsYW1hdGlvbi1jaXJjbGUnOiBuZXRfaW5mby5uX3BlZXJzIDwgMX0iIGNsYXNzPSJmYXMiPjwvaT48L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIFN5bmNpbmcgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIFN5bmNlZDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0ic3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCI+Tm88L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0iIXN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXAiPlllczwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aSA6Y2xhc3M9InsnZmEtY2hlY2snOiAhc3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCwgJ2ZhLWV4Y2xhbWF0aW9uLWNpcmNsZSc6IHN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXB9IiBjbGFzcz0iZmFzIj48L2k+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2sgSGVpZ2h0PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAje3sgc3RhdHVzLnN5bmNfaW5mby5sYXRlc3RfYmxvY2tfaGVpZ2h0IH19IDxzcGFuIHYtaWY9Im1hc3RlclN0YXR1cyIgY2xhc3M9InRleHQtbXV0ZWQiPm9mIHt7IG1hc3RlclN0YXR1cy5sYXRlc3RfYmxvY2tfaGVpZ2h0IH19PC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2sgVGltZTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7eyBzdGF0dXMuc3luY19pbmZvLmxhdGVzdF9ibG9ja190aW1lIH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQtaGVhZGVyIj4KICAgICAgICAgICAgICAgICAgICAgICAgVmFsaWRhdG9yIEluZm8KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8dGFibGUgY2xhc3M9InRhYmxlIGNhcmQtYm9keSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPlB1YmxpYyBLZXk8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZhbGlkYXRvclB1YktleSB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD5TdGF0dXM8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZhbGlkYXRvclN0YXR1cyB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD5Ub3RhbCBTdGFrZTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3Rha2UgfX0gTU5UPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPlZvdGluZyBQb3dlcjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgbmljZU51bShzdGF0dXMudmFsaWRhdG9yX2luZm8udm90aW5nX3Bvd2VyKSB9fSA8c3BhbiBjbGFzcz0idGV4dC1tdXRlZCI+b2YgMTAwLDAwMCwwMDA8L3NwYW4+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgogICAgPC9kaXY+CjwvZGl2Pgo8c2NyaXB0PgogICAgbmV3IFZ1ZSh7CiAgICAgICAgZWw6ICcjYXBwJywKICAgICAgICBkYXRhOiB7CiAgICAgICAgICAgIG1hc3RlclN0YXR1czogbnVsbCwKICAgICAgICAgICAgc3RhdHVzOiBudWxsLAogICAgICAgICAgICB2ZXJzaW9uOiBudWxsLAogICAgICAgICAgICBuZXRfaW5mbzogbnVsbCwKICAgICAgICAgICAgZXJyb3I6IG51bGwsCiAgICAgICAgICAgIHZhbGlkYXRvclB1YktleTogJy4uLicsCiAgICAgICAgICAgIHZhbGlkYXRvclN0YXR1czogJy4uLicsCiAgICAgICAgICAgIHN0YWtlOiAnLi4uJwogICAgICAgIH0sCiAgICAgICAgbW91bnRlZCgpIHsKICAgICAgICAgICAgdGhpcy5yZWZyZXNoKCkKICAgICAgICB9LAogICAgICAgIG1ldGhvZHM6IHsKICAgICAgICAgICAgbmljZU51bShudW0pIHsKICAgICAgICAgICAgICAgIHJldHVybiBudW0udG9TdHJpbmcoKS5yZXBsYWNlKC9cQig/PShcZHszfSkrKD8hXGQpKS9nLCAiLCIpCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIGJhc2U2NFRvSGV4KGJhc2U2NCkgewogICAgICAgICAgICAgICAgcmV0dXJuIENyeXB0b0pTLmVuYy5CYXNlNjQucGFyc2UoYmFzZTY0KS50b1N0cmluZygpCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHJlZnJlc2goKSB7CiAgICAgICAgICAgICAgICBheGlvcy5hbGwoWwogICAgICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiLy8iICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgJzo4ODQxL2FwaS9zdGF0dXMnKSwKICAgICAgICAgICAgICAgICAgICBheGlvcy5nZXQoIi8vIiArIHdpbmRvdy5sb2NhdGlvbi5ob3N0bmFtZSArICc6ODg0MS9hcGkvbmV0X2luZm8nKSwKICAgICAgICAgICAgICAgIF0pLnRoZW4oYXhpb3Muc3ByZWFkKGZ1bmN0aW9uIChzdGF0dXMsIG5ldF9pbmZvKSB7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5lcnJvciA9IG51bGwKCiAgICAgICAgICAgICAgICAgICAgdGhpcy5zdGF0dXMgPSBzdGF0dXMuZGF0YS5yZXN1bHQudG1fc3RhdHVzCiAgICAgICAgICAgICAgICAgICAgdGhpcy52ZXJzaW9uID0gc3RhdHVzLmRhdGEucmVzdWx0LnZlcnNpb24KICAgICAgICAgICAgICAgICAgICB0aGlzLm5ldF9pbmZvID0gbmV0X2luZm8uZGF0YS5yZXN1bHQKCiAgICAgICAgICAgICAgICAgICAgdGhpcy52YWxpZGF0b3JQdWJLZXkgPSAnTXAnICsgdGhpcy5iYXNlNjRUb0hleChzdGF0dXMuZGF0YS5yZXN1bHQudG1fc3RhdHVzLnZhbGlkYXRvcl9pbmZvLnB1Yl9rZXkudmFsdWUpCgogICAgICAgICAgICAgICAgICAgIGF4aW9zLmFsbChbCiAgICAgICAgICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiLy8iICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgJzo4ODQxL2FwaS92YWxpZGF0b3JzJyksCiAgICAgICAgICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiLy8iICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgJzo4ODQxL2FwaS9jYW5kaWRhdGUvJyArIHRoaXMudmFsaWRhdG9yUHViS2V5KSwKICAgICAgICAgICAgICAgICAgICBdKS50aGVuKGF4aW9zLnNwcmVhZChmdW5jdGlvbiAodmFsaWRhdG9ycywgY2FuZGlkYXRlKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuc3Rha2UgPSBNYXRoLnJvdW5kKGNhbmRpZGF0ZS5kYXRhLnJlc3VsdC5jYW5kaWRhdGUudG90YWxfc3Rha2UgLyBNYXRoLnBvdygxMCwgMTcpKSAvIDEwCiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChjYW5kaWRhdGUuZGF0YS5jb2RlICE9PSAwKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnZhbGlkYXRvclN0YXR1cyA9ICdOb3QgZGVjbGFyZWQnOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgICAgIGlmICh2YWxpZGF0b3JzLmRhdGEucmVzdWx0LmZpbmQoZnVuY3Rpb24odmFsKSB7IHJldHVybiB2YWwuY2FuZGlkYXRlLnB1Yl9rZXkgPT09IHRoaXMudmFsaWRhdG9yUHViS2V5IH0uYmluZCh0aGlzKSkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudmFsaWRhdG9yU3RhdHVzID0gJ1ZhbGlkYXRpbmcnOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChjYW5kaWRhdGUuZGF0YS5yZXN1bHQuc3RhdHVzID09PSAyKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnZhbGlkYXRvclN0YXR1cyA9ICdDYW5kaWRhdGUnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4KICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy52YWxpZGF0b3JTdGF0dXMgPSAnRG93bic7CiAgICAgICAgICAgICAgICAgICAgfS5iaW5kKHRoaXMpKSk7CgogICAgICAgICAgICAgICAgICAgIHNldFRpbWVvdXQodGhpcy5yZWZyZXNoLCAxMDAwKQoKCiAgICAgICAgICAgICAgICB9LmJpbmQodGhpcykpKS5jYXRjaChmdW5jdGlvbiAocmVhc29uKSB7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5lcnJvciA9IHJlYXNvbi50b1N0cmluZygpOwogICAgICAgICAgICAgICAgICAgIHNldFRpbWVvdXQodGhpcy5yZWZyZXNoLCAxMDAwKQogICAgICAgICAgICAgICAgfS5iaW5kKHRoaXMpKQoKICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiaHR0cHM6Ly9taW50ZXItbm9kZS0xLnRlc3RuZXQubWludGVyLm5ldHdvcmsvYXBpL3N0YXR1cyIpLnRoZW4oZnVuY3Rpb24gKG1hc3RlclN0YXR1cykgewogICAgICAgICAgICAgICAgICAgIHRoaXMubWFzdGVyU3RhdHVzID0gbWFzdGVyU3RhdHVzLmRhdGEucmVzdWx0CiAgICAgICAgICAgICAgICB9LmJpbmQodGhpcykpCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICB9KQo8L3NjcmlwdD4KPC9ib2R5Pgo8L2h0bWw+\"") } diff --git a/gui/html/index.html b/gui/html/index.html index 773d5caf6..ad7d7cd13 100644 --- a/gui/html/index.html +++ b/gui/html/index.html @@ -175,6 +175,10 @@

Error while connecting to local node

Status {{ validatorStatus }} + + Total Stake + {{ stake }} MNT + Voting Power {{ niceNum(status.validator_info.voting_power) }} of 100,000,000 @@ -196,7 +200,8 @@

Error while connecting to local node

net_info: null, error: null, validatorPubKey: '...', - validatorStatus: '...' + validatorStatus: '...', + stake: '...' }, mounted() { this.refresh() @@ -225,6 +230,7 @@

Error while connecting to local node

axios.get("//" + window.location.hostname + ':8841/api/validators'), axios.get("//" + window.location.hostname + ':8841/api/candidate/' + this.validatorPubKey), ]).then(axios.spread(function (validators, candidate) { + this.stake = Math.round(candidate.data.result.candidate.total_stake / Math.pow(10, 17)) / 10 if (candidate.data.code !== 0) { this.validatorStatus = 'Not declared'; return @@ -243,12 +249,12 @@

Error while connecting to local node

this.validatorStatus = 'Down'; }.bind(this))); - setTimeout(this.refresh, 500) + setTimeout(this.refresh, 1000) }.bind(this))).catch(function (reason) { this.error = reason.toString(); - setTimeout(this.refresh, 500) + setTimeout(this.refresh, 1000) }.bind(this)) axios.get("https://minter-node-1.testnet.minter.network/api/status").then(function (masterStatus) { From 5ec1fbacf567a15b3d51f89ab0446267f27a208d Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 17:33:54 +0300 Subject: [PATCH 20/29] fix --- gui/gui-packr.go | 2 +- gui/html/index.html | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/gui/gui-packr.go b/gui/gui-packr.go index 0ab93fc5f..d60af3ab3 100644 --- a/gui/gui-packr.go +++ b/gui/gui-packr.go @@ -7,5 +7,5 @@ import "github.com/gobuffalo/packr" // You can use the "packr clean" command to clean up this, // and any other packr generated files. func init() { - packr.PackJSONBytes("./html", "index.html", "\"PGh0bWw+CjxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiCiAgICAgICAgICBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIHVzZXItc2NhbGFibGU9bm8sIGluaXRpYWwtc2NhbGU9MS4wLCBtYXhpbXVtLXNjYWxlPTEuMCwgbWluaW11bS1zY2FsZT0xLjAiPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJpZT1lZGdlIj4KICAgIDx0aXRsZT5NaW50ZXIgTm9kZSBHVUk8L3RpdGxlPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL3N0YWNrcGF0aC5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC80LjEuMC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiCiAgICAgICAgICBpbnRlZ3JpdHk9InNoYTM4NC05Z1ZRNGRZRnd3V1NqSURabkxFV254Q2plU1dGcGhKaXdHUFhyMWpkZEloT2VnaXUxRndPNXFSR3ZGWE9kSlo0IiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly91c2UuZm9udGF3ZXNvbWUuY29tL3JlbGVhc2VzL3Y1LjEuMS9jc3MvYWxsLmNzcyIgaW50ZWdyaXR5PSJzaGEzODQtTzh3aFMzZmhHMk9uQTVLYXMwWTlsM2NmcG1ZamFwakkwRTR0aGVINGl1TUQrcExoYmY2SkkwaklNZlljSzN5WiIgY3Jvc3NvcmlnaW49ImFub255bW91cyI+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS92dWUiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2F4aW9zLzAuMTguMC9heGlvcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2NyeXB0by1qcy8zLjEuOS0xL2NyeXB0by1qcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHN0eWxlPgoKICAgICAgICAuY2FyZCB7CiAgICAgICAgICAgIG1hcmdpbi1ib3R0b206IDIwcHg7CiAgICAgICAgfQoKICAgICAgICBodG1sLGJvZHksLmJvZHksI2FwcCB7CiAgICAgICAgICAgIG1pbi1oZWlnaHQ6IDEwMCU7CiAgICAgICAgfQogICAgICAgIAogICAgICAgIC5ib2R5IHsKICAgICAgICAgICAgcGFkZGluZy10b3A6IDE1cHg7CiAgICAgICAgfQoKICAgICAgICAudGFibGUgewogICAgICAgICAgICBtYXJnaW4tYm90dG9tOiAwOwogICAgICAgICAgICB0YWJsZS1sYXlvdXQ6IGZpeGVkOwogICAgICAgIH0KCiAgICAgICAgLmNhcmQtaGVhZGVyIHsKICAgICAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICAgICAgfQoKICAgICAgICAuY2FyZC1oZWFkZXIgewogICAgICAgICAgICBwYWRkaW5nLWxlZnQ6IDEycHg7CiAgICAgICAgfQoKICAgICAgICAuaCB7CiAgICAgICAgICAgIHdpZHRoOiAyMDBweDsKICAgICAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2YzZjNmMzsKICAgICAgICAgICAgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2NjYzsKICAgICAgICB9CgogICAgICAgIC5iZy1zdWNjZXNzLCAuYmctZGFuZ2VyIHsKICAgICAgICAgICAgY29sb3I6IHdoaXRlOwogICAgICAgIH0KCiAgICAgICAgLmJnLWRhbmdlciB7CiAgICAgICAgICAgIGJvcmRlci1jb2xvcjogI2RjMzU0NSAhaW1wb3J0YW50OwogICAgICAgIH0KCiAgICAgICAgLmJnLXN1Y2Nlc3MgewogICAgICAgICAgICBib3JkZXItY29sb3I6ICMyOGE3NDUgIWltcG9ydGFudDsKICAgICAgICB9CgogICAgICAgIC5mYS1jaGVjayB7CiAgICAgICAgICAgIGNvbG9yOiBncmVlbjsKICAgICAgICB9CgogICAgICAgIC5mYS1leGNsYW1hdGlvbi1jaXJjbGUgewogICAgICAgICAgICBjb2xvcjogcmVkOwogICAgICAgIH0KICAgIDwvc3R5bGU+CjwvaGVhZD4KPGJvZHkgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICMzNDNhNDAxYSI+CjxkaXYgaWQ9ImFwcCI+CiAgICA8bmF2IGNsYXNzPSJuYXZiYXIgbmF2YmFyLWV4cGFuZC1sZyBuYXZiYXItZGFyayBiZy1kYXJrIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIiPgogICAgICAgICAgICA8c3BhbiBjbGFzcz0ibmF2YmFyLWJyYW5kIG1iLTAgaDEiPjxpIGNsYXNzPSJmYXMgZmEtdGVybWluYWwiPjwvaT4gJm5ic3A7IE1pbnRlciBGdWxsIE5vZGUgU3RhdHVzPC9zcGFuPgogICAgICAgIDwvZGl2PgogICAgPC9uYXY+CiAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIgYm9keSIgdi1pZj0iZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWRhbmdlciIgcm9sZT0iYWxlcnQiPgogICAgICAgICAgICA8aDQgY2xhc3M9ImFsZXJ0LWhlYWRpbmciPkVycm9yIHdoaWxlIGNvbm5lY3RpbmcgdG8gbG9jYWwgbm9kZTwvaDQ+CiAgICAgICAgICAgIDxwIGNsYXNzPSJtYi0wIj57eyBlcnJvciB9fTwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0iY29udGFpbmVyIGJvZHkgYmctd2hpdGUiIHYtaWY9InN0YXR1cyAmJiAhZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9InJvdyI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIE5vZGUgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk1vbmlrZXI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubW9uaWtlciB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Tm9kZSBJRDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby5pZCB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TGlzdGVuIEFkZHI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubGlzdGVuX2FkZHIgfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk5ldHdvcmsgSUQ8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubmV0d29yayB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TWludGVyIFZlcnNpb248L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZlcnNpb24gfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPlRlbmRlcm1pbnQgVmVyc2lvbjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby52ZXJzaW9uIH19PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIiB2LWlmPSJuZXRfaW5mbyI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZC1oZWFkZXIiPgogICAgICAgICAgICAgICAgICAgICAgICBOZXQgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIExpc3RlbmluZzwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PGkgOmNsYXNzPSJ7J2ZhLWNoZWNrJzogbmV0X2luZm8ubGlzdGVuaW5nfSIgY2xhc3M9ImZhcyI+PC9pPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Q29ubmVjdGVkIFBlZXJzPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD57eyBuZXRfaW5mby5uX3BlZXJzIH19IDxpIDpjbGFzcz0ieydmYS1leGNsYW1hdGlvbi1jaXJjbGUnOiBuZXRfaW5mby5uX3BlZXJzIDwgMX0iIGNsYXNzPSJmYXMiPjwvaT48L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIFN5bmNpbmcgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIFN5bmNlZDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0ic3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCI+Tm88L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0iIXN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXAiPlllczwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aSA6Y2xhc3M9InsnZmEtY2hlY2snOiAhc3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCwgJ2ZhLWV4Y2xhbWF0aW9uLWNpcmNsZSc6IHN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXB9IiBjbGFzcz0iZmFzIj48L2k+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2sgSGVpZ2h0PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAje3sgc3RhdHVzLnN5bmNfaW5mby5sYXRlc3RfYmxvY2tfaGVpZ2h0IH19IDxzcGFuIHYtaWY9Im1hc3RlclN0YXR1cyIgY2xhc3M9InRleHQtbXV0ZWQiPm9mIHt7IG1hc3RlclN0YXR1cy5sYXRlc3RfYmxvY2tfaGVpZ2h0IH19PC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2sgVGltZTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7eyBzdGF0dXMuc3luY19pbmZvLmxhdGVzdF9ibG9ja190aW1lIH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQtaGVhZGVyIj4KICAgICAgICAgICAgICAgICAgICAgICAgVmFsaWRhdG9yIEluZm8KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8dGFibGUgY2xhc3M9InRhYmxlIGNhcmQtYm9keSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPlB1YmxpYyBLZXk8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZhbGlkYXRvclB1YktleSB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD5TdGF0dXM8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZhbGlkYXRvclN0YXR1cyB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD5Ub3RhbCBTdGFrZTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3Rha2UgfX0gTU5UPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPlZvdGluZyBQb3dlcjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgbmljZU51bShzdGF0dXMudmFsaWRhdG9yX2luZm8udm90aW5nX3Bvd2VyKSB9fSA8c3BhbiBjbGFzcz0idGV4dC1tdXRlZCI+b2YgMTAwLDAwMCwwMDA8L3NwYW4+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgogICAgPC9kaXY+CjwvZGl2Pgo8c2NyaXB0PgogICAgbmV3IFZ1ZSh7CiAgICAgICAgZWw6ICcjYXBwJywKICAgICAgICBkYXRhOiB7CiAgICAgICAgICAgIG1hc3RlclN0YXR1czogbnVsbCwKICAgICAgICAgICAgc3RhdHVzOiBudWxsLAogICAgICAgICAgICB2ZXJzaW9uOiBudWxsLAogICAgICAgICAgICBuZXRfaW5mbzogbnVsbCwKICAgICAgICAgICAgZXJyb3I6IG51bGwsCiAgICAgICAgICAgIHZhbGlkYXRvclB1YktleTogJy4uLicsCiAgICAgICAgICAgIHZhbGlkYXRvclN0YXR1czogJy4uLicsCiAgICAgICAgICAgIHN0YWtlOiAnLi4uJwogICAgICAgIH0sCiAgICAgICAgbW91bnRlZCgpIHsKICAgICAgICAgICAgdGhpcy5yZWZyZXNoKCkKICAgICAgICB9LAogICAgICAgIG1ldGhvZHM6IHsKICAgICAgICAgICAgbmljZU51bShudW0pIHsKICAgICAgICAgICAgICAgIHJldHVybiBudW0udG9TdHJpbmcoKS5yZXBsYWNlKC9cQig/PShcZHszfSkrKD8hXGQpKS9nLCAiLCIpCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIGJhc2U2NFRvSGV4KGJhc2U2NCkgewogICAgICAgICAgICAgICAgcmV0dXJuIENyeXB0b0pTLmVuYy5CYXNlNjQucGFyc2UoYmFzZTY0KS50b1N0cmluZygpCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHJlZnJlc2goKSB7CiAgICAgICAgICAgICAgICBheGlvcy5hbGwoWwogICAgICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiLy8iICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgJzo4ODQxL2FwaS9zdGF0dXMnKSwKICAgICAgICAgICAgICAgICAgICBheGlvcy5nZXQoIi8vIiArIHdpbmRvdy5sb2NhdGlvbi5ob3N0bmFtZSArICc6ODg0MS9hcGkvbmV0X2luZm8nKSwKICAgICAgICAgICAgICAgIF0pLnRoZW4oYXhpb3Muc3ByZWFkKGZ1bmN0aW9uIChzdGF0dXMsIG5ldF9pbmZvKSB7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5lcnJvciA9IG51bGwKCiAgICAgICAgICAgICAgICAgICAgdGhpcy5zdGF0dXMgPSBzdGF0dXMuZGF0YS5yZXN1bHQudG1fc3RhdHVzCiAgICAgICAgICAgICAgICAgICAgdGhpcy52ZXJzaW9uID0gc3RhdHVzLmRhdGEucmVzdWx0LnZlcnNpb24KICAgICAgICAgICAgICAgICAgICB0aGlzLm5ldF9pbmZvID0gbmV0X2luZm8uZGF0YS5yZXN1bHQKCiAgICAgICAgICAgICAgICAgICAgdGhpcy52YWxpZGF0b3JQdWJLZXkgPSAnTXAnICsgdGhpcy5iYXNlNjRUb0hleChzdGF0dXMuZGF0YS5yZXN1bHQudG1fc3RhdHVzLnZhbGlkYXRvcl9pbmZvLnB1Yl9rZXkudmFsdWUpCgogICAgICAgICAgICAgICAgICAgIGF4aW9zLmFsbChbCiAgICAgICAgICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiLy8iICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgJzo4ODQxL2FwaS92YWxpZGF0b3JzJyksCiAgICAgICAgICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiLy8iICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgJzo4ODQxL2FwaS9jYW5kaWRhdGUvJyArIHRoaXMudmFsaWRhdG9yUHViS2V5KSwKICAgICAgICAgICAgICAgICAgICBdKS50aGVuKGF4aW9zLnNwcmVhZChmdW5jdGlvbiAodmFsaWRhdG9ycywgY2FuZGlkYXRlKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuc3Rha2UgPSBNYXRoLnJvdW5kKGNhbmRpZGF0ZS5kYXRhLnJlc3VsdC5jYW5kaWRhdGUudG90YWxfc3Rha2UgLyBNYXRoLnBvdygxMCwgMTcpKSAvIDEwCiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChjYW5kaWRhdGUuZGF0YS5jb2RlICE9PSAwKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnZhbGlkYXRvclN0YXR1cyA9ICdOb3QgZGVjbGFyZWQnOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgICAgIGlmICh2YWxpZGF0b3JzLmRhdGEucmVzdWx0LmZpbmQoZnVuY3Rpb24odmFsKSB7IHJldHVybiB2YWwuY2FuZGlkYXRlLnB1Yl9rZXkgPT09IHRoaXMudmFsaWRhdG9yUHViS2V5IH0uYmluZCh0aGlzKSkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudmFsaWRhdG9yU3RhdHVzID0gJ1ZhbGlkYXRpbmcnOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChjYW5kaWRhdGUuZGF0YS5yZXN1bHQuc3RhdHVzID09PSAyKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnZhbGlkYXRvclN0YXR1cyA9ICdDYW5kaWRhdGUnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4KICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy52YWxpZGF0b3JTdGF0dXMgPSAnRG93bic7CiAgICAgICAgICAgICAgICAgICAgfS5iaW5kKHRoaXMpKSk7CgogICAgICAgICAgICAgICAgICAgIHNldFRpbWVvdXQodGhpcy5yZWZyZXNoLCAxMDAwKQoKCiAgICAgICAgICAgICAgICB9LmJpbmQodGhpcykpKS5jYXRjaChmdW5jdGlvbiAocmVhc29uKSB7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5lcnJvciA9IHJlYXNvbi50b1N0cmluZygpOwogICAgICAgICAgICAgICAgICAgIHNldFRpbWVvdXQodGhpcy5yZWZyZXNoLCAxMDAwKQogICAgICAgICAgICAgICAgfS5iaW5kKHRoaXMpKQoKICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiaHR0cHM6Ly9taW50ZXItbm9kZS0xLnRlc3RuZXQubWludGVyLm5ldHdvcmsvYXBpL3N0YXR1cyIpLnRoZW4oZnVuY3Rpb24gKG1hc3RlclN0YXR1cykgewogICAgICAgICAgICAgICAgICAgIHRoaXMubWFzdGVyU3RhdHVzID0gbWFzdGVyU3RhdHVzLmRhdGEucmVzdWx0CiAgICAgICAgICAgICAgICB9LmJpbmQodGhpcykpCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICB9KQo8L3NjcmlwdD4KPC9ib2R5Pgo8L2h0bWw+\"") + packr.PackJSONBytes("./html", "index.html", "\"PGh0bWw+CjxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiCiAgICAgICAgICBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIHVzZXItc2NhbGFibGU9bm8sIGluaXRpYWwtc2NhbGU9MS4wLCBtYXhpbXVtLXNjYWxlPTEuMCwgbWluaW11bS1zY2FsZT0xLjAiPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJpZT1lZGdlIj4KICAgIDx0aXRsZT5NaW50ZXIgTm9kZSBHVUk8L3RpdGxlPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL3N0YWNrcGF0aC5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC80LjEuMC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiCiAgICAgICAgICBpbnRlZ3JpdHk9InNoYTM4NC05Z1ZRNGRZRnd3V1NqSURabkxFV254Q2plU1dGcGhKaXdHUFhyMWpkZEloT2VnaXUxRndPNXFSR3ZGWE9kSlo0IiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly91c2UuZm9udGF3ZXNvbWUuY29tL3JlbGVhc2VzL3Y1LjEuMS9jc3MvYWxsLmNzcyIgaW50ZWdyaXR5PSJzaGEzODQtTzh3aFMzZmhHMk9uQTVLYXMwWTlsM2NmcG1ZamFwakkwRTR0aGVINGl1TUQrcExoYmY2SkkwaklNZlljSzN5WiIgY3Jvc3NvcmlnaW49ImFub255bW91cyI+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS92dWUiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2F4aW9zLzAuMTguMC9heGlvcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2NyeXB0by1qcy8zLjEuOS0xL2NyeXB0by1qcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHN0eWxlPgoKICAgICAgICAuY2FyZCB7CiAgICAgICAgICAgIG1hcmdpbi1ib3R0b206IDIwcHg7CiAgICAgICAgfQoKICAgICAgICBodG1sLGJvZHksLmJvZHksI2FwcCB7CiAgICAgICAgICAgIG1pbi1oZWlnaHQ6IDEwMCU7CiAgICAgICAgfQogICAgICAgIAogICAgICAgIC5ib2R5IHsKICAgICAgICAgICAgcGFkZGluZy10b3A6IDE1cHg7CiAgICAgICAgfQoKICAgICAgICAudGFibGUgewogICAgICAgICAgICBtYXJnaW4tYm90dG9tOiAwOwogICAgICAgICAgICB0YWJsZS1sYXlvdXQ6IGZpeGVkOwogICAgICAgIH0KCiAgICAgICAgLmNhcmQtaGVhZGVyIHsKICAgICAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICAgICAgfQoKICAgICAgICAuY2FyZC1oZWFkZXIgewogICAgICAgICAgICBwYWRkaW5nLWxlZnQ6IDEycHg7CiAgICAgICAgfQoKICAgICAgICAuaCB7CiAgICAgICAgICAgIHdpZHRoOiAyMDBweDsKICAgICAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2YzZjNmMzsKICAgICAgICAgICAgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2NjYzsKICAgICAgICB9CgogICAgICAgIC5iZy1zdWNjZXNzLCAuYmctZGFuZ2VyIHsKICAgICAgICAgICAgY29sb3I6IHdoaXRlOwogICAgICAgIH0KCiAgICAgICAgLmJnLWRhbmdlciB7CiAgICAgICAgICAgIGJvcmRlci1jb2xvcjogI2RjMzU0NSAhaW1wb3J0YW50OwogICAgICAgIH0KCiAgICAgICAgLmJnLXN1Y2Nlc3MgewogICAgICAgICAgICBib3JkZXItY29sb3I6ICMyOGE3NDUgIWltcG9ydGFudDsKICAgICAgICB9CgogICAgICAgIC5mYS1jaGVjayB7CiAgICAgICAgICAgIGNvbG9yOiBncmVlbjsKICAgICAgICB9CgogICAgICAgIC5mYS1leGNsYW1hdGlvbi1jaXJjbGUgewogICAgICAgICAgICBjb2xvcjogcmVkOwogICAgICAgIH0KICAgIDwvc3R5bGU+CjwvaGVhZD4KPGJvZHkgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICMzNDNhNDAxYSI+CjxkaXYgaWQ9ImFwcCI+CiAgICA8bmF2IGNsYXNzPSJuYXZiYXIgbmF2YmFyLWV4cGFuZC1sZyBuYXZiYXItZGFyayBiZy1kYXJrIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIiPgogICAgICAgICAgICA8c3BhbiBjbGFzcz0ibmF2YmFyLWJyYW5kIG1iLTAgaDEiPjxpIGNsYXNzPSJmYXMgZmEtdGVybWluYWwiPjwvaT4gJm5ic3A7IE1pbnRlciBGdWxsIE5vZGUgU3RhdHVzPC9zcGFuPgogICAgICAgIDwvZGl2PgogICAgPC9uYXY+CiAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIgYm9keSIgdi1pZj0iZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWRhbmdlciIgcm9sZT0iYWxlcnQiPgogICAgICAgICAgICA8aDQgY2xhc3M9ImFsZXJ0LWhlYWRpbmciPkVycm9yIHdoaWxlIGNvbm5lY3RpbmcgdG8gbG9jYWwgbm9kZTwvaDQ+CiAgICAgICAgICAgIDxwIGNsYXNzPSJtYi0wIj57eyBlcnJvciB9fTwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0iY29udGFpbmVyIGJvZHkgYmctd2hpdGUiIHYtaWY9InN0YXR1cyAmJiAhZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9InJvdyI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIE5vZGUgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk1vbmlrZXI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubW9uaWtlciB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Tm9kZSBJRDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby5pZCB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TGlzdGVuIEFkZHI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubGlzdGVuX2FkZHIgfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk5ldHdvcmsgSUQ8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubmV0d29yayB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TWludGVyIFZlcnNpb248L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZlcnNpb24gfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPlRlbmRlcm1pbnQgVmVyc2lvbjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby52ZXJzaW9uIH19PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIiB2LWlmPSJuZXRfaW5mbyI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZC1oZWFkZXIiPgogICAgICAgICAgICAgICAgICAgICAgICBOZXQgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIExpc3RlbmluZzwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PGkgOmNsYXNzPSJ7J2ZhLWNoZWNrJzogbmV0X2luZm8ubGlzdGVuaW5nfSIgY2xhc3M9ImZhcyI+PC9pPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Q29ubmVjdGVkIFBlZXJzPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD57eyBuZXRfaW5mby5uX3BlZXJzIH19IDxpIDpjbGFzcz0ieydmYS1leGNsYW1hdGlvbi1jaXJjbGUnOiBuZXRfaW5mby5uX3BlZXJzIDwgMX0iIGNsYXNzPSJmYXMiPjwvaT48L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIFN5bmNpbmcgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIFN5bmNlZDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0ic3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCI+Tm88L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0iIXN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXAiPlllczwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aSA6Y2xhc3M9InsnZmEtY2hlY2snOiAhc3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCwgJ2ZhLWV4Y2xhbWF0aW9uLWNpcmNsZSc6IHN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXB9IiBjbGFzcz0iZmFzIj48L2k+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2sgSGVpZ2h0PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAje3sgc3RhdHVzLnN5bmNfaW5mby5sYXRlc3RfYmxvY2tfaGVpZ2h0IH19IDxzcGFuIHYtaWY9Im1hc3RlclN0YXR1cyIgY2xhc3M9InRleHQtbXV0ZWQiPm9mIHt7IG1hc3RlclN0YXR1cy5sYXRlc3RfYmxvY2tfaGVpZ2h0IH19PC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2sgVGltZTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7eyBzdGF0dXMuc3luY19pbmZvLmxhdGVzdF9ibG9ja190aW1lIH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQtaGVhZGVyIj4KICAgICAgICAgICAgICAgICAgICAgICAgVmFsaWRhdG9yIEluZm8KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8dGFibGUgY2xhc3M9InRhYmxlIGNhcmQtYm9keSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPlB1YmxpYyBLZXk8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZhbGlkYXRvclB1YktleSB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD5TdGF0dXM8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZhbGlkYXRvclN0YXR1cyB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD5Ub3RhbCBTdGFrZTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3Rha2UgfX0gTU5UPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPlZvdGluZyBQb3dlcjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgbmljZU51bShzdGF0dXMudmFsaWRhdG9yX2luZm8udm90aW5nX3Bvd2VyKSB9fSA8c3BhbiBjbGFzcz0idGV4dC1tdXRlZCI+b2YgMTAwLDAwMCwwMDA8L3NwYW4+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgogICAgPC9kaXY+CjwvZGl2Pgo8c2NyaXB0PgogICAgbmV3IFZ1ZSh7CiAgICAgICAgZWw6ICcjYXBwJywKICAgICAgICBkYXRhOiB7CiAgICAgICAgICAgIG1hc3RlclN0YXR1czogbnVsbCwKICAgICAgICAgICAgc3RhdHVzOiBudWxsLAogICAgICAgICAgICB2ZXJzaW9uOiBudWxsLAogICAgICAgICAgICBuZXRfaW5mbzogbnVsbCwKICAgICAgICAgICAgZXJyb3I6IG51bGwsCiAgICAgICAgICAgIHZhbGlkYXRvclB1YktleTogJy4uLicsCiAgICAgICAgICAgIHZhbGlkYXRvclN0YXR1czogJy4uLicsCiAgICAgICAgICAgIHN0YWtlOiAnLi4uJwogICAgICAgIH0sCiAgICAgICAgbW91bnRlZCgpIHsKICAgICAgICAgICAgdGhpcy5yZWZyZXNoKCkKICAgICAgICB9LAogICAgICAgIG1ldGhvZHM6IHsKICAgICAgICAgICAgbmljZU51bShudW0pIHsKICAgICAgICAgICAgICAgIHJldHVybiBudW0udG9TdHJpbmcoKS5yZXBsYWNlKC9cQig/PShcZHszfSkrKD8hXGQpKS9nLCAiLCIpCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIGJhc2U2NFRvSGV4KGJhc2U2NCkgewogICAgICAgICAgICAgICAgcmV0dXJuIENyeXB0b0pTLmVuYy5CYXNlNjQucGFyc2UoYmFzZTY0KS50b1N0cmluZygpCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHJlZnJlc2goKSB7CiAgICAgICAgICAgICAgICBheGlvcy5hbGwoWwogICAgICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiLy8iICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgJzo4ODQxL2FwaS9zdGF0dXMnKSwKICAgICAgICAgICAgICAgICAgICBheGlvcy5nZXQoIi8vIiArIHdpbmRvdy5sb2NhdGlvbi5ob3N0bmFtZSArICc6ODg0MS9hcGkvbmV0X2luZm8nKSwKICAgICAgICAgICAgICAgIF0pLnRoZW4oYXhpb3Muc3ByZWFkKGZ1bmN0aW9uIChzdGF0dXMsIG5ldF9pbmZvKSB7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5lcnJvciA9IG51bGwKCiAgICAgICAgICAgICAgICAgICAgdGhpcy5zdGF0dXMgPSBzdGF0dXMuZGF0YS5yZXN1bHQudG1fc3RhdHVzCiAgICAgICAgICAgICAgICAgICAgdGhpcy52ZXJzaW9uID0gc3RhdHVzLmRhdGEucmVzdWx0LnZlcnNpb24KICAgICAgICAgICAgICAgICAgICB0aGlzLm5ldF9pbmZvID0gbmV0X2luZm8uZGF0YS5yZXN1bHQKCiAgICAgICAgICAgICAgICAgICAgdGhpcy52YWxpZGF0b3JQdWJLZXkgPSAnTXAnICsgdGhpcy5iYXNlNjRUb0hleChzdGF0dXMuZGF0YS5yZXN1bHQudG1fc3RhdHVzLnZhbGlkYXRvcl9pbmZvLnB1Yl9rZXkudmFsdWUpCgogICAgICAgICAgICAgICAgICAgIGF4aW9zLmFsbChbCiAgICAgICAgICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiLy8iICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgJzo4ODQxL2FwaS92YWxpZGF0b3JzJyksCiAgICAgICAgICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiLy8iICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgJzo4ODQxL2FwaS9jYW5kaWRhdGUvJyArIHRoaXMudmFsaWRhdG9yUHViS2V5KSwKICAgICAgICAgICAgICAgICAgICBdKS50aGVuKGF4aW9zLnNwcmVhZChmdW5jdGlvbiAodmFsaWRhdG9ycywgY2FuZGlkYXRlKSB7CgogICAgICAgICAgICAgICAgICAgICAgICBpZiAoY2FuZGlkYXRlLmRhdGEuY29kZSAhPT0gMCkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy52YWxpZGF0b3JTdGF0dXMgPSAnTm90IGRlY2xhcmVkJzsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuc3Rha2UgPSAwOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuc3Rha2UgPSBNYXRoLnJvdW5kKGNhbmRpZGF0ZS5kYXRhLnJlc3VsdC5jYW5kaWRhdGUudG90YWxfc3Rha2UgLyBNYXRoLnBvdygxMCwgMTcpKSAvIDEwCgogICAgICAgICAgICAgICAgICAgICAgICBpZiAodmFsaWRhdG9ycy5kYXRhLnJlc3VsdC5maW5kKGZ1bmN0aW9uKHZhbCkgeyByZXR1cm4gdmFsLmNhbmRpZGF0ZS5wdWJfa2V5ID09PSB0aGlzLnZhbGlkYXRvclB1YktleSB9LmJpbmQodGhpcykpKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnZhbGlkYXRvclN0YXR1cyA9ICdWYWxpZGF0aW5nJzsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybgogICAgICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgICAgICBpZiAoY2FuZGlkYXRlLmRhdGEucmVzdWx0LnN0YXR1cyA9PT0gMikgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy52YWxpZGF0b3JTdGF0dXMgPSAnQ2FuZGlkYXRlJwogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudmFsaWRhdG9yU3RhdHVzID0gJ0Rvd24nOwogICAgICAgICAgICAgICAgICAgIH0uYmluZCh0aGlzKSkpOwoKICAgICAgICAgICAgICAgICAgICBzZXRUaW1lb3V0KHRoaXMucmVmcmVzaCwgMTAwMCkKCgogICAgICAgICAgICAgICAgfS5iaW5kKHRoaXMpKSkuY2F0Y2goZnVuY3Rpb24gKHJlYXNvbikgewogICAgICAgICAgICAgICAgICAgIHRoaXMuZXJyb3IgPSByZWFzb24udG9TdHJpbmcoKTsKICAgICAgICAgICAgICAgICAgICBzZXRUaW1lb3V0KHRoaXMucmVmcmVzaCwgMTAwMCkKICAgICAgICAgICAgICAgIH0uYmluZCh0aGlzKSkKCiAgICAgICAgICAgICAgICBheGlvcy5nZXQoImh0dHBzOi8vbWludGVyLW5vZGUtMS50ZXN0bmV0Lm1pbnRlci5uZXR3b3JrL2FwaS9zdGF0dXMiKS50aGVuKGZ1bmN0aW9uIChtYXN0ZXJTdGF0dXMpIHsKICAgICAgICAgICAgICAgICAgICB0aGlzLm1hc3RlclN0YXR1cyA9IG1hc3RlclN0YXR1cy5kYXRhLnJlc3VsdAogICAgICAgICAgICAgICAgfS5iaW5kKHRoaXMpKQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfSkKPC9zY3JpcHQ+CjwvYm9keT4KPC9odG1sPg==\"") } diff --git a/gui/html/index.html b/gui/html/index.html index ad7d7cd13..84935d30c 100644 --- a/gui/html/index.html +++ b/gui/html/index.html @@ -230,12 +230,15 @@

Error while connecting to local node

axios.get("//" + window.location.hostname + ':8841/api/validators'), axios.get("//" + window.location.hostname + ':8841/api/candidate/' + this.validatorPubKey), ]).then(axios.spread(function (validators, candidate) { - this.stake = Math.round(candidate.data.result.candidate.total_stake / Math.pow(10, 17)) / 10 + if (candidate.data.code !== 0) { this.validatorStatus = 'Not declared'; + this.stake = 0; return } + this.stake = Math.round(candidate.data.result.candidate.total_stake / Math.pow(10, 17)) / 10 + if (validators.data.result.find(function(val) { return val.candidate.pub_key === this.validatorPubKey }.bind(this))) { this.validatorStatus = 'Validating'; return From 1794c74e43010616da54dfa35feb53c8324a201c Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 18:39:48 +0300 Subject: [PATCH 21/29] fix --- core/minter/minter.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/minter/minter.go b/core/minter/minter.go index 5c2c11e5e..afb05c4f9 100644 --- a/core/minter/minter.go +++ b/core/minter/minter.go @@ -312,10 +312,6 @@ func (app *Blockchain) Commit() abciTypes.ResponseCommit { panic(err) } - if err != nil { - panic(err) - } - app.appDB.Set([]byte("root"), hash) // todo: make provider From f22fd82c87960acc3d9799dbd9060a0dcfc0fad3 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 18:47:29 +0300 Subject: [PATCH 22/29] New genesis --- genesis/genesis.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/genesis/genesis.go b/genesis/genesis.go index abb3795bd..a068df5de 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -12,16 +12,15 @@ import ( ) var ( - Network = "minter-test-network-22-local" + Network = "minter-test-network-22" ) func GetTestnetGenesis() (*tmtypes.GenesisDoc, error) { validatorsPubKeys := []string{ - "f60aeE+af6WDOk4Rv1GA9eCQpc8nMtwsFwnkvk34KAk=", - //"SuHuc+YTbIWwypM6mhNHdYozSIXxCzI4OYpnrC6xU7g=", - //"c42kG6ant9abcpSvoVi4nFobQQy/DCRDyFxf4krR3Rw=", - //"bxbB/yGm+5RqrtD0wfzKJyty/ZBJiPkdOIMoK4rjG6I=", - //"nhPy9UaN14KzFkRPvWZZXhPbp9e9Pvob7NULQgRfWMY=", + "SuHuc+YTbIWwypM6mhNHdYozSIXxCzI4OYpnrC6xU7g=", + "c42kG6ant9abcpSvoVi4nFobQQy/DCRDyFxf4krR3Rw=", + "bxbB/yGm+5RqrtD0wfzKJyty/ZBJiPkdOIMoK4rjG6I=", + "nhPy9UaN14KzFkRPvWZZXhPbp9e9Pvob7NULQgRfWMY=", } validators := make([]tmtypes.GenesisValidator, len(validatorsPubKeys)) @@ -32,7 +31,7 @@ func GetTestnetGenesis() (*tmtypes.GenesisDoc, error) { validators[i] = tmtypes.GenesisValidator{ PubKey: validatorPubKey, - Power: 10, + Power: int64(100000000 / len(validatorsPubKeys)), } } @@ -64,7 +63,7 @@ func GetTestnetGenesis() (*tmtypes.GenesisDoc, error) { genesis := tmtypes.GenesisDoc{ ChainID: Network, - GenesisTime: time.Date(2018, 9, 8, 0, 0, 0, 0, time.UTC), + GenesisTime: time.Date(2018, 9, 19, 9, 0, 0, 0, time.UTC), ConsensusParams: nil, Validators: validators, AppHash: appHash[:], From df48eebb797ce4528031cfa7ee846268ea61d989 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 20:25:49 +0300 Subject: [PATCH 23/29] fixes --- core/minter/minter.go | 2 +- core/state/state_object.go | 15 ++++------- core/state/statedb.go | 52 +++++++++++++++++++++++++++++++++----- genesis/genesis.go | 2 +- 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/core/minter/minter.go b/core/minter/minter.go index afb05c4f9..dd942633d 100644 --- a/core/minter/minter.go +++ b/core/minter/minter.go @@ -30,7 +30,7 @@ type Blockchain struct { appDB db.DB stateDeliver *state.StateDB stateCheck *state.StateDB - rootHash [16]byte + rootHash [20]byte height uint64 rewards *big.Int validatorsStatuses map[[20]byte]int8 diff --git a/core/state/state_object.go b/core/state/state_object.go index cf1d655c9..9518f63ee 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -21,7 +21,6 @@ import ( "math/big" "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/rlp" "sort" ) @@ -33,10 +32,8 @@ import ( // Account values can be accessed and modified through the object. // Finally, call CommitTrie to write the modified storage trie into a database. type stateObject struct { - address types.Address - addrHash types.Hash // hash of ethereum address of the account - data Account - db *StateDB + address types.Address + data Account // Cache flags. // When an object is marked suicided it will be delete from the trie @@ -122,11 +119,9 @@ func newObject(db *StateDB, address types.Address, data Account, onDirty func(ad } return &stateObject{ - db: db, - address: address, - addrHash: crypto.Keccak256Hash(address[:]), - data: data, - onDirty: onDirty, + address: address, + data: data, + onDirty: onDirty, } } diff --git a/core/state/statedb.go b/core/state/statedb.go index 67180aaca..90ab242da 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -239,9 +239,7 @@ func (s *StateDB) updateStateFrozenFund(stateFrozenFund *stateFrozenFund) { height := make([]byte, 8) binary.BigEndian.PutUint64(height, stateFrozenFund.blockHeight) - key := append(frozenFundsPrefix, height...) - - s.iavl.Set(key, data) + s.iavl.Set(append(frozenFundsPrefix, height...), data) } func (s *StateDB) updateStateCoin(stateCoin *stateCoin) { @@ -624,7 +622,8 @@ func (s *StateDB) CreateCandidate( func (s *StateDB) Commit(deleteEmptyObjects bool) (root []byte, version int64, err error) { // Commit objects to the trie. - for addr, stateObject := range s.stateObjects { + for _, addr := range getOrderedObjectsKeys(s.stateObjects) { + stateObject := s.stateObjects[addr] _, isDirty := s.stateObjectsDirty[addr] switch { case stateObject.suicided || (isDirty && deleteEmptyObjects && stateObject.empty()): @@ -639,7 +638,8 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root []byte, version int64, e } // Commit coins to the trie. - for symbol, stateCoin := range s.stateCoins { + for _, symbol := range getOrderedCoinsKeys(s.stateCoins) { + stateCoin := s.stateCoins[symbol] _, isDirty := s.stateCoinsDirty[symbol] if isDirty { if stateCoin.data.Volume.Cmp(types.Big0) == 0 { @@ -653,7 +653,8 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root []byte, version int64, e } // Commit frozen funds to the trie. - for block, frozenFund := range s.stateFrozenFunds { + for _, block := range getOrderedFrozenFundsKeys(s.stateFrozenFunds) { + frozenFund := s.stateFrozenFunds[block] _, isDirty := s.stateFrozenFundsDirty[block] switch { case frozenFund.deleted: @@ -688,6 +689,45 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root []byte, version int64, e return hash, version, err } +func getOrderedObjectsKeys(objects map[types.Address]*stateObject) []types.Address { + keys := make([]types.Address, 0, len(objects)) + for k := range objects { + keys = append(keys, k) + } + + sort.Slice(keys, func(i, j int) bool { + return bytes.Compare(keys[i].Bytes(), keys[j].Bytes()) == 1 + }) + + return keys +} + +func getOrderedCoinsKeys(objects map[types.CoinSymbol]*stateCoin) []types.CoinSymbol { + keys := make([]types.CoinSymbol, 0, len(objects)) + for k := range objects { + keys = append(keys, k) + } + + sort.Slice(keys, func(i, j int) bool { + return bytes.Compare(keys[i].Bytes(), keys[j].Bytes()) == 1 + }) + + return keys +} + +func getOrderedFrozenFundsKeys(objects map[uint64]*stateFrozenFund) []uint64 { + keys := make([]uint64, 0, len(objects)) + for k := range objects { + keys = append(keys, k) + } + + sort.Slice(keys, func(i, j int) bool { + return keys[i] > keys[j] + }) + + return keys +} + func (s *StateDB) CoinExists(symbol types.CoinSymbol) bool { if symbol == types.GetBaseCoin() { diff --git a/genesis/genesis.go b/genesis/genesis.go index a068df5de..705f89837 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -35,7 +35,7 @@ func GetTestnetGenesis() (*tmtypes.GenesisDoc, error) { } } - appHash := [16]byte{} + appHash := [20]byte{} appState := AppState{ FirstValidatorAddress: types.HexToAddress("Mxee81347211c72524338f9680072af90744333146"), From 96549b1550f868180a2443def97ea37afe73bef3 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 18 Sep 2018 22:28:20 +0300 Subject: [PATCH 24/29] Refactoring --- core/state/state_frozen_fund.go | 1 + core/state/statedb.go | 40 +++++++++++++-------------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/core/state/state_frozen_fund.go b/core/state/state_frozen_fund.go index 8d647781f..2d4c26f15 100644 --- a/core/state/state_frozen_fund.go +++ b/core/state/state_frozen_fund.go @@ -93,6 +93,7 @@ func (c *stateFrozenFund) deepCopy(db *StateDB, onDirty func(blockHeight uint64) func (c *stateFrozenFund) Delete() { c.deleted = true + c.onDirty(c.blockHeight) } func (c *stateFrozenFund) AddFund(address types.Address, candidateKey []byte, coin types.CoinSymbol, value *big.Int) { diff --git a/core/state/statedb.go b/core/state/statedb.go index 90ab242da..f527c1161 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -622,46 +622,38 @@ func (s *StateDB) CreateCandidate( func (s *StateDB) Commit(deleteEmptyObjects bool) (root []byte, version int64, err error) { // Commit objects to the trie. - for _, addr := range getOrderedObjectsKeys(s.stateObjects) { + for _, addr := range getOrderedObjectsKeys(s.stateObjectsDirty) { stateObject := s.stateObjects[addr] - _, isDirty := s.stateObjectsDirty[addr] - switch { - case stateObject.suicided || (isDirty && deleteEmptyObjects && stateObject.empty()): - // If the object has been removed, don't bother syncing it - // and just mark it for deletion in the trie. + if stateObject.suicided || (deleteEmptyObjects && stateObject.empty()) { s.deleteStateObject(stateObject) - case isDirty: - // Update the object in the main account trie. + } else { s.updateStateObject(stateObject) } delete(s.stateObjectsDirty, addr) } // Commit coins to the trie. - for _, symbol := range getOrderedCoinsKeys(s.stateCoins) { + for _, symbol := range getOrderedCoinsKeys(s.stateCoinsDirty) { stateCoin := s.stateCoins[symbol] - _, isDirty := s.stateCoinsDirty[symbol] - if isDirty { - if stateCoin.data.Volume.Cmp(types.Big0) == 0 { - s.deleteStateCoin(stateCoin) - } else { - s.updateStateCoin(stateCoin) - } + + if stateCoin.data.Volume.Cmp(types.Big0) == 0 { + s.deleteStateCoin(stateCoin) + } else { + s.updateStateCoin(stateCoin) } delete(s.stateCoinsDirty, symbol) } // Commit frozen funds to the trie. - for _, block := range getOrderedFrozenFundsKeys(s.stateFrozenFunds) { + for _, block := range getOrderedFrozenFundsKeys(s.stateFrozenFundsDirty) { frozenFund := s.stateFrozenFunds[block] - _, isDirty := s.stateFrozenFundsDirty[block] - switch { - case frozenFund.deleted: + if frozenFund.deleted { s.deleteFrozenFunds(frozenFund) - case isDirty: + } else { s.updateStateFrozenFund(frozenFund) } + delete(s.stateFrozenFundsDirty, block) } @@ -689,7 +681,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root []byte, version int64, e return hash, version, err } -func getOrderedObjectsKeys(objects map[types.Address]*stateObject) []types.Address { +func getOrderedObjectsKeys(objects map[types.Address]struct{}) []types.Address { keys := make([]types.Address, 0, len(objects)) for k := range objects { keys = append(keys, k) @@ -702,7 +694,7 @@ func getOrderedObjectsKeys(objects map[types.Address]*stateObject) []types.Addre return keys } -func getOrderedCoinsKeys(objects map[types.CoinSymbol]*stateCoin) []types.CoinSymbol { +func getOrderedCoinsKeys(objects map[types.CoinSymbol]struct{}) []types.CoinSymbol { keys := make([]types.CoinSymbol, 0, len(objects)) for k := range objects { keys = append(keys, k) @@ -715,7 +707,7 @@ func getOrderedCoinsKeys(objects map[types.CoinSymbol]*stateCoin) []types.CoinSy return keys } -func getOrderedFrozenFundsKeys(objects map[uint64]*stateFrozenFund) []uint64 { +func getOrderedFrozenFundsKeys(objects map[uint64]struct{}) []uint64 { keys := make([]uint64, 0, len(objects)) for k := range objects { keys = append(keys, k) From 82c9403d1ae68f147dcbe0ea9c3cade127d4e785 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Wed, 19 Sep 2018 10:12:33 +0300 Subject: [PATCH 25/29] Remove unused code --- core/state/statedb.go | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index f527c1161..9a90dc57b 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -52,11 +52,6 @@ var ( cfg = config.GetConfig() ) -// StateDBs within the ethereum protocol are used to store anything -// within the merkle trie. StateDBs take care of caching and storing -// nested states. It's the general query interface to retrieve: -// * Coins -// * Accounts type StateDB struct { db dbm.DB iavl *iavl.MutableTree @@ -79,15 +74,6 @@ type StateDB struct { stakeCache map[types.CoinSymbol]StakeCache - // DB error. - // State objects are used by the consensus core and VM which are - // unable to deal with database-level errors. Any error that occurs - // during a database read is memoized here and will eventually be returned - // by StateDB.Commit. - dbErr error - - thash, bhash types.Hash - lock sync.Mutex } @@ -96,7 +82,6 @@ type StakeCache struct { BipValue *big.Int } -// Create a new state from a given trie func New(height int64, db dbm.DB) (*StateDB, error) { tree := iavl.NewMutableTree(db, 128) @@ -122,22 +107,6 @@ func New(height int64, db dbm.DB) (*StateDB, error) { }, nil } -// setError remembers the first non-nil error it is called with. -func (s *StateDB) setError(err error) { - - if err != nil { - fmt.Printf("ERROR: %s\n", err.Error()) - } - - if s.dbErr == nil { - s.dbErr = err - } -} - -func (s *StateDB) Error() error { - return s.dbErr -} - // Empty returns whether the state object is either non-existent // or empty according to the EIP161 specification (balance = nonce = code = 0) func (s *StateDB) Empty(addr types.Address) bool { From 48f5cd64bb65d642fef22d271ef2eadaa501914f Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Wed, 19 Sep 2018 10:13:22 +0300 Subject: [PATCH 26/29] Fix comment --- core/transaction/unbond.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/transaction/unbond.go b/core/transaction/unbond.go index 0b249dd42..f2365769f 100644 --- a/core/transaction/unbond.go +++ b/core/transaction/unbond.go @@ -94,7 +94,7 @@ func (data UnbondData) Run(sender types.Address, tx *Transaction, context *state } if !isCheck { - // now + 31 days + // now + 30 days unbondAtBlock := currentBlock + unbondPeriod rewardPool.Add(rewardPool, commissionInBaseCoin) From 29498f78be56449af0065761faaa383ed3a0f0aa Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Wed, 19 Sep 2018 10:22:58 +0300 Subject: [PATCH 27/29] Refactor --- core/state/bitarray.go | 12 ++++++------ core/state/state_validator.go | 2 +- core/state/statedb.go | 13 +++++++------ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/core/state/bitarray.go b/core/state/bitarray.go index 3532d663a..4892b2883 100644 --- a/core/state/bitarray.go +++ b/core/state/bitarray.go @@ -18,12 +18,12 @@ type BitArray struct { // NewBitArray returns a new bit array. // It returns nil if the number of bits is zero. -func NewBitArray(bits uint) *BitArray { +func NewBitArray(bits int) *BitArray { if bits <= 0 { return nil } return &BitArray{ - Bits: bits, + Bits: uint(bits), Elems: make([]uint64, (bits+63)/64), } } @@ -38,13 +38,13 @@ func (bA *BitArray) Size() uint { // GetIndex returns the bit at index i within the bit array. // The behavior is undefined if i >= bA.Bits -func (bA *BitArray) GetIndex(i uint) bool { +func (bA *BitArray) GetIndex(i int) bool { if bA == nil { return false } bA.mtx.Lock() defer bA.mtx.Unlock() - return bA.getIndex(i) + return bA.getIndex(uint(i)) } func (bA *BitArray) getIndex(i uint) bool { @@ -56,13 +56,13 @@ func (bA *BitArray) getIndex(i uint) bool { // SetIndex sets the bit at index i within the bit array. // The behavior is undefined if i >= bA.Bits -func (bA *BitArray) SetIndex(i uint, v bool) bool { +func (bA *BitArray) SetIndex(i int, v bool) bool { if bA == nil { return false } bA.mtx.Lock() defer bA.mtx.Unlock() - return bA.setIndex(i, v) + return bA.setIndex(uint(i), v) } func (bA *BitArray) setIndex(i uint, v bool) bool { diff --git a/core/state/state_validator.go b/core/state/state_validator.go index 97c97b179..046649e9a 100644 --- a/core/state/state_validator.go +++ b/core/state/state_validator.go @@ -68,7 +68,7 @@ type Validator struct { func (validator *Validator) CountAbsentTimes() int { count := 0 - for i := uint(0); i < 24; i++ { + for i := 0; i < ValidatorMaxAbsentWindow; i++ { if validator.AbsentTimes.GetIndex(i) { count++ } diff --git a/core/state/statedb.go b/core/state/statedb.go index 9a90dc57b..bc3372ed0 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -40,7 +40,8 @@ import ( const UnbondPeriod = 518400 var ( - ValidatorMaxAbsentTimes = 12 + ValidatorMaxAbsentWindow = 24 + ValidatorMaxAbsentTimes = 12 addressPrefix = []byte("a") coinPrefix = []byte("c") @@ -541,7 +542,7 @@ func (s *StateDB) CreateValidator( PubKey: pubkey, Commission: commission, AccumReward: big.NewInt(0), - AbsentTimes: NewBitArray(24), + AbsentTimes: NewBitArray(ValidatorMaxAbsentWindow), }) s.MarkStateValidatorsDirty() @@ -1026,7 +1027,7 @@ func (s *StateDB) SetValidatorPresent(height int64, address [20]byte) { for i := range validators.data { validator := &validators.data[i] if validator.GetAddress() == address { - validator.AbsentTimes.SetIndex(uint(height%24), false) + validator.AbsentTimes.SetIndex(int(height)%ValidatorMaxAbsentWindow, false) } } @@ -1057,11 +1058,11 @@ func (s *StateDB) SetValidatorAbsent(height int64, address [20]byte) { return } - validator.AbsentTimes.SetIndex(uint(height%24), true) + validator.AbsentTimes.SetIndex(int(height)%ValidatorMaxAbsentWindow, true) if validator.CountAbsentTimes() > ValidatorMaxAbsentTimes { candidate.Status = CandidateStatusOffline - validator.AbsentTimes = NewBitArray(24) + validator.AbsentTimes = NewBitArray(ValidatorMaxAbsentWindow) validator.toDrop = true totalStake := big.NewInt(0) @@ -1174,7 +1175,7 @@ func (s *StateDB) SetNewValidators(candidates []Candidate) { for _, candidate := range candidates { accumReward := big.NewInt(0) - absentTimes := NewBitArray(24) + absentTimes := NewBitArray(ValidatorMaxAbsentWindow) for _, oldVal := range oldVals.data { if oldVal.GetAddress() == candidate.GetAddress() { From f842259224fa8d542440205259744c5b744a0529 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Wed, 19 Sep 2018 10:23:34 +0300 Subject: [PATCH 28/29] Refactor --- core/state/bitarray.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state/bitarray.go b/core/state/bitarray.go index 4892b2883..5dbd8ceab 100644 --- a/core/state/bitarray.go +++ b/core/state/bitarray.go @@ -361,9 +361,9 @@ func (bA *BitArray) UnmarshalJSON(bz []byte) error { bits := match[1] // Construct new BitArray and copy over. - numBits := uint(len(bits)) + numBits := len(bits) bA2 := NewBitArray(numBits) - for i := uint(0); i < numBits; i++ { + for i := 0; i < numBits; i++ { if bits[i] == 'x' { bA2.SetIndex(i, true) } From 0395c9ff8b6be77b104e08d40fc4cf3a734f4c83 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Wed, 19 Sep 2018 11:07:01 +0300 Subject: [PATCH 29/29] Remove comments --- core/state/statedb.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index bc3372ed0..4183f2996 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// Package state provides a caching layer atop the Ethereum state trie. package state import ( @@ -969,7 +968,6 @@ func (s *StateDB) SubStake(sender types.Address, pubkey []byte, coin types.CoinS for i := range stateCandidates.data { candidate := &stateCandidates.data[i] if candidate.PubKey.Compare(pubkey) == 0 { - // todo: remove if stake == 0 currentStakeValue := candidate.GetStakeOfAddress(sender, coin).Value currentStakeValue.Sub(currentStakeValue, value) } @@ -1091,7 +1089,6 @@ func (s *StateDB) SetValidatorAbsent(height int64, address [20]byte) { totalStake.Add(totalStake, newValue) } - // TODO: recalc total stake in bips validator.TotalBipStake = totalStake }