Browse Source

Move over to DB/overlay-based state.

cl-refactor
Gav Wood 11 years ago
parent
commit
9347a1ab8b
  1. 11
      libethereum/BlockInfo.cpp
  2. 1
      libethereum/BlockInfo.h
  3. 1
      libethereum/Exceptions.h
  4. 6
      libethereum/RLP.cpp
  5. 18
      libethereum/RLP.h
  6. 187
      libethereum/State.cpp
  7. 35
      libethereum/State.h
  8. 4
      libethereum/Transaction.h
  9. 2
      libethereum/Trie.cpp
  10. 56
      libethereum/Trie.h
  11. 2
      libethereum/vector_ref.h

11
libethereum/BlockInfo.cpp

@ -42,7 +42,7 @@ bytes BlockInfo::createGenesisBlock()
{
RLPStream block(3);
auto sha3EmptyList = sha3(RLPEmptyList);
block.appendList(8) << (uint)0 << sha3EmptyList << (uint)0 << sha3(RLPNull) << sha3EmptyList << ((uint)1 << 36) << (uint)0 << (uint)0;
block.appendList(9) << (uint)0 << sha3EmptyList << (uint)0 << sha3(RLPNull) << sha3EmptyList << ((uint)1 << 36) << (uint)0 << (uint)0 << (uint)0;
block.appendRaw(RLPEmptyList);
block.appendRaw(RLPEmptyList);
return block.out();
@ -50,12 +50,14 @@ bytes BlockInfo::createGenesisBlock()
h256 BlockInfo::headerHashWithoutNonce() const
{
return sha3((RLPStream(7) << parentHash << sha3Uncles << coinbaseAddress << stateRoot << sha3Transactions << difficulty << timestamp).out());
RLPStream s;
fillStream(s, false);
return sha3(s.out());
}
void BlockInfo::fillStream(RLPStream& _s, bool _nonce) const
{
_s.appendList(_nonce ? 8 : 7) << parentHash << sha3Uncles << coinbaseAddress << stateRoot << sha3Transactions << difficulty << timestamp;
_s.appendList(_nonce ? 9 : 8) << parentHash << sha3Uncles << coinbaseAddress << stateRoot << sha3Transactions << difficulty << timestamp << extraData;
if (_nonce)
_s << nonce;
}
@ -80,7 +82,8 @@ void BlockInfo::populate(bytesConstRef _block)
sha3Transactions = header[4].toHash<h256>();
difficulty = header[5].toInt<u256>();
timestamp = header[6].toInt<u256>();
nonce = header[7].toInt<u256>();
extraData = header[7].toHash<h256>();
nonce = header[8].toInt<u256>();
}
catch (RLP::BadCast)
{

1
libethereum/BlockInfo.h

@ -38,6 +38,7 @@ public:
h256 sha3Transactions;
u256 difficulty;
u256 timestamp;
h256 extraData;
u256 nonce;
BlockInfo();

1
libethereum/Exceptions.h

@ -27,5 +27,6 @@ class InvalidDifficulty: public std::exception {};
class InvalidTimestamp: public std::exception {};
class InvalidNonce: public std::exception { public: InvalidNonce(u256 _required = 0, u256 _candidate = 0): required(_required), candidate(_candidate) {} u256 required; u256 candidate; };
class InvalidParentHash: public std::exception {};
class InvalidContractAddress: public std::exception {};
}

6
libethereum/RLP.cpp

