/* This file is part of cpp-ethereum. cpp-ethereum is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. cpp-ethereum 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 General Public License for more details. You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ /** @file TrieDB.h * @author Gav Wood * @date 2014 */ #pragma once #pragma warning(push) #pragma warning(disable: 4100 4267) #include #pragma warning(pop) #include #include #include #include #include #include "MemoryDB.h" #include "OverlayDB.h" #include "TrieCommon.h" namespace ldb = leveldb; namespace dev { namespace eth { struct TrieDBChannel: public LogChannel { static const char* name() { return "-T-"; } static const int verbosity = 6; }; #define tdebug clog(TrieDBChannel) class InvalidTrie: public std::exception {}; extern const h256 c_shaNull; /** * @brief Merkle Patricia Tree "Trie": a modifed base-16 Radix tree. * This version uses an database backend. * Usage: * @code * GenericTrieDB t(&myDB); * assert(t.isNull()); * t.init(); * assert(t.isEmpty()); * t.insert(x, y); * assert(t.at(x) == y.toString()); * t.remove(x); * assert(t.isEmpty()); * @endcode */ template class GenericTrieDB { public: GenericTrieDB(DB* _db): m_db(_db) {} GenericTrieDB(DB* _db, h256 _root) { open(_db, _root); } ~GenericTrieDB() {} void open(DB* _db, h256 _root) { m_db = _db; setRoot(_root); } void init(); void setRoot(h256 _root) { m_root = _root == h256() ? c_shaNull : _root; if (m_root == c_shaNull && !m_db->exists(m_root)) init(); /*std::cout << "Setting root to " << _root << " (patched to " << m_root << ")" << std::endl;*/ if (!node(m_root).size()) throw RootNotFound(); } bool haveRoot(h256 _root, bool _enforceRefs = true) { return _root == h256() ? true : m_db->lookup(_root, _enforceRefs).size(); } /// True if the trie is uninitialised (i.e. that the DB doesn't contain the root node). bool isNull() const { return !node(m_root).size(); } /// True if the trie is initialised but empty (i.e. that the DB contains the root node which is empty). bool isEmpty() const { return m_root == c_shaNull && node(m_root).size(); } h256 root() const { assert(node(m_root).size()); h256 ret = (m_root == c_shaNull ? h256() : m_root); /*std::cout << "Returning root as " << ret << " (really " << m_root << ")" << std::endl;*/ return ret; } // patch the root in the case of the empty trie. TODO: handle this properly. void debugPrint() {} void descendKey(h256 _k, std::set& _keyMask, bool _wasExt, std::ostream* _out, int _indent = 0) const { _keyMask.erase(_k); if (_k == m_root && _k == c_shaNull) // root allowed to be empty return; descendList(RLP(node(_k)), _keyMask, _wasExt, _out, _indent); // if not, it must be a list } void descendEntry(RLP const& _r, std::set& _keyMask, bool _wasExt, std::ostream* _out, int _indent) const { if (_r.isData() && _r.size() == 32) descendKey(_r.toHash(), _keyMask, _wasExt, _out, _indent); else if (_r.isList()) descendList(_r, _keyMask, _wasExt, _out, _indent); else throw InvalidTrie(); } void descendList(RLP const& _r, std::set& _keyMask, bool _wasExt, std::ostream* _out, int _indent) const { if (_r.isList() && _r.itemCount() == 2 && (!_wasExt || _out)) { if (_out) (*_out) << std::string(_indent * 2, ' ') << (_wasExt ? "!2 " : "2 ") << sha3(_r.data()).abridged() << ": " << _r << "\n"; if (!isLeaf(_r)) // don't go down leaves descendEntry(_r[1], _keyMask, true, _out, _indent + 1); } else if (_r.isList() && _r.itemCount() == 17) { if (_out) (*_out) << std::string(_indent * 2, ' ') << "17 " << sha3(_r.data()).abridged() << ": " << _r << "\n"; for (unsigned i = 0; i < 16; ++i) if (!_r[i].isEmpty()) // 16 branches are allowed to be empty descendEntry(_r[i], _keyMask, false, _out, _indent + 1); } else throw InvalidTrie(); } std::set leftOvers(std::ostream* _out = nullptr) const { std::set k = m_db->keys(); descendKey(m_root, k, false, _out); return k; } void debugStructure(std::ostream& _out) const { leftOvers(&_out); } bool check(bool _requireNoLeftOvers) const { try { return leftOvers().empty() || !_requireNoLeftOvers; } catch (...) { return false; } } std::string at(bytesConstRef _key) const; void insert(bytesConstRef _key, bytesConstRef _value); void remove(bytesConstRef _key); void contains(bytesConstRef _key) { return !at(_key).empty(); } class iterator { public: using value_type = std::pair; iterator() {} iterator(GenericTrieDB const* _db); iterator& operator++() { next(); return *this; } value_type operator*() const { return at(); } value_type operator->() const { return at(); } bool operator==(iterator const& _c) const { return _c.m_trail == m_trail; } bool operator!=(iterator const& _c) const { return _c.m_trail != m_trail; } value_type at() const; private: void next(); struct Node { std::string rlp; std::string key; // as hexPrefixEncoding. byte child; // 255 -> entering void setFirstChild() { child = 16; } void incrementChild() { child = child == 16 ? 0 : child == 15 ? 17 : (child + 1); } bool operator==(Node const& _c) const { return rlp == _c.rlp && key == _c.key && child == _c.child; } bool operator!=(Node const& _c) const { return !operator==(_c); } }; std::vector m_trail; GenericTrieDB const* m_that; }; iterator begin() const { return this; } iterator end() const { return iterator(); } private: RLPStream& streamNode(RLPStream& _s, bytes const& _b); std::string atAux(RLP const& _here, NibbleSlice _key) const; void mergeAtAux(RLPStream& _out, RLP const& _replace, NibbleSlice _key, bytesConstRef _value); bytes mergeAt(RLP const& _replace, NibbleSlice _k, bytesConstRef _v, bool _inLine = false); bool deleteAtAux(RLPStream& _out, RLP const& _replace, NibbleSlice _key); bytes deleteAt(RLP const& _replace, NibbleSlice _k); // in: null (DEL) -- OR -- [_k, V] (DEL) // out: [_k, _s] // -- OR -- // in: [V0, ..., V15, S16] (DEL) AND _k == {} // out: [V0, ..., V15, _s] bytes place(RLP const& _orig, NibbleSlice _k, bytesConstRef _s); // in: [K, S] (DEL) // out: null // -- OR -- // in: [V0, ..., V15, S] (DEL) // out: [V0, ..., V15, null] bytes remove(RLP const& _orig); // in: [K1 & K2, V] (DEL) : nibbles(K1) == _s, 0 < _s <= nibbles(K1 & K2) // out: [K1, H] ; [K2, V] => H (INS) (being [K1, [K2, V]] if necessary) bytes cleve(RLP const& _orig, unsigned _s); // in: [K1, H] (DEL) ; H <= [K2, V] (DEL) (being [K1, [K2, V]] (DEL) if necessary) // out: [K1 & K2, V] bytes graft(RLP const& _orig); // in: [V0, ... V15, S] (DEL) // out1: [k{i}, Vi] where i < 16 // out2: [k{}, S] where i == 16 bytes merge(RLP const& _orig, byte _i); // in: [k{}, S] (DEL) // out: [null ** 16, S] // -- OR -- // in: [k{i}, N] (DEL) // out: [null ** i, N, null ** (16 - i)] // -- OR -- // in: [k{i}K, V] (DEL) // out: [null ** i, H, null ** (16 - i)] ; [K, V] => H (INS) (being [null ** i, [K, V], null ** (16 - i)] if necessary) bytes branch(RLP const& _orig); bool isTwoItemNode(RLP const& _n) const; std::string deref(RLP const& _n) const; std::string node(h256 _h) const { return m_db->lookup(_h); } void insertNode(h256 _h, bytesConstRef _v) { m_db->insert(_h, _v); } void killNode(h256 _h) { m_db->kill(_h); } h256 insertNode(bytesConstRef _v) { auto h = sha3(_v); insertNode(h, _v); return h; } void killNode(RLP const& _d) { if (_d.data().size() >= 32) killNode(sha3(_d.data())); } h256 m_root; DB* m_db = nullptr; }; template std::ostream& operator<<(std::ostream& _out, GenericTrieDB const& _db) { for (auto const& i: _db) _out << escaped(i.first.toString(), false) << ": " << escaped(i.second.toString(), false) << std::endl; return _out; } template class TrieDB: public GenericTrieDB { public: TrieDB(DB* _db): GenericTrieDB(_db) {} TrieDB(DB* _db, h256 _root): GenericTrieDB(_db, _root) {} std::string operator[](KeyType _k) const { return at(_k); } bool contains(KeyType _k) const { return GenericTrieDB::contains(bytesConstRef((byte const*)&_k, sizeof(KeyType))); } std::string at(KeyType _k) const { return GenericTrieDB::at(bytesConstRef((byte const*)&_k, sizeof(KeyType))); } void insert(KeyType _k, bytesConstRef _value) { GenericTrieDB::insert(bytesConstRef((byte const*)&_k, sizeof(KeyType)), _value); } void insert(KeyType _k, bytes const& _value) { insert(_k, bytesConstRef(&_value)); } void remove(KeyType _k) { GenericTrieDB::remove(bytesConstRef((byte const*)&_k, sizeof(KeyType))); } class iterator: public GenericTrieDB::iterator { public: using Super = typename GenericTrieDB::iterator; using value_type = std::pair; iterator() {} iterator(TrieDB const* _db): Super(_db) {} value_type operator*() const { return at(); } value_type operator->() const { return at(); } value_type at() const; }; iterator begin() const { return this; } iterator end() const { return iterator(); } }; template std::ostream& operator<<(std::ostream& _out, TrieDB const& _db) { for (auto const& i: _db) _out << i.first << ": " << escaped(i.second.toString(), false) << std::endl; return _out; } } } // Template implementations... namespace dev { namespace eth { template GenericTrieDB::iterator::iterator(GenericTrieDB const* _db) { m_that = _db; m_trail.push_back({_db->node(_db->m_root), std::string(1, '\0'), 255}); // one null byte is the HPE for the empty key. next(); } template typename GenericTrieDB::iterator::value_type GenericTrieDB::iterator::at() const { assert(m_trail.size()); Node const& b = m_trail.back(); assert(b.key.size()); assert(!(b.key[0] & 0x10)); // should be an integer number of bytes (i.e. not an odd number of nibbles). RLP rlp(b.rlp); if (rlp.itemCount() == 2) return std::make_pair(bytesConstRef(b.key).cropped(1), rlp[1].payload()); else return std::make_pair(bytesConstRef(b.key).cropped(1), rlp[16].payload()); } template void GenericTrieDB::iterator::next() { while (true) { if (m_trail.empty()) { m_that = nullptr; return; } Node const& b = m_trail.back(); RLP rlp(b.rlp); if (m_trail.back().child == 255) { // Entering. Look for first... if (rlp.isEmpty()) { m_trail.pop_back(); continue; } if (!(rlp.isList() && (rlp.itemCount() == 2 || rlp.itemCount() == 17))) { #if ETH_PARANOIA cwarn << "BIG FAT ERROR. STATE TRIE CORRUPTED!!!!!"; cwarn << b.rlp.size() << toHex(b.rlp); cwarn << rlp; auto c = rlp.itemCount(); cwarn << c; throw InvalidTrie(); #else m_that = nullptr; return; #endif } if (rlp.itemCount() == 2) { // Just turn it into a valid Branch m_trail.back().key = hexPrefixEncode(keyOf(m_trail.back().key), keyOf(rlp), false); if (isLeaf(rlp)) { // leaf - exit now. m_trail.back().child = 0; return; } // enter child. m_trail.back().rlp = m_that->deref(rlp[1]); // no need to set .child as 255 - it's already done. continue; } else { // Already a branch - look for first valid. m_trail.back().setFirstChild(); // run through to... } } else { // Continuing/exiting. Look for next... if (!(rlp.isList() && rlp.itemCount() == 17)) { m_trail.pop_back(); continue; } // else run through to... m_trail.back().incrementChild(); } // ...here. should only get here if we're a list. assert(rlp.isList() && rlp.itemCount() == 17); for (;; m_trail.back().incrementChild()) if (m_trail.back().child == 17) { // finished here. m_trail.pop_back(); break; } else if (!rlp[m_trail.back().child].isEmpty()) { if (m_trail.back().child == 16) return; // have a value at this node - exit now. else { // lead-on to another node - enter child. // fixed so that Node passed into push_back is constructed *before* m_trail is potentially resized (which invalidates back and rlp) Node const& back = m_trail.back(); m_trail.push_back(Node{ m_that->deref(rlp[back.child]), hexPrefixEncode(keyOf(back.key), NibbleSlice(bytesConstRef(&back.child, 1), 1), false), 255 }); break; } } } } template typename TrieDB::iterator::value_type TrieDB::iterator::at() const { auto p = Super::at(); value_type ret; assert(p.first.size() == sizeof(KeyType)); memcpy(&ret.first, p.first.data(), sizeof(KeyType)); ret.second = p.second; return ret; } template void GenericTrieDB::init() { m_root = insertNode(&RLPNull); // std::cout << "Initialised root to " << m_root << std::endl; assert(node(m_root).size()); } template void GenericTrieDB::insert(bytesConstRef _key, bytesConstRef _value) { #if ETH_PARANOIA tdebug << "Insert" << toHex(_key.cropped(0, 4)) << "=>" << toHex(_value); #endif std::string rv = node(m_root); assert(rv.size()); bytes b = mergeAt(RLP(rv), NibbleSlice(_key), _value); // mergeAt won't attempt to delete the node is it's less than 32 bytes // However, we know it's the root node and thus always hashed. // So, if it's less than 32 (and thus should have been deleted but wasn't) then we delete it here. if (rv.size() < 32) killNode(m_root); m_root = insertNode(&b); } template std::string GenericTrieDB::at(bytesConstRef _key) const { return atAux(RLP(node(m_root)), _key); } template std::string GenericTrieDB::atAux(RLP const& _here, NibbleSlice _key) const { if (_here.isEmpty() || _here.isNull()) // not found. return std::string(); assert(_here.isList() && (_here.itemCount() == 2 || _here.itemCount() == 17)); if (_here.itemCount() == 2) { auto k = keyOf(_here); if (_key == k && isLeaf(_here)) // reached leaf and it's us return _here[1].toString(); else if (_key.contains(k) && !isLeaf(_here)) // not yet at leaf and it might yet be us. onwards... return atAux(_here[1].isList() ? _here[1] : RLP(node(_here[1].toHash())), _key.mid(k.size())); else // not us. return std::string(); } else { if (_key.size() == 0) return _here[16].toString(); auto n = _here[_key[0]]; if (n.isEmpty()) return std::string(); else return atAux(n.isList() ? n : RLP(node(n.toHash())), _key.mid(1)); } } template bytes GenericTrieDB::mergeAt(RLP const& _orig, NibbleSlice _k, bytesConstRef _v, bool _inLine) { #if ETH_PARANOIA tdebug << "mergeAt " << _orig << _k << sha3(_orig.data()).abridged(); #endif // The caller will make sure that the bytes are inserted properly. // - This might mean inserting an entry into m_over // We will take care to ensure that (our reference to) _orig is killed. // Empty - just insert here if (_orig.isEmpty()) return place(_orig, _k, _v); assert(_orig.isList() && (_orig.itemCount() == 2 || _orig.itemCount() == 17)); if (_orig.itemCount() == 2) { // pair... NibbleSlice k = keyOf(_orig); // exactly our node - place value in directly. if (k == _k && isLeaf(_orig)) return place(_orig, _k, _v); // partial key is our key - move down. if (_k.contains(k) && !isLeaf(_orig)) { if (!_inLine) killNode(_orig); RLPStream s(2); s.append(_orig[0]); mergeAtAux(s, _orig[1], _k.mid(k.size()), _v); return s.out(); } auto sh = _k.shared(k); // std::cout << _k << " sh " << k << " = " << sh << std::endl; if (sh) // shared stuff - cleve at disagreement. return mergeAt(RLP(cleve(_orig, sh)), _k, _v, true); else // nothing shared - branch return mergeAt(RLP(branch(_orig)), _k, _v, true); } else { // branch... // exactly our node - place value. if (_k.size() == 0) return place(_orig, _k, _v); // Kill the node. if (!_inLine) killNode(_orig); // not exactly our node - delve to next level at the correct index. byte n = _k[0]; RLPStream r(17); for (byte i = 0; i < 17; ++i) if (i == n) mergeAtAux(r, _orig[i], _k.mid(1), _v); else r.append(_orig[i]); return r.out(); } } template void GenericTrieDB::mergeAtAux(RLPStream& _out, RLP const& _orig, NibbleSlice _k, bytesConstRef _v) { #if ETH_PARANOIA tdebug << "mergeAtAux " << _orig << _k << sha3(_orig.data()).abridged() << ((_orig.isData() && _orig.size() <= 32) ? _orig.toHash().abridged() : std::string()); #endif RLP r = _orig; std::string s; // _orig is always a segment of a node's RLP - removing it alone is pointless. However, if may be a hash, in which case we deref and we know it is removable. bool isRemovable = false; if (!r.isList() && !r.isEmpty()) { s = node(_orig.toHash()); r = RLP(s); assert(!r.isNull()); isRemovable = true; } bytes b = mergeAt(r, _k, _v, !isRemovable); streamNode(_out, b); } template void GenericTrieDB::remove(bytesConstRef _key) { #if ETH_PARANOIA tdebug << "Remove" << toHex(_key.cropped(0, 4).toBytes()); #endif std::string rv = node(m_root); bytes b = deleteAt(RLP(rv), NibbleSlice(_key)); if (b.size()) { if (rv.size() < 32) killNode(m_root); m_root = insertNode(&b); } } template bool GenericTrieDB::isTwoItemNode(RLP const& _n) const { return (_n.isData() && RLP(node(_n.toHash())).itemCount() == 2) || (_n.isList() && _n.itemCount() == 2); } template std::string GenericTrieDB::deref(RLP const& _n) const { return _n.isList() ? _n.data().toString() : node(_n.toHash()); } template bytes GenericTrieDB::deleteAt(RLP const& _orig, NibbleSlice _k) { #if ETH_PARANOIA tdebug << "deleteAt " << _orig << _k << sha3(_orig.data()).abridged(); #endif // The caller will make sure that the bytes are inserted properly. // - This might mean inserting an entry into m_over // We will take care to ensure that (our reference to) _orig is killed. // Empty - not found - no change. if (_orig.isEmpty()) return bytes(); assert(_orig.isList() && (_orig.itemCount() == 2 || _orig.itemCount() == 17)); if (_orig.itemCount() == 2) { // pair... NibbleSlice k = keyOf(_orig); // exactly our node - return null. if (k == _k && isLeaf(_orig)) return RLPNull; // partial key is our key - move down. if (_k.contains(k)) { RLPStream s; s.appendList(2) << _orig[0]; if (!deleteAtAux(s, _orig[1], _k.mid(k.size()))) return bytes(); killNode(_orig); RLP r(s.out()); if (isTwoItemNode(r[1])) return graft(r); return s.out(); } else // not found - no change. return bytes(); } else { // branch... // exactly our node - remove and rejig. if (_k.size() == 0 && !_orig[16].isEmpty()) { // Kill the node. killNode(_orig); byte used = uniqueInUse(_orig, 16); if (used != 255) if (isTwoItemNode(_orig[used])) return graft(RLP(merge(_orig, used))); else return merge(_orig, used); else { RLPStream r(17); for (byte i = 0; i < 16; ++i) r << _orig[i]; r << ""; return r.out(); } } else { // not exactly our node - delve to next level at the correct index. RLPStream r(17); byte n = _k[0]; for (byte i = 0; i < 17; ++i) if (i == n) if (!deleteAtAux(r, _orig[i], _k.mid(1))) // bomb out if the key didn't turn up. return bytes(); else {} else r << _orig[i]; // Kill the node. killNode(_orig); // check if we ended up leaving the node invalid. RLP rlp(r.out()); byte used = uniqueInUse(rlp, 255); if (used == 255) // no - all ok. return r.out(); // yes; merge if (isTwoItemNode(rlp[used])) return graft(RLP(merge(rlp, used))); else return merge(rlp, used); } } } template bool GenericTrieDB::deleteAtAux(RLPStream& _out, RLP const& _orig, NibbleSlice _k) { #if ETH_PARANOIA tdebug << "deleteAtAux " << _orig << _k << sha3(_orig.data()).abridged() << ((_orig.isData() && _orig.size() <= 32) ? _orig.toHash().abridged() : std::string()); #endif bytes b = _orig.isEmpty() ? bytes() : deleteAt(_orig.isList() ? _orig : RLP(node(_orig.toHash())), _k); if (!b.size()) // not found - no change. return false; /* if (_orig.isList()) killNode(_orig); else killNode(_orig.toHash());*/ streamNode(_out, b); return true; } template bytes GenericTrieDB::place(RLP const& _orig, NibbleSlice _k, bytesConstRef _s) { #if ETH_PARANOIA tdebug << "place " << _orig << _k; #endif killNode(_orig); if (_orig.isEmpty()) return (RLPStream(2) << hexPrefixEncode(_k, true) << _s).out(); assert(_orig.isList() && (_orig.itemCount() == 2 || _orig.itemCount() == 17)); if (_orig.itemCount() == 2) return (RLPStream(2) << _orig[0] << _s).out(); auto s = RLPStream(17); for (unsigned i = 0; i < 16; ++i) s << _orig[i]; s << _s; return s.out(); } // in1: [K, S] (DEL) // out1: null // in2: [V0, ..., V15, S] (DEL) // out2: [V0, ..., V15, null] iff exists i: !!Vi -- OR -- null otherwise template bytes GenericTrieDB::remove(RLP const& _orig) { #if ETH_PARANOIA tdebug << "kill " << _orig; #endif killNode(_orig); assert(_orig.isList() && (_orig.itemCount() == 2 || _orig.itemCount() == 17)); if (_orig.itemCount() == 2) return RLPNull; RLPStream r(17); for (unsigned i = 0; i < 16; ++i) r << _orig[i]; r << ""; return r.out(); } template RLPStream& GenericTrieDB::streamNode(RLPStream& _s, bytes const& _b) { if (_b.size() < 32) _s.appendRaw(_b); else _s.append(insertNode(&_b)); return _s; } template bytes GenericTrieDB::cleve(RLP const& _orig, unsigned _s) { #if ETH_PARANOIA tdebug << "cleve " << _orig << _s; #endif killNode(_orig); assert(_orig.isList() && _orig.itemCount() == 2); auto k = keyOf(_orig); assert(_s && _s <= k.size()); RLPStream bottom(2); bottom << hexPrefixEncode(k, isLeaf(_orig), /*ugh*/(int)_s) << _orig[1]; RLPStream top(2); top << hexPrefixEncode(k, false, 0, /*ugh*/(int)_s); streamNode(top, bottom.out()); return top.out(); } template bytes GenericTrieDB::graft(RLP const& _orig) { #if ETH_PARANOIA tdebug << "graft " << _orig; #endif assert(_orig.isList() && _orig.itemCount() == 2); std::string s; RLP n; if (_orig[1].isList()) n = _orig[1]; else { // remove second item from the trie after derefrencing it into s & n. auto lh = _orig[1].toHash(); s = node(lh); killNode(lh); n = RLP(s); } assert(n.itemCount() == 2); return (RLPStream(2) << hexPrefixEncode(keyOf(_orig), keyOf(n), isLeaf(n)) << n[1]).out(); // auto ret = // std::cout << keyOf(_orig) << " ++ " << keyOf(n) << " == " << keyOf(RLP(ret)) << std::endl; // return ret; } template bytes GenericTrieDB::merge(RLP const& _orig, byte _i) { #if ETH_PARANOIA tdebug << "merge " << _orig << (int)_i; #endif assert(_orig.isList() && _orig.itemCount() == 17); RLPStream s(2); if (_i != 16) { assert(!_orig[_i].isEmpty()); s << hexPrefixEncode(bytesConstRef(&_i, 1), false, 1, 2, 0); } else s << hexPrefixEncode(bytes(), true); s << _orig[_i]; return s.out(); } template bytes GenericTrieDB::branch(RLP const& _orig) { #if ETH_PARANOIA tdebug << "branch " << _orig; #endif assert(_orig.isList() && _orig.itemCount() == 2); killNode(_orig); auto k = keyOf(_orig); RLPStream r(17); if (k.size() == 0) { assert(isLeaf(_orig)); for (unsigned i = 0; i < 16; ++i) r << ""; r << _orig[1]; } else { byte b = k[0]; for (unsigned i = 0; i < 16; ++i) if (i == b) if (isLeaf(_orig) || k.size() > 1) { RLPStream bottom(2); bottom << hexPrefixEncode(k.mid(1), isLeaf(_orig)) << _orig[1]; streamNode(r, bottom.out()); } else r << _orig[1]; else r << ""; r << ""; } return r.out(); } } }