@ -113,7 +113,7 @@ eth::uint RLP::items() const
return ret;
}
RLPStream& RLPStream::append(std::string const& _s)
RLPStream& RLPStream::appendString(bytesConstRef _s)
{
if (_s.size() < 0x38)
m_out.push_back(_s.size() | 0x40);
@ -125,7 +125,7 @@ RLPStream& RLPStream::append(std::string const& _s)
return *this;
}
RLPStream& RLPStream::appendString(bytes const& _s)
RLPStream& RLPStream::appendString(string const& _s)
{
if (_s.size() < 0x38)
m_out.push_back(_s.size() | 0x40);
@ -137,7 +137,7 @@ RLPStream& RLPStream::appendString(bytes const& _s)
return *this;
}
RLPStream& RLPStream::appendRaw(bytes const& _s)
RLPStream& RLPStream::appendRaw(bytesConstRef _s)
{
uint os = m_out.size();
m_out.resize(os + _s.size());

18
libethereum/RLP.h

@ -163,6 +163,10 @@ public:
explicit operator bigint() const { return toBigInt(); }
template <unsigned _N> explicit operator FixedHash<_N>() const { return toHash<_N>(); }
/// Converts to bytearray. @returns the empty byte array if not a string.
bytes toBytes() const { if (!isString()) return bytes(); bytes(payload().data(), payload().data() + items()); }
/// Converts to bytearray. @returns the empty byte array if not a string.
bytesConstRef toBytesConstRef() const { if (!isString()) return bytesConstRef(); payload().cropped(0, items()); }
/// Converts to string. @returns the empty string if not a string.
std::string toString() const { if (!isString()) return std::string(); return payload().cropped(0, items()).toString(); }
/// Converts to string. @throws BadCast if not a string.
@ -296,10 +300,13 @@ public:
RLPStream& append(h160 _s, bool _compact = false) { return appendFixed(_s, _compact); }
RLPStream& append(h256 _s, bool _compact = false) { return appendFixed(_s, _compact); }
RLPStream& append(bigint _s);
RLPStream& append(std::string const& _s);
RLPStream& appendList(uint _count);
RLPStream& appendRaw(bytes const& _rlp);
RLPStream& appendString(bytes const& _rlp);
RLPStream& appendString(bytesConstRef _s);
RLPStream& appendString(bytes const& _s) { return appendString(bytesConstRef(&_s)); }
RLPStream& appendString(std::string const& _s);
RLPStream& appendRaw(bytesConstRef _rlp);
RLPStream& appendRaw(bytes const& _rlp) { return appendRaw(&_rlp); }
RLPStream& appendRaw(RLP const& _rlp) { return appendRaw(_rlp.data()); }
/// Shift operators for appending data items.
RLPStream& operator<<(uint _i) { return append(_i); }
@ -308,8 +315,9 @@ public:
RLPStream& operator<<(h160 _i) { return append(_i); }
RLPStream& operator<<(h256 _i) { return append(_i); }
RLPStream& operator<<(bigint _i) { return append(_i); }
RLPStream& operator<<(char const* _s) { return append(std::string(_s)); }
RLPStream& operator<<(std::string const& _s) { return append(_s); }
RLPStream& operator<<(char const* _s) { return appendString(std::string(_s)); }
RLPStream& operator<<(std::string const& _s) { return appendString(_s); }
RLPStream& operator<<(RLP const& _i) { return appendRaw(_i); }
template <class _T> RLPStream& operator<<(std::vector<_T> const& _s) { appendList(_s.size()); for (auto const& i: _s) append(i); return *this; }
/// Read the byte stream.

187
libethereum/State.cpp

@ -23,11 +23,13 @@
#include <sha.h>
#include <sha3.h>
#include <ripemd.h>
#include <time.h>
#include <random>
#include "Trie.h"
#include "BlockChain.h"
#include "Instruction.h"
#include "Exceptions.h"
#include "Dagger.h"
#include "State.h"
using namespace std;
using namespace eth;
@ -46,6 +48,10 @@ State::State(Address _coinbaseAddress): m_ourAddress(_coinbaseAddress)
secp256k1_start();
m_previousBlock = BlockInfo::genesis();
m_currentBlock.coinbaseAddress = m_ourAddress;
ldb::Options o;
ldb::DB::Open(o, "state", &m_db);
m_state.open(m_db, m_currentBlock.stateRoot, &m_over);
}
void State::sync(BlockChain const& _bc)
@ -75,11 +81,8 @@ void State::sync(BlockChain const& _bc, h256 _block)
// We mined the last block.
// Our state is good - we just need to move on to next.
m_previousBlock = m_currentBlock;
m_current.clear();
m_transactions.clear();
m_currentBlock = BlockInfo();
m_currentBlock.coinbaseAddress = m_ourAddress;
++m_currentNumber;
resetCurrent();
m_currentNumber++;
}
else if (bi == m_previousBlock)
{
@ -107,14 +110,18 @@ void State::sync(BlockChain const& _bc, h256 _block)
for (auto it = next(l.cbegin()); it != l.cend(); ++it)
playback(_bc.block(*it));
m_transactions.clear();
m_current.clear();
m_currentBlock = BlockInfo();
m_currentNumber = _bc.details(_bc.currentHash()).number + 1;
m_currentBlock.coinbaseAddress = m_ourAddress;
resetCurrent();
}
}
void State::resetCurrent()
{
m_transactions.clear();
m_currentBlock = BlockInfo();
m_currentBlock.coinbaseAddress = m_ourAddress;
m_currentBlock.stateRoot = m_previousBlock.stateRoot;
}
void State::sync(TransactionQueue& _tq)
{
@ -195,15 +202,14 @@ u256 State::playback(bytesConstRef _block, BlockInfo const& _grandParent)
throw InvalidStateRoot();
m_previousBlock = m_currentBlock;
m_currentBlock = BlockInfo();
m_currentBlock.coinbaseAddress = m_ourAddress;
resetCurrent();
return tdIncrease;
}
// @returns the block that represents the difference between m_previousBlock and m_currentBlock.
// (i.e. all the transactions we executed).
bytes State::compileBlock(BlockChain const& _bc)
void State::prepareToMine(BlockChain const& _bc)
{
RLPStream uncles;
if (m_previousBlock != BlockInfo::genesis())
@ -226,78 +232,126 @@ bytes State::compileBlock(BlockChain const& _bc)
m_currentBlock.sha3Transactions = sha3(m_currentTxs);
m_currentBlock.sha3Uncles = sha3(m_currentUncles);
RLPStream ret;
ret.appendList(3);
m_currentBlock.fillStream(ret, true);
ret.appendRaw(m_currentTxs);
ret.appendRaw(m_currentUncles);
return ret.out();
}
h256 State::rootHash() const
bool State::mine(uint _msTimeout)
{
// TODO!
return h256();
}
// Update timestamp according to clock.
m_currentBlock.timestamp = time(0);
// Update difficulty according to timestamp.
m_currentBlock.difficulty = m_currentBlock.calculateDifficulty(m_previousBlock);
// TODO: Miner class that keeps dagger between mine calls (or just non-polling mining).
Dagger d(m_currentBlock.headerHashWithoutNonce());
m_currentBlock.nonce = d.search(_msTimeout, m_currentBlock.difficulty);
if (m_currentBlock.nonce)
{
// Got it! Compile block:
RLPStream ret;
ret.appendList(3);
m_currentBlock.fillStream(ret, true);
ret.appendRaw(m_currentTxs);
ret.appendRaw(m_currentUncles);
ret.swapOut(m_currentBytes);
return true;
}
bool State::mine(uint _msTimeout) const
{
// TODO: update timestamp according to clock.
// TODO: update difficulty according to timestamp.
// TODO: look for a nonce that makes a good hash.
// ...but don't take longer than _msTimeout ms.
return false;
}
bool State::isNormalAddress(Address _address) const
bool State::isNormalAddress(Address _id) const
{
auto it = m_current.find(_address);
return it != m_current.end() && it->second.type() == AddressType::Normal;
return RLP(m_state[_id]).itemCount() == 2;
}
bool State::isContractAddress(Address _address) const
bool State::isContractAddress(Address _id) const
{
auto it = m_current.find(_address);
return it != m_current.end() && it->second.type() == AddressType::Contract;
return RLP(m_state[_id]).itemCount() == 3;
}
u256 State::balance(Address _id) const
{
auto it = m_current.find(_id);
return it == m_current.end() ? 0 : it->second.balance();
RLP rlp(m_state[_id]);
if (rlp.isList())
return rlp[0].toInt<u256>();
else
return 0;
}
void State::noteSending(Address _id)
{
RLP rlp(m_state[_id]);
if (rlp.isList())
if (rlp.itemCount() == 2)
m_state.insert(_id, rlpList(rlp[0], rlp[1].toInt<u256>() + 1));
else
m_state.insert(_id, rlpList(rlp[0], rlp[1].toInt<u256>() + 1, rlp[2]));
else
m_state.insert(_id, rlpList(0, 1));
}
void State::addBalance(Address _id, u256 _amount)
{
auto it = m_current.find(_id);
if (it == m_current.end())
it->second.balance() = _amount;
RLP rlp(m_state[_id]);
if (rlp.isList())
if (rlp.itemCount() == 2)
m_state.insert(_id, rlpList(rlp[0].toInt<u256>() + _amount, rlp[1]));
else
m_state.insert(_id, rlpList(rlp[0].toInt<u256>() + _amount, rlp[1], rlp[2]));
else
it->second.balance() += _amount;
m_state.insert(_id, rlpList(_amount, 0));
}
void State::subBalance(Address _id, bigint _amount)
{
auto it = m_current.find(_id);
if (it == m_current.end() || (bigint)it->second.balance() < _amount)
RLP rlp(m_state[_id]);
if (rlp.isList())
{
bigint bal = rlp[0].toInt<u256>();
if (bal < _amount)
throw NotEnoughCash();
bal -= _amount;
if (rlp.itemCount() == 2)
m_state.insert(_id, rlpList(bal, rlp[1]));
else
m_state.insert(_id, rlpList(bal, rlp[1], rlp[2]));
}
else
throw NotEnoughCash();
it->second.balance() = (u256)((bigint)it->second.balance() - _amount);
}
u256 State::transactionsFrom(Address _address) const
u256 State::transactionsFrom(Address _id) const
{
RLP rlp(m_state[_id]);
if (rlp.isList())
return rlp[0].toInt<u256>(RLP::LaisezFaire);
else
return 0;
}
u256 State::contractMemory(Address _id, u256 _memory) const
{
auto it = m_current.find(_address);
return it == m_current.end() ? 0 : it->second.nonce();
RLP rlp(m_state[_id]);
if (rlp.itemCount() != 3)
throw InvalidContractAddress();
return fromBigEndian<u256>(TrieDB<h256>(m_db, rlp[2].toHash<h256>(), (std::map<h256, std::string>*)&m_over)[_memory]);
}
u256 State::contractMemory(Address _contract, u256 _memory) const
void State::setContractMemory(Address _contract, u256 _memory, u256 _value)
{
auto m = m_current.find(_contract);
if (m == m_current.end())
return 0;
auto i = m->second.memory().find(_memory);
return i == m->second.memory().end() ? 0 : i->second;
RLP rlp(m_state[_contract]);
TrieDB<h256> c(m_db, &m_over);
std::string s = toBigEndianString(_value);
if (rlp.itemCount() == 3)
{
c.setRoot(rlp[2].toHash<h256>());
c.insert(_memory, bytesConstRef(s));
m_state.insert(_contract, rlpList(rlp[0], rlp[1], c.root()));
}
else
throw InvalidContractAddress();
}
bool State::execute(bytesConstRef _rlp)
@ -346,6 +400,9 @@ void State::execute(Transaction const& _t, Address _sender)
if (balance(_sender) < _t.value + _t.fee)
throw NotEnoughCash();
// Increment associated nonce for sender.
noteSending(_sender);
if (_t.receiveAddress)
{
subBalance(_sender, _t.value + _t.fee);
@ -368,9 +425,8 @@ void State::execute(Transaction const& _t, Address _sender)
if (isContractAddress(newAddress))
throw ContractAddressCollision();
auto& mem = m_current[newAddress].memory();
for (uint i = 0; i < _t.data.size(); ++i)
mem[i] = _t.data[i];
setContractMemory(newAddress, i, _t.data[i]);
subBalance(_sender, _t.value + _t.fee);
addBalance(newAddress, _t.value);
addBalance(m_currentBlock.coinbaseAddress, _t.fee);
@ -381,12 +437,6 @@ void State::execute(Address _myAddress, Address _txSender, u256 _txValue, u256 _
{
std::vector<u256> stack;
// Find our memory.
auto m = m_current.find(_myAddress);
if (m == m_current.end())
throw NoSuchContract();
auto& myMemory = m->second.memory();
// Set up some local functions.
auto require = [&](u256 _n)
{
@ -395,15 +445,17 @@ void State::execute(Address _myAddress, Address _txSender, u256 _txValue, u256 _
};
auto mem = [&](u256 _n) -> u256
{
auto i = myMemory.find(_n);
return i == myMemory.end() ? 0 : i->second;
return contractMemory(_myAddress, _n);
// auto i = myMemory.find(_n);
// return i == myMemory.end() ? 0 : i->second;
};
auto setMem = [&](u256 _n, u256 _v)
{
if (_v)
setContractMemory(_myAddress, _n, _v);
/* if (_v)
myMemory[_n] = _v;
else
myMemory.erase(_n);
myMemory.erase(_n);*/
};
u256 curPC = 0;
@ -868,9 +920,10 @@ void State::execute(Address _myAddress, Address _txSender, u256 _txValue, u256 _
{
require(1);
Address dest = left160(stack.back());
u256 minusVoidFee = m_current[_myAddress].memory().size() * c_memoryFee;
// TODO: easy once we have the local cache of memory in place.
u256 minusVoidFee = 0;//m_current[_myAddress].memory().size() * c_memoryFee;
addBalance(dest, balance(_myAddress) + minusVoidFee);
m_current.erase(_myAddress);
m_state.remove(_myAddress);
// ...follow through to...
}
case Instruction::STOP:

35
libethereum/State.h

@ -31,6 +31,7 @@
#include "BlockInfo.h"
#include "AddressState.h"
#include "Transaction.h"
#include "Trie.h"
namespace eth
{
@ -47,15 +48,18 @@ class State
{
public:
/// Construct null state object.
State() {}
// State() {}
/// Construct state object.
explicit State(Address _coinbaseAddress);
/// Compiles uncles and transactions list, and puts hashes into the current block header.
void prepareToMine(BlockChain const& _bc);
/// Attempt to find valid nonce for block that this state represents.
/// @param _msTimeout Timeout before return in milliseconds.
/// @returns true if it got lucky.
bool mine(uint _msTimeout = 1000) const;
bool mine(uint _msTimeout = 1000);
/// Get the complete current block, including valid nonce.
bytes const& blockData() const { return m_currentBytes; }
@ -98,12 +102,21 @@ public:
/// @returns 0 if no contract exists at that address.
u256 contractMemory(Address _contract, u256 _memory) const;
/// Set the value of a memory position of a contract.
void setContractMemory(Address _contract, u256 _memory, u256 _value);
/// Get the full memory of a contract.
// std::map<u256, u256> contractMemory(Address _contract) const;
/// Note that the given address is sending a transaction and thus increment the associated ticker.
void noteSending(Address _id);
/// Get the number of transactions a particular address has sent (used for the transaction nonce).
/// @returns 0 if the address has never been used.
u256 transactionsFrom(Address _address) const;
/// The hash of the root of our state tree.
h256 rootHash() const;
h256 rootHash() const { return m_state.root(); }
/// Finalise the block, applying the earned rewards.
void applyRewards(Addresses const& _uncleAddresses);
@ -114,8 +127,6 @@ public:
/// This might throw.
u256 playback(bytesConstRef _block, BlockInfo const& _bi, BlockInfo const& _parent, BlockInfo const& _grandParent);
bytes compileBlock(BlockChain const& _bc);
private:
/// Fee-adder on destruction RAII class.
struct MinerFeeAdder
@ -140,8 +151,16 @@ private:
/// Execute a contract transaction.
void execute(Address _myAddress, Address _txSender, u256 _txValue, u256 _txFee, u256s const& _txData, u256* o_totalFee);
// TODO: move over to Trie in leveldb; specify a snapshot for .
std::map<Address, AddressState> m_current; ///< The current state. We work with a C++ hash map rather than a Trie.
/// Sets m_currentBlock to a clean state, (i.e. no change from m_previousBlock).
void resetCurrent();
void mergeOverlay() { for (auto const& i: m_over) m_db->Put(m_writeOptions, ldb::Slice((char const*)i.first.data(), i.first.size), ldb::Slice(i.second.data(), i.second.size())); m_over.clear(); }
void dropOverlay() { m_over.clear(); }
ldb::DB* m_db; ///< The DB, holding all of our Tries' backend data.
ldb::WriteOptions m_writeOptions;
std::map<h256, std::string> m_over; ///< The current overlay onto the state DB.
TrieDB<Address> m_state; ///< Our state tree.
std::map<h256, Transaction> m_transactions; ///< The current list of transactions that we've included in the state.
BlockInfo m_previousBlock; ///< The previous block's information.
@ -152,7 +171,7 @@ private:
bytes m_currentTxs;
bytes m_currentUncles;
Address m_ourAddress; ///< Our address (i.e. the address to which fees go).
Address m_ourAddress; ///< Our address (i.e. the address to which fees go).
/// The fee structure. Values yet to be agreed on...
static const u256 c_stepFee;

4
libethereum/Transaction.h

@ -30,8 +30,8 @@ namespace eth
struct Signature
{
byte v;
u256 r;
u256 s;
h256 r;
h256 s;
};
// [ nonce, receiving_address, value, fee, [ data item 0, data item 1 ... data item n ], v, r, s ]

2
libethereum/Trie.cpp

@ -665,4 +665,6 @@ void Trie::remove(std::string const& _key)
}
}
h256 const GenericTrieDB::c_null = sha3(RLPNull);
}

56
libethereum/Trie.h

@ -22,7 +22,9 @@
#pragma once
#include <map>
#include <leveldb/db.h>
#include "RLP.h"
namespace ldb = leveldb;
namespace eth
{
@ -56,6 +58,60 @@ private:
TrieNode* m_root;
};
/**
* @brief Merkle Patricia Tree "Trie": a modifed base-16 Radix tree.
* This version uses an LDB backend - TODO: split off m_db & m_over into opaque key/value map layer and allow caching & testing without DB.
* TODO: Implement!
*/
class GenericTrieDB
{
public:
GenericTrieDB() {}
GenericTrieDB(ldb::DB* _db, std::map<h256, std::string>* _overlay = nullptr): GenericTrieDB() { open(_db, c_null, _overlay); }
GenericTrieDB(ldb::DB* _db, h256 _root, std::map<h256, std::string>* _overlay = nullptr): GenericTrieDB() { open(_db, _root, _overlay); }
~GenericTrieDB() {}
void open(ldb::DB* _db, h256 _root, std::map<h256, std::string>* _overlay = nullptr) { m_root = _root; m_db = _db; m_over = _overlay; }
void setRoot(h256 _root) { m_root = _root; }
h256 root() const { return m_root; }
void debugPrint() {}
std::string at(bytesConstRef _key) const { return std::string(); }
void insert(bytesConstRef _key, bytesConstRef _value) {}
void remove(bytesConstRef _key) {}
// TODO: iterators.
private:
std::string node(h256 _h) const { if (_h == c_null) return std::string(); if (m_over) { auto it = m_over->find(_h); if (it != m_over->end()) return it->second; } std::string ret; m_db->Get(m_readOptions, ldb::Slice((char const*)&m_root, 32), &ret); return ret; }
static const h256 c_null;
h256 m_root = c_null;
ldb::DB* m_db = nullptr;
std::map<h256, std::string>* m_over = nullptr;
ldb::ReadOptions m_readOptions;
};
template <class KeyType>
class TrieDB: public GenericTrieDB
{
public:
TrieDB() {}
TrieDB(ldb::DB* _db, std::map<h256, std::string>* _overlay = nullptr): GenericTrieDB(_db, _overlay) {}
TrieDB(ldb::DB* _db, h256 _root, std::map<h256, std::string>* _overlay = nullptr): GenericTrieDB() { open(_db, _root, _overlay); }
std::string operator[](KeyType _k) const { return at(_k); }
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))); }
};
}

2
libethereum/vector_ref.h

@ -20,7 +20,7 @@ public:
vector_ref(_T* _data, unsigned _count): m_data(_data), m_count(_count) {}
vector_ref(std::string* _data): m_data((_T*)_data->data()), m_count(_data->size() / sizeof(_T)) {}
vector_ref(typename std::conditional<std::is_const<_T>::value, std::vector<typename std::remove_const<_T>::type> const*, std::vector<_T>*>::type _data): m_data(_data->data()), m_count(_data->size()) {}
vector_ref(typename std::conditional<std::is_const<_T>::value, std::string const&, std::string&>::type _data): m_data((_T*)_data->data()), m_count(_data->size() / sizeof(_T)) {}
vector_ref(typename std::conditional<std::is_const<_T>::value, std::string const&, std::string&>::type _data): m_data((_T*)_data.data()), m_count(_data.size() / sizeof(_T)) {}
vector_ref(leveldb::Slice const& _s): m_data(_s.data()), m_count(_s.size() / sizeof(_T)) {}
explicit operator bool() const { return m_data && m_count; }

Loading…
Cancel
Save