2019-11-29 14:42:17 +02:00
# include "BTC.h"
2019-12-08 02:36:46 +02:00
# include "LRUCache.h"
2019-12-11 11:32:46 +02:00
# include "RecordFile.h"
2019-11-29 14:42:17 +02:00
# include "Storage.h"
# include "rocksdb/db.h"
2019-12-08 01:00:40 +02:00
# include "rocksdb/iterator.h"
2019-12-12 00:07:29 +02:00
# include "rocksdb/merge_operator.h"
2019-11-29 14:42:17 +02:00
# include "rocksdb/options.h"
# include "rocksdb/slice.h"
# include <QByteArray>
# include <QDir>
# include <atomic>
2019-12-12 00:07:29 +02:00
# include <cstring> // for memcpy
2019-12-07 20:34:19 +02:00
# include <list>
2019-12-03 15:41:09 +02:00
# include <optional>
2019-11-29 14:42:17 +02:00
# include <shared_mutex>
2019-12-12 00:07:29 +02:00
# include <string>
2019-12-07 20:34:19 +02:00
# include <tuple>
2019-11-29 14:42:17 +02:00
# include <type_traits>
2019-12-14 00:47:11 +02:00
# include <utility>
2019-11-29 14:42:17 +02:00
# include <vector>
2019-12-03 15:41:09 +02:00
DatabaseError : : ~ DatabaseError ( ) { } // weak vtable warning suppression
DatabaseSerializationError : : ~ DatabaseSerializationError ( ) { } // weak vtable warning suppression
DatabaseFormatError : : ~ DatabaseFormatError ( ) { } // weak vtable warning suppression
2019-12-09 12:10:59 +02:00
DatabaseKeyNotFound : : ~ DatabaseKeyNotFound ( ) { } // weak vtable warning suppression
2019-11-29 14:42:17 +02:00
namespace {
/// Encapsulates the 'meta' db table
struct Meta {
uint32_t magic = 0xf33db33f , version = 0x1 ;
2019-11-30 01:33:27 +02:00
QString chain ; ///< "test", "main", etc
2019-12-14 09:26:20 +02:00
uint16_t platformBits = sizeof ( long ) * 8U ; ///< we save the platform wordsize to the db
2019-11-29 14:42:17 +02:00
} ;
// some database keys we use -- todo: if this grows large, move it elsewhere
2019-12-11 11:32:46 +02:00
static const rocksdb : : Slice kMeta { " meta " } , kNumHeaders { " num_headers " } ;
2019-11-29 14:42:17 +02:00
static constexpr size_t MAX_HEADERS = 100000000 ; // 100 mln max headers for now.
2019-11-29 17:11:23 +02:00
// serialize/deser -- for basic types we use QDataStream, but we also have specializations at the end of this file
template < typename Type >
QByteArray Serialize ( const Type & n ) {
2019-11-29 14:42:17 +02:00
QByteArray ba ;
2019-12-11 18:25:24 +02:00
if constexpr ( std : : is_base_of_v < QByteArray , Type > ) {
2019-12-09 13:42:11 +02:00
ba = n ;
} else {
QDataStream ds ( & ba , QIODevice : : WriteOnly | QIODevice : : Truncate ) ;
ds < < n ;
}
2019-11-29 14:42:17 +02:00
return ba ;
}
2019-11-29 17:11:23 +02:00
template < typename Type >
Type Deserialize ( const QByteArray & ba , bool * ok = nullptr ) {
Type ret { } ;
2019-12-11 18:25:24 +02:00
if constexpr ( std : : is_base_of_v < QByteArray , Type > ) {
2019-12-09 13:42:11 +02:00
ret = ba ;
} else {
QDataStream ds ( ba ) ;
ds > > ret ;
if ( ok )
* ok = ds . status ( ) = = QDataStream : : Status : : Ok ;
}
2019-11-29 14:42:17 +02:00
return ret ;
}
2019-12-14 12:21:31 +02:00
/// Return a shallow, temporary copy of the memory of an object as a QByteArray. This reduces typing of
/// the boilerplate: "QByteArray::FromRawData(reinterpret_cast...." etc everywhere in this file.
/// Note: It is unsafe to use this function for anything other than obtaining a weak reference to the memory of an
/// object as a QByteArray for temporary purposes. The original object must live at least as long as this returned
/// QByteArray. Note that even copy-constructing a new QByteArray from this returned QByteArray will lead to
/// dangling pointers. See: https://doc.qt.io/qt-5/qbytearray.html#fromRawData.
template < typename Object ,
std : : enable_if_t < ! std : : is_pointer_v < std : : remove_cv_t < Object > > , int > = 0 >
QByteArray ShallowTmp ( const Object * mem , size_t size = sizeof ( Object ) ) {
return QByteArray : : fromRawData ( reinterpret_cast < const char * > ( mem ) , int ( size ) ) ;
}
/// Construct a QByteArray from a deep copy of any object's memory area. Slower than ShallowTmp above but 100% safe
/// to use after the original object expires since the returned QByteArray takes ownership of its private copy of
/// the memory it allocated.
template < typename Object ,
std : : enable_if_t < ! std : : is_pointer_v < std : : remove_cv_t < Object > > , int > = 0 >
QByteArray DeepCpy ( const Object * mem , size_t size = sizeof ( Object ) ) {
return QByteArray ( reinterpret_cast < const char * > ( mem ) , int ( size ) ) ;
}
2019-12-03 06:38:19 +02:00
/// Serialize a simple value such as an int directly, without using the space overhead that QDataStream imposes.
/// This is less safe but is more compact since the bytes of the passed-in value are written directly to the
/// returned QByteArray, without any encapsulation. Note that use of this mechanism makes all data in the database
/// no longer platform-neutral, which is ok. The presumption is users can re-synch their DB if switching
/// architectures.
template < typename Scalar ,
std : : enable_if_t < std : : is_scalar_v < Scalar > & & ! std : : is_pointer_v < Scalar > , int > = 0 >
2019-12-14 12:21:31 +02:00
[[maybe_unused]] QByteArray SerializeScalar ( const Scalar & s ) { return DeepCpy ( & s ) ; }
2019-12-11 08:26:53 +02:00
template < typename Scalar ,
std : : enable_if_t < std : : is_scalar_v < Scalar > & & ! std : : is_pointer_v < Scalar > , int > = 0 >
2019-12-14 12:21:31 +02:00
QByteArray SerializeScalarNoCopy ( const Scalar & s ) { return ShallowTmp ( & s ) ; }
2019-12-03 06:38:19 +02:00
/// Inverse of above. Pass in an optional 'pos' pointer if you wish to continue reading raw scalars from the same
/// QByteArray during subsequent calls to this template function. *ok, if specified, is set to false if we ran off
/// the QByteArray's bounds, and a default-constructed value of 'Scalar' is returned. No other safety checking is
/// done. On successful deserialization of the scalar, *pos (if specified) is updated to point just past the
/// last byte of the successuflly converted item. On failure, *pos is always set to point past the end of the
/// QByteArray.
template < typename Scalar ,
std : : enable_if_t < std : : is_scalar_v < Scalar > & & ! std : : is_pointer_v < Scalar > , int > = 0 >
Scalar DeserializeScalar ( const QByteArray & ba , bool * ok = nullptr , int * pos_out = nullptr ) {
Scalar ret { } ;
int dummy = 0 ;
int & pos = pos_out ? * pos_out : dummy ;
if ( pos > = 0 & & pos + int ( sizeof ( ret ) ) < = ba . size ( ) ) {
if ( ok ) * ok = true ;
ret = * reinterpret_cast < const Scalar * > ( ba . data ( ) + pos ) ;
pos + = sizeof ( ret ) ;
} else {
if ( ok ) * ok = false ;
pos = ba . size ( ) ;
}
return ret ;
}
2019-11-29 14:42:17 +02:00
// specializations
template < > QByteArray Serialize ( const Meta & ) ;
template < > Meta Deserialize ( const QByteArray & , bool * ) ;
2019-12-09 12:10:59 +02:00
template < > QByteArray Serialize ( const TXO & ) ;
template < > TXO Deserialize ( const QByteArray & , bool * ) ;
2019-12-07 23:30:07 +02:00
template < > QByteArray Serialize ( const TXOInfo & ) ;
template < > TXOInfo Deserialize ( const QByteArray & , bool * ) ;
2019-12-12 16:58:31 +02:00
// TxNumVec
2019-12-12 00:07:29 +02:00
using TxNumVec = std : : vector < TxNum > ;
// this serializes a vector of TxNums to a compact representation (6 bytes, eg 48 bits per TxNum), in little endian byte order
template < > QByteArray Serialize ( const TxNumVec & ) ;
// this deserializes a vector of TxNums from a compact representation (6 bytes, eg 48 bits per TxNum), assuming little endian byte order
template < > TxNumVec Deserialize ( const QByteArray & , bool * ) ;
2019-12-13 02:45:45 +02:00
// CompactTXO -- not currently used since we prefer toBytes() directly (TODO: remove if we end up never using this)
2019-12-12 23:09:54 +02:00
template < > QByteArray Serialize ( const CompactTXO & ) ;
template < > CompactTXO Deserialize ( const QByteArray & , bool * ) ;
2019-12-11 18:25:24 +02:00
/// NOTE: The slice should live as long as the returned QByteArray does. The QByteArray is a weak pointer into the slice!
2019-12-14 12:21:31 +02:00
inline QByteArray FromSlice ( const rocksdb : : Slice & s ) { return ShallowTmp ( s . data ( ) , s . size ( ) ) ; }
2019-12-11 18:25:24 +02:00
/// Generic conversion from any type we operate on to a rocksdb::Slice. Note that the type in question should have
/// a conversion function written (eg Serialize) if it is anything other than a QByteArray or a scalar.
template < bool safeScalar = false , typename Thing >
auto ToSlice ( const Thing & thing ) {
if constexpr ( std : : is_base_of_v < rocksdb : : Slice , Thing > ) {
// same type, no-op, return ref to thing (const Slice &)
return static_cast < const rocksdb : : Slice & > ( thing ) ;
} else if constexpr ( std : : is_base_of_v < QByteArray , Thing > ) {
// QByteArray conversion, return reference to data in QByteArray
return rocksdb : : Slice ( thing . constData ( ) , size_t ( thing . size ( ) ) ) ;
} else if constexpr ( ! safeScalar & & std : : is_scalar_v < Thing > & & ! std : : is_pointer_v < Thing > ) {
2019-12-11 18:39:19 +02:00
return rocksdb : : Slice ( reinterpret_cast < const char * > ( & thing ) , sizeof ( thing ) ) ; // returned slice points to raw scalar memory itself
2019-12-11 18:25:24 +02:00
} else {
2019-12-11 18:52:33 +02:00
// the purpose of this holder is to keep the temporary QByteArray alive for as long as the slice itself is alive
2019-12-11 18:25:24 +02:00
struct BagOfHolding {
QByteArray bytes ;
rocksdb : : Slice slice ;
operator const rocksdb : : Slice & ( ) const { return slice ; }
2019-12-11 18:52:33 +02:00
} h { Serialize ( thing ) , ToSlice ( h . bytes ) } ;
2019-12-11 18:25:24 +02:00
return h ; // this holder type "acts like" a Slice due to its operator const Slice &()
}
} ;
2019-12-03 15:41:09 +02:00
/// DB read/write helpers
/// NOTE: these may throw DatabaseError
/// If missingOk=false, then the returned optional is guaranteed to have a value if this function returns without throwing.
/// If missingOk=true, then if there was no other database error and the key was not found, the returned optional !has_value()
///
2019-12-09 12:10:59 +02:00
/// Template arg "safeScalar", if true, will deserialize scalar int, float, etc data using the Deserialize<>
2019-12-03 15:41:09 +02:00
/// function (uses QDataStream, is platform neutral, but is slightly slower). If false, we will use the
/// DeserializeScalar<> fast function for scalars such as ints. It's important to read from the DB in the same
/// 'safeScalar' mode as was written!
2019-12-11 18:25:24 +02:00
template < typename RetType , bool safeScalar = false , typename KeyType >
std : : optional < RetType > GenericDBGet ( rocksdb : : DB * db , const KeyType & keyIn , bool missingOk = false ,
2019-12-03 15:41:09 +02:00
const QString & errorMsgPrefix = QString ( ) , ///< used to specify a custom error message in the thrown exception
2019-12-08 02:36:46 +02:00
bool acceptExtraBytesAtEndOfData = false ,
const rocksdb : : ReadOptions & ropts = rocksdb : : ReadOptions ( ) ) ///< if true, we are ok with extra unparsed bytes in data. otherwise we throw. (this check is only done for !safeScalar mode on basic types)
2019-12-03 13:57:02 +02:00
{
2019-12-03 15:41:09 +02:00
rocksdb : : PinnableSlice datum ;
2019-12-03 13:57:02 +02:00
std : : optional < RetType > ret ;
2019-12-03 15:41:09 +02:00
if ( UNLIKELY ( ! db ) ) throw InternalError ( " GenericDBGet was passed a null pointer! " ) ;
2019-12-11 18:25:24 +02:00
const auto status = db - > Get ( ropts , db - > DefaultColumnFamily ( ) , ToSlice < safeScalar > ( keyIn ) , & datum ) ;
2019-12-09 12:10:59 +02:00
if ( status . IsNotFound ( ) ) {
if ( missingOk )
return ret ; // optional will not has_value() to indicate missing key
throw DatabaseKeyNotFound ( QString ( " %1: %2 " )
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : " Key not found in db " )
. arg ( QString : : fromStdString ( status . ToString ( ) ) ) ) ;
2019-12-03 13:57:02 +02:00
} else if ( ! status . ok ( ) ) {
throw DatabaseError ( QString ( " %1: %2 " )
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : " Error reading a key from the db " )
2019-12-09 12:10:59 +02:00
. arg ( QString : : fromStdString ( status . ToString ( ) ) ) ) ;
2019-12-03 13:57:02 +02:00
} else {
// ok status
2019-12-03 15:41:09 +02:00
if constexpr ( std : : is_base_of_v < QByteArray , std : : remove_cv_t < RetType > > ) {
2019-12-03 16:02:53 +02:00
static_assert ( ! safeScalar , " safeScalar=true mode is not supported for QByteArrays (it only is useful for scalar types) " ) ;
2019-12-03 15:41:09 +02:00
// special compile-time case for QByteArray subclasses -- return a deep copy of the data bytes directly.
2019-12-03 15:59:36 +02:00
// TODO: figure out a way to do this without the 1 extra copy! (PinnableSlice -> ret).
2019-12-03 19:09:38 +02:00
ret . emplace ( reinterpret_cast < const char * > ( datum . data ( ) ) , QByteArray : : size_type ( datum . size ( ) ) ) ;
2019-12-03 15:41:09 +02:00
} else if constexpr ( ! safeScalar & & std : : is_scalar_v < RetType > & & ! std : : is_pointer_v < RetType > ) {
if ( ! acceptExtraBytesAtEndOfData & & datum . size ( ) > sizeof ( RetType ) ) {
// reject extra stuff at end of data stream
throw DatabaseFormatError ( QString ( " %1: Extra bytes at the end of data " )
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : " Database format error " ) ) ;
}
2019-12-03 13:57:02 +02:00
bool ok ;
2019-12-03 19:09:38 +02:00
ret . emplace ( DeserializeScalar < RetType > ( FromSlice ( datum ) , & ok ) ) ;
2019-12-03 13:57:02 +02:00
if ( ! ok ) {
2019-12-03 15:41:09 +02:00
throw DatabaseSerializationError (
QString ( " %1: Key was retrieved ok, but data could not be deserialized as a scalar '%2' " )
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : " Error deserializing a scalar from db " )
. arg ( typeid ( RetType ) . name ( ) ) ) ;
2019-12-03 13:57:02 +02:00
}
} else {
2019-12-03 19:09:38 +02:00
if ( UNLIKELY ( acceptExtraBytesAtEndOfData ) )
Debug ( ) < < " Warning: Caller misuse of function ' " < < __FUNCTION__
< < " '. 'acceptExtraBytesAtEndOfData=true' is ignored when deserializing using QDataStream. " ;
2019-12-03 13:57:02 +02:00
bool ok ;
2019-12-03 19:09:38 +02:00
ret . emplace ( Deserialize < RetType > ( FromSlice ( datum ) , & ok ) ) ;
2019-12-03 13:57:02 +02:00
if ( ! ok ) {
2019-12-03 15:41:09 +02:00
throw DatabaseSerializationError (
QString ( " %1: Key was retrieved ok, but data could not be deserialized " )
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : " Error deserializing an object from db " ) ) ;
2019-12-03 13:57:02 +02:00
}
}
}
return ret ;
}
2019-12-11 18:25:24 +02:00
2019-12-03 15:41:09 +02:00
/// Conveneience for above with the missingOk flag set to false. Will always throw or return a real value.
2019-12-11 18:25:24 +02:00
template < typename RetType , bool safeScalar = false , typename KeyType >
RetType GenericDBGetFailIfMissing ( rocksdb : : DB * db , const KeyType & k , const QString & errMsgPrefix = QString ( ) , bool extraDataOk = false ,
2019-12-08 02:36:46 +02:00
const rocksdb : : ReadOptions & ropts = rocksdb : : ReadOptions ( ) )
2019-12-03 15:41:09 +02:00
{
2019-12-08 02:36:46 +02:00
return GenericDBGet < RetType , safeScalar > ( db , k , false , errMsgPrefix , extraDataOk , ropts ) . value ( ) ;
2019-12-03 15:41:09 +02:00
}
2019-12-07 20:34:19 +02:00
2019-12-09 13:42:11 +02:00
/// Throws on all errors. Otherwise writes to db.
template < bool safeScalar = false , typename KeyType , typename ValueType >
2019-12-11 18:25:24 +02:00
void GenericDBPut
2019-12-11 11:32:46 +02:00
( rocksdb : : DB * db , const KeyType & key , const ValueType & value ,
const QString & errorMsgPrefix = QString ( ) , ///< used to specify a custom error message in the thrown exception
const rocksdb : : WriteOptions & opts = rocksdb : : WriteOptions ( ) )
2019-12-09 13:42:11 +02:00
{
2019-12-11 18:25:24 +02:00
auto st = db - > Put ( opts , ToSlice < safeScalar > ( key ) , ToSlice < safeScalar > ( value ) ) ;
if ( ! st . ok ( ) )
throw DatabaseError ( QString ( " %1: %2 " )
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : " Error writing object to db " )
. arg ( QString : : fromStdString ( st . ToString ( ) ) ) ) ;
2019-12-09 13:42:11 +02:00
}
2019-12-15 09:08:31 +02:00
/// Throws on all errors. Otherwise deletes a key from db. It is not an error to delete a non-existing key.
template < bool safeScalar = false , typename KeyType >
void GenericDBDelete
( rocksdb : : DB * db , const KeyType & key ,
const QString & errorMsgPrefix = QString ( ) , ///< used to specify a custom error message in the thrown exception
const rocksdb : : WriteOptions & opts = rocksdb : : WriteOptions ( ) )
{
auto st = db - > Delete ( opts , ToSlice < safeScalar > ( key ) ) ;
if ( ! st . ok ( ) )
throw DatabaseError ( QString ( " %1: %2 " )
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : " Error deleting a key from db " )
. arg ( QString : : fromStdString ( st . ToString ( ) ) ) ) ;
}
2019-12-09 13:42:11 +02:00
2019-12-11 19:57:01 +02:00
//// A helper data structs -- written to the blkinfo table. This helps localize a txnum to a specific position in
/// a block. The table is keyed off of block_height(uint32_t) -> serialized BlkInfo (raw bytes)
2019-12-07 20:34:19 +02:00
struct BlkInfo {
TxNum txNum0 = 0 ;
2019-12-11 18:25:24 +02:00
unsigned nTx = 0 ;
2019-12-12 12:07:18 +02:00
BlkInfo ( ) = default ;
BlkInfo ( const BlkInfo & ) = default ;
2019-12-12 13:20:18 +02:00
[[maybe_unused]] BlkInfo ( TxNum txn , unsigned ntx ) : txNum0 ( txn ) , nTx ( ntx ) { }
[[maybe_unused]] bool operator = = ( const BlkInfo & o ) const { return txNum0 = = o . txNum0 & & nTx = = o . nTx ; }
[[maybe_unused]] bool operator < ( const BlkInfo & o ) const { return txNum0 = = o . txNum0 ? nTx < o . nTx : txNum0 < o . txNum0 ; }
2019-12-12 12:07:18 +02:00
BlkInfo & operator = ( const BlkInfo & ) = default ;
2019-12-07 20:34:19 +02:00
} ;
2019-12-11 19:57:01 +02:00
// serializes as raw bytes from struct
template < > QByteArray Serialize ( const BlkInfo & ) ;
// deserializes as raw bytes from struct
template < > BlkInfo Deserialize ( const QByteArray & , bool * ) ;
2019-12-14 00:47:11 +02:00
/// Block rewind/undo information. One of these is kept around in the db for the last 10 blocks.
/// It basically stores a record of all the UTXO's added and removed, as well as the set of
/// scripthashes.
struct UndoInfo {
using ScriptHashSet = std : : unordered_set < HashX , HashHasher > ;
using UTXOAddUndo = std : : tuple < TXO , HashX , CompactTXO > ;
using UTXODelUndo = std : : tuple < TXO , TXOInfo > ;
2019-12-14 11:42:26 +02:00
BlockHeight height = 0 ; ///< we save a copy of this infomation as a sanity check
2019-12-14 00:47:11 +02:00
BlockHash hash ; ///< we save a copy of this information as a sanity check. (bytes are in "reversed", bitcoind ToHex()-style memory order)
BlkInfo blkInfo ; ///< we save a copy of this from the global value for convenience and as a sanity check.
// below is the actual critical undo information
ScriptHashSet scriptHashes ;
std : : vector < UTXOAddUndo > addUndos ;
std : : vector < UTXODelUndo > delUndos ;
[[maybe_unused]] QString toDebugString ( ) const ;
2019-12-14 11:42:26 +02:00
[[maybe_unused]] bool operator = = ( const UndoInfo & ) const ; // for debug ser/deser
bool isValid ( ) const { return hash . size ( ) = = HashLen ; } ///< cheap, imperfect check for validity
void clear ( ) { height = 0 ; hash . clear ( ) ; blkInfo = BlkInfo ( ) ; scriptHashes . clear ( ) ; addUndos . clear ( ) ; delUndos . clear ( ) ; }
2019-12-14 00:47:11 +02:00
} ;
QString UndoInfo : : toDebugString ( ) const {
QString ret ;
QTextStream ts ( & ret ) ;
ts < < " <Undo info for height: " < < height < < " addUndos: " < < addUndos . size ( ) < < " delUndos: " < < delUndos . size ( )
< < " scriptHashes: " < < scriptHashes . size ( ) < < " nTx: " < < blkInfo . nTx < < " txNum0: " < < blkInfo . txNum0
< < " hash: " < < hash . toHex ( ) < < " > " ;
return ret ;
}
2019-12-14 11:42:26 +02:00
bool UndoInfo : : operator = = ( const UndoInfo & o ) const {
return height = = o . height & & hash = = o . hash & & blkInfo = = o . blkInfo & & scriptHashes = = o . scriptHashes
& & addUndos = = o . addUndos & & delUndos = = o . delUndos ;
}
// serialize as raw bytes mostly (no QDataStream)
template < > QByteArray Serialize ( const UndoInfo & ) ;
// serialize from raw bytes mostly (no QDataStream)
template < > UndoInfo Deserialize ( const QByteArray & , bool * ) ;
2019-12-14 00:47:11 +02:00
2019-12-12 20:38:56 +02:00
/// Associative merge operator used for scripthash history concatenation
/// TODO: this needs to be made more efficient by implementing the real MergeOperator interface and combining
/// appends efficiently to reduce allocations. Right now it's called for each append.
2019-12-12 00:07:29 +02:00
class ConcatOperator : public rocksdb : : AssociativeMergeOperator {
public :
~ ConcatOperator ( ) override ;
2019-12-12 01:28:27 +02:00
mutable std : : atomic < unsigned > merges = 0 ;
2019-12-12 00:23:19 +02:00
2019-12-12 00:07:29 +02:00
// Gives the client a way to express the read -> modify -> write semantics
// key: (IN) The key that's associated with this merge operation.
// existing_value:(IN) null indicates the key does not exist before this op
// value: (IN) the value to update/merge the existing_value with
// new_value: (OUT) Client is responsible for filling the merge result
// here. The string that new_value is pointing to will be empty.
// logger: (IN) Client could use this to log errors during merge.
//
// Return true on success.
// All values passed in will be client-specific values. So if this method
// returns false, it is because client specified bad data or there was
// internal corruption. The client should assume that this will be treated
// as an error by the library.
bool Merge ( const rocksdb : : Slice & key , const rocksdb : : Slice * existing_value ,
const rocksdb : : Slice & value , std : : string * new_value ,
rocksdb : : Logger * logger ) const override ;
const char * Name ( ) const override { return " ConcatOperator " ; /* NOTE: this must be the same for the same db each time it is opened! */ }
} ;
2019-12-14 00:47:11 +02:00
ConcatOperator : : ~ ConcatOperator ( ) { } // weak vtable warning prevention
2019-12-12 00:07:29 +02:00
2019-12-14 00:47:11 +02:00
bool ConcatOperator : : Merge ( const rocksdb : : Slice & key , const rocksdb : : Slice * existing_value ,
const rocksdb : : Slice & value , std : : string * new_value , rocksdb : : Logger * logger ) const
{
( void ) key ; ( void ) logger ;
+ + merges ;
new_value - > resize ( ( existing_value ? existing_value - > size ( ) : 0 ) + value . size ( ) ) ;
char * cur = new_value - > data ( ) ;
if ( existing_value ) {
std : : memcpy ( cur , existing_value - > data ( ) , existing_value - > size ( ) ) ;
cur + = existing_value - > size ( ) ;
}
std : : memcpy ( cur , value . data ( ) , value . size ( ) ) ;
return true ;
2019-12-12 00:07:29 +02:00
}
2019-12-14 00:47:11 +02:00
2019-11-29 14:42:17 +02:00
}
2019-12-12 00:07:29 +02:00
2019-11-29 14:42:17 +02:00
struct Storage : : Pvt
{
2019-12-11 00:05:16 +02:00
const int blockHeaderSize = BTC : : GetBlockHeaderSize ( ) ;
2019-11-30 01:33:27 +02:00
Meta meta ;
Lock metaLock ;
2019-11-29 14:42:17 +02:00
std : : atomic < std : : underlying_type_t < SaveItem > > pendingSaves { 0 } ;
struct RocksDBs {
2019-12-09 13:42:11 +02:00
const rocksdb : : ReadOptions defReadOpts ; ///< avoid creating this each time
const rocksdb : : WriteOptions defWriteOpts ; ///< avoid creating this each time
2019-12-13 00:14:09 +02:00
rocksdb : : Options opts , shistOpts ;
2019-12-12 00:07:29 +02:00
2019-12-12 12:07:18 +02:00
std : : shared_ptr < ConcatOperator > concatOperator ;
2019-12-12 20:38:56 +02:00
std : : unique_ptr < rocksdb : : DB > meta , headers , blkinfo , utxoset ,
2019-12-15 09:08:31 +02:00
shist , shunspent , // scripthash_history and scripthash_unspent
undo ; // undo (reorg rewind)
2019-11-29 14:42:17 +02:00
} db ;
2019-11-30 00:42:33 +02:00
2019-12-11 11:32:46 +02:00
std : : unique_ptr < RecordFile > txNumsFile ;
2019-11-30 00:42:33 +02:00
BTC : : HeaderVerifier headerVerifier ;
2019-12-11 00:05:16 +02:00
mutable RWLock headerVerifierLock ;
2019-12-07 20:34:19 +02:00
std : : atomic < TxNum > txNumNext { 0 } ;
2019-12-12 12:07:18 +02:00
std : : vector < BlkInfo > blkInfos ;
std : : map < TxNum , unsigned > blkInfosByTxNum ; ///< ordered map of TxNum0 for a block -> index into above blkInfo array
RWLock blkInfoLock ; ///< locks blkInfos and blkInfosByTxNum
2019-12-09 12:10:59 +02:00
std : : atomic < int64_t > utxoCt = 0 ;
2019-12-08 02:36:46 +02:00
2019-12-12 13:01:31 +02:00
/// TODO: Tune the cahce size. With the below settings it is approx. 50MB-60MB when totally filled
/// (each entry is ~40 bytes + overhead). This cache is anticipated to see heavy use for get_history, so we may
/// wish to make it larger.
static constexpr size_t nCacheMax = 1000000 , nCacheElasticity = 250000 ;
2019-12-08 02:36:46 +02:00
LRU : : Cache < true , TxNum , TxHash > lruNum2Hash { nCacheMax , nCacheElasticity } ;
2019-11-29 14:42:17 +02:00
} ;
Storage : : Storage ( const std : : shared_ptr < Options > & options )
: Mgr ( nullptr ) , options ( options ) , p ( new Pvt )
{
setObjectName ( " Storage " ) ;
_thread . setObjectName ( objectName ( ) ) ;
}
Storage : : ~ Storage ( ) { Debug ( " %s " , __FUNCTION__ ) ; cleanup ( ) ; }
void Storage : : startup ( )
{
2019-12-07 20:34:19 +02:00
{ // open all db's ...
2019-12-13 00:14:09 +02:00
rocksdb : : Options & opts ( p - > db . opts ) , & shistOpts ( p - > db . shistOpts ) ;
2019-12-07 20:34:19 +02:00
// Optimize RocksDB. This is the easiest way to get RocksDB to perform well
2019-12-08 02:36:46 +02:00
opts . IncreaseParallelism ( int ( Util : : getNPhysicalProcessors ( ) ) ) ;
2019-12-07 20:34:19 +02:00
opts . OptimizeLevelStyleCompaction ( ) ;
// create the DB if it's not already present
opts . create_if_missing = true ;
opts . error_if_exists = false ;
2019-12-08 11:45:57 +02:00
//opts.max_open_files = 50; ///< testing -- seems this affects memory usage see: https://github.com/facebook/rocksdb/issues/4112
2019-12-08 05:02:46 +02:00
opts . keep_log_file_num = 5 ; // ??
2019-12-07 20:34:19 +02:00
opts . compression = rocksdb : : CompressionType : : kNoCompression ; // for now we test without compression. TODO: characterize what is fastest and best..
2019-12-12 00:07:29 +02:00
shistOpts = opts ; // copy what we just did
2019-12-12 12:07:18 +02:00
shistOpts . merge_operator = p - > db . concatOperator = std : : make_shared < ConcatOperator > ( ) ; // this set of options uses the concat merge operator (we use this to append to history entries in the db)
2019-12-07 20:34:19 +02:00
2019-12-12 00:07:29 +02:00
using DBInfoTup = std : : tuple < QString , std : : unique_ptr < rocksdb : : DB > & , const rocksdb : : Options & > ;
2019-12-07 20:34:19 +02:00
const std : : list < DBInfoTup > dbs2open = {
2019-12-12 00:07:29 +02:00
{ " meta " , p - > db . meta , opts } ,
{ " headers " , p - > db . headers , opts } ,
{ " blkinfo " , p - > db . blkinfo , opts } ,
{ " utxoset " , p - > db . utxoset , opts } ,
{ " scripthash_history " , p - > db . shist , shistOpts } ,
2019-12-13 00:14:09 +02:00
{ " scripthash_unspent " , p - > db . shunspent , opts } ,
2019-12-15 09:08:31 +02:00
{ " undo " , p - > db . undo , opts } ,
2019-12-07 20:34:19 +02:00
} ;
2019-12-12 00:07:29 +02:00
const auto OpenDB = [ this ] ( const DBInfoTup & tup ) {
auto & [ name , uptr , opts ] = tup ;
2019-12-07 20:34:19 +02:00
rocksdb : : DB * db = nullptr ;
rocksdb : : Status s ;
// try and open database
const QString path = options - > datadir + QDir : : separator ( ) + name ;
2019-12-12 00:07:29 +02:00
s = rocksdb : : DB : : Open ( opts , path . toStdString ( ) , & db ) ;
2019-12-07 20:34:19 +02:00
if ( ! s . ok ( ) | | ! db )
throw DatabaseError ( QString ( " Error opening %1 database: %2 (path: %3) " )
. arg ( name ) . arg ( QString : : fromStdString ( s . ToString ( ) ) ) . arg ( path ) ) ;
uptr . reset ( db ) ;
} ;
// open all db's defined above
for ( auto & tup : dbs2open )
OpenDB ( tup ) ;
} // /open db's
2019-11-29 14:42:17 +02:00
// load/check meta
{
2019-11-30 01:33:27 +02:00
Meta m_db ;
2019-12-03 15:41:09 +02:00
static const QString errMsg { " Incompatible database format -- delete the datadir and resynch. RocksDB error " } ;
if ( auto opt = GenericDBGet < Meta > ( p - > db . meta . get ( ) , kMeta , true , errMsg ) ;
opt . has_value ( ) )
{
m_db = opt . value ( ) ;
2019-12-14 09:26:20 +02:00
if ( m_db . magic ! = p - > meta . magic | | m_db . version ! = p - > meta . version | | m_db . platformBits ! = p - > meta . platformBits ) {
2019-12-03 15:41:09 +02:00
throw DatabaseFormatError ( errMsg ) ;
2019-11-29 14:42:17 +02:00
}
2019-11-30 01:33:27 +02:00
p - > meta = m_db ;
2019-11-29 14:42:17 +02:00
Debug ( ) < < " Read meta from db ok " ;
2019-12-03 15:41:09 +02:00
} else {
// ok, did not exist .. write a new one to db
saveMeta_impl ( ) ;
2019-11-29 14:42:17 +02:00
}
}
Log ( ) < < " Loading database ... " ;
// load headers -- may throw
2019-12-11 00:05:16 +02:00
loadCheckHeadersInDB ( ) ;
2019-12-09 13:42:11 +02:00
// check txnums
2019-12-11 19:57:01 +02:00
loadCheckTxNumsFileAndBlkInfo ( ) ;
2019-12-12 20:38:56 +02:00
// count utxos -- note this depends on "blkInfos" being filled in so it much be called after loadCheckTxNumsFileAndBlkInfo()
loadCheckUTXOsInDB ( ) ;
2019-11-29 14:42:17 +02:00
start ( ) ; // starts our thread
}
void Storage : : cleanup ( )
{
stop ( ) ; // joins our thread
2019-12-11 00:05:16 +02:00
// TODO: unsaved/"dirty state" detection here -- and forced save, if needed.
2019-11-29 14:42:17 +02:00
}
2019-12-08 22:07:01 +02:00
2019-11-29 14:42:17 +02:00
auto Storage : : stats ( ) const - > Stats
{
// TODO ...
2019-12-12 00:23:19 +02:00
QVariantMap ret ;
2019-12-12 12:07:18 +02:00
auto & c = p - > db . concatOperator ;
2019-12-12 00:23:19 +02:00
ret [ " merge calls " ] = c ? c - > merges . load ( ) : QVariant ( ) ;
return ret ;
2019-11-29 14:42:17 +02:00
}
2019-12-11 00:05:16 +02:00
// Keep returned LockGuard in scope while you use the HeaderVerifier
auto Storage : : headerVerifier ( ) - > std : : pair < BTC : : HeaderVerifier & , ExclusiveLockGuard >
2019-11-29 14:42:17 +02:00
{
2019-12-11 00:05:16 +02:00
return std : : pair < BTC : : HeaderVerifier & , ExclusiveLockGuard > ( p - > headerVerifier , p - > headerVerifierLock ) ;
2019-11-29 14:42:17 +02:00
}
2019-12-11 00:05:16 +02:00
auto Storage : : headerVerifier ( ) const - > std : : pair < const BTC : : HeaderVerifier & , SharedLockGuard >
2019-11-29 14:42:17 +02:00
{
2019-12-11 00:05:16 +02:00
return std : : pair < const BTC : : HeaderVerifier & , SharedLockGuard > ( p - > headerVerifier , p - > headerVerifierLock ) ;
2019-11-29 14:42:17 +02:00
}
2019-12-11 00:05:16 +02:00
2019-11-30 00:42:33 +02:00
2019-11-30 01:33:27 +02:00
QString Storage : : getChain ( ) const
{
LockGuard l ( p - > metaLock ) ;
return p - > meta . chain ;
}
void Storage : : setChain ( const QString & chain )
{
{
LockGuard l ( p - > metaLock ) ;
p - > meta . chain = chain ;
}
save ( SaveItem : : Meta ) ;
}
2019-12-07 20:34:19 +02:00
/// returns the "next" TxNum
TxNum Storage : : getTxNum ( ) const { return p - > txNumNext . load ( ) ; }
2019-12-11 00:05:16 +02:00
auto Storage : : latestTip ( ) const - > std : : pair < int , HeaderHash > {
std : : pair < int , HeaderHash > ret = headerVerifier ( ) . first . lastHeaderProcessed ( ) ; // ok; lock stays locked until statement end.
2019-12-07 20:34:19 +02:00
2019-12-11 00:05:16 +02:00
if ( ret . second . isEmpty ( ) | | ret . first < 0 ) {
ret . first = - 1 ;
ret . second . clear ( ) ;
} else {
// .ret now has the actual header but we want the hash
ret . second = BTC : : HashRev ( ret . second ) ;
2019-12-07 15:54:56 +02:00
}
return ret ;
}
2019-11-29 14:42:17 +02:00
void Storage : : save ( SaveSpec typed_spec )
{
using IntType = decltype ( p - > pendingSaves . load ( ) ) ;
// enqueue save on event loop if not previously enqueued (we know it was previously enqueued if the p->pendingSaves
// atomic variable is not 0).
if ( const auto spec = IntType ( typed_spec ) ; ! p - > pendingSaves . fetch_or ( spec ) )
{
2019-12-07 23:30:07 +02:00
QTimer : : singleShot ( 0 , this , [ this ] { save_impl ( ) ; } ) ;
2019-11-29 14:42:17 +02:00
}
}
2019-12-07 23:30:07 +02:00
void Storage : : save_impl ( SaveSpec override )
2019-11-29 14:42:17 +02:00
{
2019-12-07 23:30:07 +02:00
if ( const auto flags = SaveSpec ( p - > pendingSaves . exchange ( 0 ) ) | override ; flags ) { // atomic clear of flags, grab prev val
2019-11-29 17:11:23 +02:00
try {
2019-11-30 01:33:27 +02:00
if ( flags & SaveItem : : Meta ) { // Meta
LockGuard l ( p - > metaLock ) ;
saveMeta_impl ( ) ;
}
2019-11-29 17:11:23 +02:00
} catch ( const std : : exception & e ) {
Fatal ( ) < < e . what ( ) ; // will abort app...
2019-11-29 14:42:17 +02:00
}
}
}
2019-11-30 01:33:27 +02:00
void Storage : : saveMeta_impl ( )
{
if ( ! p - > db . meta ) return ;
2019-12-09 13:42:11 +02:00
if ( auto status = p - > db . meta - > Put ( p - > db . defWriteOpts , kMeta , ToSlice ( Serialize ( p - > meta ) ) ) ; ! status . ok ( ) ) {
2019-11-30 01:33:27 +02:00
throw DatabaseError ( " Failed to write meta to db " ) ;
}
Debug ( ) < < " Wrote new metadata to db " ;
}
2019-12-11 00:05:16 +02:00
void Storage : : appendHeader ( const Header & h , unsigned int height )
2019-11-29 14:42:17 +02:00
{
2019-12-11 00:05:16 +02:00
const auto targetHeight = GenericDBGet < uint32_t , true > ( p - > db . headers . get ( ) , kNumHeaders , true , // missing ok
" Error reading header count from database " ) . value_or ( 0 ) ;
if ( UNLIKELY ( height ! = targetHeight ) )
throw InternalError ( QString ( " Bad use of appendHeader -- expected height %1, got height %2 " ) . arg ( targetHeight ) . arg ( height ) ) ;
rocksdb : : WriteBatch batch ;
2019-12-11 18:39:19 +02:00
if ( auto stat = batch . Put ( ToSlice ( uint32_t ( height ) ) , ToSlice ( h ) ) ; ! stat . ok ( ) )
2019-12-11 00:05:16 +02:00
throw DatabaseError ( QString ( " Error writing header %1: %2 " ) . arg ( height ) . arg ( QString : : fromStdString ( stat . ToString ( ) ) ) ) ;
2019-12-11 18:39:19 +02:00
if ( auto stat = batch . Put ( kNumHeaders , ToSlice < true > ( uint32_t ( height + 1 ) ) ) ; ! stat . ok ( ) )
2019-12-11 00:05:16 +02:00
throw DatabaseError ( QString ( " Error writing header size key: %1 " ) . arg ( QString : : fromStdString ( stat . ToString ( ) ) ) ) ;
if ( auto stat = p - > db . headers - > Write ( p - > db . defWriteOpts , & batch ) ; ! stat . ok ( ) )
throw DatabaseError ( QString ( " Error writing header %1: %2 " ) . arg ( height ) . arg ( QString : : fromStdString ( stat . ToString ( ) ) ) ) ;
}
auto Storage : : headerForHeight ( unsigned height , QString * err ) - > std : : optional < Header >
{
std : : optional < Header > ret ;
if ( int ( height ) < = latestTip ( ) . first ) {
static const QString errMsg ( " Failed to retrieve header from db " ) ;
try {
ret . emplace (
GenericDBGetFailIfMissing < QByteArray > (
2019-12-11 18:25:24 +02:00
p - > db . headers . get ( ) , uint32_t ( height ) , errMsg , false , p - > db . defReadOpts ) ) ;
2019-12-11 00:05:16 +02:00
if ( UNLIKELY ( ret . value ( ) . size ( ) ! = p - > blockHeaderSize ) ) {
ret . reset ( ) ;
throw DatabaseSerializationError ( " Bad header read from db. Wrong size! " ) ; // jumps to below catch
2019-11-29 17:11:23 +02:00
}
2019-12-11 00:05:16 +02:00
} catch ( const std : : exception & e ) {
if ( err ) * err = e . what ( ) ;
2019-11-29 14:42:17 +02:00
}
}
2019-12-11 00:05:16 +02:00
return ret ;
2019-12-07 23:30:07 +02:00
}
2019-12-11 00:05:16 +02:00
/// Convenient batched alias for above. Returns a set of headers starting at height. May return < count if not
/// all headers were found. Thead safe.
auto Storage : : headersFromHeight ( unsigned height , unsigned count , QString * err ) - > std : : vector < Header >
{
std : : vector < Header > ret ;
int num = std : : min ( 1 + latestTip ( ) . first - int ( height ) , int ( count ) ) ;
if ( num > 0 ) {
if ( err ) * err = " " ;
ret . reserve ( unsigned ( num ) ) ;
for ( int i = 0 ; i < num ; + + i ) {
auto opt = headerForHeight ( height + unsigned ( i ) , err ) ;
if ( ! opt . has_value ( ) )
break ;
ret . emplace_back ( std : : move ( opt . value ( ) ) ) ;
}
} else if ( err ) * err = " No headers in the specified range " ;
ret . shrink_to_fit ( ) ;
return ret ;
}
2019-11-29 14:42:17 +02:00
2019-12-11 00:05:16 +02:00
void Storage : : loadCheckHeadersInDB ( )
2019-11-29 14:42:17 +02:00
{
2019-11-29 17:15:06 +02:00
FatalAssert ( ! ! p - > db . headers ) < < __FUNCTION__ < < " : Headers db is not open " ;
2019-11-29 14:42:17 +02:00
2019-12-11 00:05:16 +02:00
Log ( ) < < " Verifying headers ... " ;
2019-11-30 00:42:33 +02:00
uint32_t num = 0 ;
2019-11-29 14:42:17 +02:00
const auto t0 = Util : : getTimeNS ( ) ;
{
2019-12-03 15:41:09 +02:00
auto * const db = p - > db . headers . get ( ) ;
2019-12-08 02:36:46 +02:00
2019-12-03 15:41:09 +02:00
num = GenericDBGet < uint32_t , true > ( db , kNumHeaders , true , " Error reading header count from database " ) . value_or ( 0 ) ; // missing ok
if ( num > MAX_HEADERS )
throw DatabaseFormatError ( QString ( " Header count (%1) in database exceeds MAX_HEADERS! This is likely due to "
" a database format mistmatch. Delete the datadir and resynch it. " )
. arg ( num ) ) ;
2019-11-29 17:11:23 +02:00
// verify headers: hashPrevBlock must match what we actually read from db
if ( num ) {
2019-11-30 00:42:33 +02:00
auto [ verif , lock ] = headerVerifier ( ) ;
2019-12-11 00:05:16 +02:00
Debug ( ) < < " Verifying " < < num < < " " < < Util : : Pluralize ( " header " , num ) < < " ... " ;
const QString errMsg ( " Error retrieving header from db " ) ;
2019-11-30 00:42:33 +02:00
QString err ;
2019-12-11 00:05:16 +02:00
// read db
for ( uint32_t i = 0 ; i < num ; + + i ) {
// guaranteed to return a value or throw
2019-12-11 18:25:24 +02:00
const auto bytes = GenericDBGetFailIfMissing < QByteArray > ( db , uint32_t ( i ) , errMsg , false , p - > db . defReadOpts ) ;
2019-12-11 00:05:16 +02:00
if ( UNLIKELY ( bytes . size ( ) ! = p - > blockHeaderSize ) )
throw DatabaseFormatError ( QString ( " Error reading header %1, wrong size: %2 " ) . arg ( i ) . arg ( bytes . size ( ) ) ) ;
if ( ! verif ( bytes , & err ) )
2019-11-30 00:42:33 +02:00
throw DatabaseError ( QString ( " %1. Possible databaase corruption. Delete the datadir and resynch. " ) . arg ( err ) ) ;
2019-11-29 17:11:23 +02:00
}
2019-11-29 14:42:17 +02:00
}
}
2019-11-29 17:11:23 +02:00
if ( num ) {
const auto elapsed = Util : : getTimeNS ( ) ;
Debug ( ) < < " Read & verified " < < num < < " " < < Util : : Pluralize ( " header " , num ) < < " from db in " < < QString : : number ( ( elapsed - t0 ) / 1e6 , ' f ' , 3 ) < < " msec " ;
}
2019-11-29 14:42:17 +02:00
}
2019-12-13 02:45:45 +02:00
void Storage : : loadCheckTxNumsFileAndBlkInfo ( )
{
// may throw.
p - > txNumsFile = std : : make_unique < RecordFile > ( options - > datadir + QDir : : separator ( ) + " txnum2txhash " , HashLen , 0x000012e2 ) ;
p - > txNumNext = p - > txNumsFile - > numRecords ( ) ;
Debug ( ) < < " Read TxNumNext from file: " < < p - > txNumNext . load ( ) ;
TxNum ct = 0 ;
if ( const int height = latestTip ( ) . first ; height > = 0 )
{
p - > blkInfos . reserve ( std : : min ( size_t ( height + 1 ) , MAX_HEADERS ) ) ;
Log ( ) < < " Checking tx counts ... " ;
for ( int i = 0 ; i < = height ; + + i ) {
static const QString errMsg ( " Failed to read a blkInfo from db, the database may be corrupted " ) ;
const auto blkInfo = GenericDBGetFailIfMissing < BlkInfo > ( p - > db . blkinfo . get ( ) , uint32_t ( i ) , errMsg , false , p - > db . defReadOpts ) ;
if ( blkInfo . txNum0 ! = ct )
throw DatabaseFormatError ( QString ( " BlkInfo for height %1 does not match computed txNum of %2. "
" \n \n The database may be corrupted. Delete the datadir and resynch it. \n " )
. arg ( i ) . arg ( ct ) ) ;
ct + = blkInfo . nTx ;
p - > blkInfos . emplace_back ( blkInfo ) ;
p - > blkInfosByTxNum [ blkInfo . txNum0 ] = unsigned ( p - > blkInfos . size ( ) - 1 ) ;
}
Log ( ) < < ct < < " total transactions " ;
}
if ( ct ! = p - > txNumNext ) {
throw DatabaseFormatError ( QString ( " BlkInfo txNums do not add up to expected value of %1 != %2. "
" \n \n The database may be corrupted. Delete the datadir and resynch it. \n " )
. arg ( ct ) . arg ( p - > txNumNext . load ( ) ) ) ;
}
}
// NOTE: this must be called *after* loadCheckTxNumsFileAndBlkInfo(), because it needs a valid p->txNumNext
2019-12-09 12:10:59 +02:00
void Storage : : loadCheckUTXOsInDB ( )
{
FatalAssert ( ! ! p - > db . utxoset ) < < __FUNCTION__ < < " : Utxo set db is not open " ;
2019-12-08 01:00:40 +02:00
2019-12-09 12:10:59 +02:00
Log ( ) < < " Verifying utxo set ... " ;
2019-12-08 01:00:40 +02:00
2019-12-09 12:10:59 +02:00
const auto t0 = Util : : getTimeNS ( ) ;
{
const int currentHeight = latestTip ( ) . first ;
2019-12-09 13:42:11 +02:00
std : : unique_ptr < rocksdb : : Iterator > iter ( p - > db . utxoset - > NewIterator ( p - > db . defReadOpts ) ) ;
2019-12-09 12:10:59 +02:00
if ( ! iter ) throw DatabaseError ( " Unable to obtain an iterator to the utxo set db " ) ;
iter - > SeekToFirst ( ) ;
p - > utxoCt = 0 ;
for ( iter - > SeekToFirst ( ) ; iter - > Valid ( ) ; iter - > Next ( ) ) {
// TODO: the below checks may be too slow. See about removing them and just counting the iter.
const auto txo = Deserialize < TXO > ( FromSlice ( iter - > key ( ) ) ) ;
if ( ! txo . isValid ( ) ) {
throw DatabaseSerializationError ( " Read an invalid txo from the utxo set database. "
" This may be due to a database format mismatch. "
2019-12-09 13:42:11 +02:00
" \n \n Delete the datadir and resynch to bitcoind. \n " ) ;
2019-12-09 12:10:59 +02:00
}
auto info = Deserialize < TXOInfo > ( FromSlice ( iter - > value ( ) ) ) ;
if ( ! info . isValid ( ) )
throw DatabaseSerializationError ( QString ( " Txo %1 has invalid metadata in the db. "
" This may be due to a database format mismatch. "
2019-12-09 13:42:11 +02:00
" \n \n Delete the datadir and resynch to bitcoind. \n " )
2019-12-09 12:10:59 +02:00
. arg ( txo . toString ( ) ) ) ;
2019-12-13 02:45:45 +02:00
// uncomment this to do a deep test: TODO: Make this configurable from the CLI -- this last check is very slow.
//const CompactTXO ctxo = CompactTXO(info.txNum, txo.prevoutN);
//const QByteArray shuKey = info.hashX + ctxo.toBytes();
//static const QString errPrefix("Error reading scripthash_unspent");
if ( bool fail1 = false , fail2 = false /*, fail3 = false*/ ;
2019-12-12 20:38:56 +02:00
( fail1 = ( info . confirmedHeight . has_value ( ) & & int ( info . confirmedHeight . value ( ) ) > currentHeight ) )
2019-12-13 02:45:45 +02:00
| | ( fail2 = info . txNum > = p - > txNumNext )
/*|| (fail3 = !GenericDBGet<QByteArray>(p->db.shunspent.get(), shuKey, true, errPrefix, false, p->db.defReadOpts).has_value())*/ ) {
2019-12-09 12:10:59 +02:00
// TODO: reorg? Inconsisent db? FIXME
QString msg ;
{
QTextStream ts ( & msg ) ;
ts < < " Inconsistent database: txo " < < txo . toString ( ) < < " at height: "
2019-12-12 20:38:56 +02:00
< < info . confirmedHeight . value ( ) ;
if ( fail1 ) {
ts < < " > current height: " < < currentHeight < < " . " ;
} else if ( fail2 ) {
ts < < " . TxNum: " < < info . txNum < < " >= " < < p - > txNumNext < < " . " ;
2019-12-13 02:45:45 +02:00
} /*else if (fail3) {
ts < < " . Failed to find ctxo " < < ctxo . toString ( ) < < " in scripthash_unspent db. " ;
} */
2019-12-12 20:38:56 +02:00
ts < < " \n \n The database has been corrupted. Please delete the datadir and resynch to bitcoind. \n " ;
2019-12-08 01:00:40 +02:00
}
2019-12-09 12:10:59 +02:00
throw DatabaseError ( msg ) ;
}
if ( 0 = = + + p - > utxoCt % 100000 ) {
* ( 0 = = p - > utxoCt % 2500000 ? std : : make_unique < Log > ( ) : std : : make_unique < Debug > ( ) ) < < " Verified " < < p - > utxoCt < < " utxos ... " ;
2019-12-08 01:00:40 +02:00
}
}
2019-12-09 12:10:59 +02:00
const auto ct = utxoSetSize ( ) ;
if ( ct )
Log ( ) < < " UTXO set: " < < ct < < Util : : Pluralize ( " utxo " , ct )
< < " , " < < QString : : number ( utxoSetSizeMiB ( ) , ' f ' , 3 ) < < " MiB " ;
}
const auto elapsed = Util : : getTimeNS ( ) ;
Debug ( ) < < " Read txos from db in " < < QString : : number ( ( elapsed - t0 ) / 1e6 , ' f ' , 3 ) < < " msec " ;
}
2019-12-08 01:00:40 +02:00
2019-12-09 13:42:11 +02:00
2019-12-09 12:10:59 +02:00
/// Thread-safe. Immediately save a UTXO to the db. May throw on database error.
2019-12-12 20:38:56 +02:00
void Storage : : utxoAddToDB ( const TXO & txo , const TXOInfo & info , const CompactTXO & ctxo )
2019-12-09 12:10:59 +02:00
{
assert ( bool ( p - > db . utxoset ) ) ;
if ( txo . isValid ( ) ) {
2019-12-11 18:25:24 +02:00
static const QString errMsgPrefix ( " Failed to add a utxo to the utxo db " ) ;
GenericDBPut ( p - > db . utxoset . get ( ) , txo , info , errMsgPrefix , p - > db . defWriteOpts ) ; // may throw on failure
2019-12-12 20:38:56 +02:00
{
2019-12-13 02:45:45 +02:00
// Update the scripthash unspent. This is a very simple table which we scan by hashX prefix using
// an iterator in listUnspent. Each entry's key is prefixed with the HashX bytes but suffixed with the
// serialized CompactTXO bytes. Each entry has no data for the value.
static const QString errMsgPrefix ( " Failed to add an entry to the scripthash_unspent db " ) ;
static const rocksdb : : Slice empty ;
const QByteArray key = info . hashX + ctxo . toBytes ( ) ;
GenericDBPut ( p - > db . shunspent . get ( ) , key , empty , errMsgPrefix , p - > db . defWriteOpts ) ; // may throw, which is what we want
2019-12-12 20:38:56 +02:00
}
2019-12-09 12:10:59 +02:00
+ + p - > utxoCt ;
}
}
/// Thread-safe. Query db for a UTXO, and return it if found. May throw on database error.
std : : optional < TXOInfo > Storage : : utxoGetFromDB ( const TXO & txo , bool throwIfMissing )
{
assert ( bool ( p - > db . utxoset ) ) ;
2019-12-13 02:45:45 +02:00
static const QString errMsgPrefix ( " Failed to read a utxo from the utxo db " ) ;
2019-12-11 18:25:24 +02:00
return GenericDBGet < TXOInfo > ( p - > db . utxoset . get ( ) , txo , ! throwIfMissing , errMsgPrefix , false , p - > db . defReadOpts ) ;
2019-12-09 12:10:59 +02:00
}
/// Delete a Utxo from the db. Will throw only on database error (but not if it was missing).
2019-12-12 20:38:56 +02:00
void Storage : : utxoDeleteFromDB ( const TXO & txo , const HashX & hashX , const CompactTXO & ctxo )
2019-12-09 12:10:59 +02:00
{
assert ( bool ( p - > db . utxoset ) ) ;
2019-12-13 02:45:45 +02:00
{
// delete from utxoset db
auto stat = p - > db . utxoset - > Delete ( rocksdb : : WriteOptions ( ) , ToSlice ( Serialize ( txo ) ) ) ;
if ( ! stat . ok ( ) ) {
throw DatabaseError ( QString ( " Failed to delete from the utxo db (%1:%2): %3 " )
. arg ( QString ( txo . prevoutHash . toHex ( ) ) ) . arg ( txo . prevoutN ) . arg ( QString : : fromStdString ( stat . ToString ( ) ) ) ) ;
}
2019-12-08 01:00:40 +02:00
}
2019-12-12 20:38:56 +02:00
{
2019-12-13 02:45:45 +02:00
// delete from scripthash_unspent db
const QByteArray key = hashX + ctxo . toBytes ( ) ;
if ( auto stat = p - > db . shunspent - > Delete ( p - > db . defWriteOpts , ToSlice ( key ) ) ; ! stat . ok ( ) ) {
throw DatabaseError ( QString ( " Failed to delete a from the scripthash_unspent db (%1:%2): %3 " )
. arg ( QString ( txo . prevoutHash . toHex ( ) ) ) . arg ( txo . prevoutN ) . arg ( QString : : fromStdString ( stat . ToString ( ) ) ) ) ;
2019-12-12 20:38:56 +02:00
}
}
2019-12-09 12:10:59 +02:00
- - p - > utxoCt ;
}
2019-12-08 01:00:40 +02:00
2019-12-09 12:10:59 +02:00
int64_t Storage : : utxoSetSize ( ) const { return p - > utxoCt ; }
double Storage : : utxoSetSizeMiB ( ) const {
constexpr int64_t elemSize = TXO : : serSize ( ) + TXOInfo : : serSize ( ) ;
return ( utxoSetSize ( ) * elemSize ) / 1e6 ;
}
2019-12-07 20:34:19 +02:00
2019-12-14 00:47:11 +02:00
QString Storage : : addBlock ( PreProcessedBlockPtr ppb , bool saveUndo , unsigned nReserve )
2019-12-07 20:34:19 +02:00
{
assert ( bool ( ppb ) & & bool ( p ) ) ;
2019-12-14 00:47:11 +02:00
std : : unique_ptr < UndoInfo > undo ;
if ( saveUndo ) {
undo . reset ( new UndoInfo ) ;
undo - > height = ppb - > height ;
}
2019-12-07 20:34:19 +02:00
QString errRet ;
2019-12-15 09:14:56 +02:00
// take all locks now.. since this is a Big Deal. TODO: add more locks here?
std : : scoped_lock guard ( p - > headerVerifierLock , p - > blkInfoLock ) ;
2019-12-07 20:34:19 +02:00
const auto verifUndo = p - > headerVerifier ; // keep a copy for undo purposes in case this fails
try {
2019-12-11 19:57:01 +02:00
const auto blockTxNum0 = p - > txNumNext . load ( ) ;
2019-12-11 11:32:46 +02:00
2019-12-07 20:34:19 +02:00
// Verify header chain makes sense (by checking hashes, using the shared header verifier)
QByteArray rawHeader ;
{
QString errMsg ;
if ( ! p - > headerVerifier ( ppb - > header , & errMsg ) ) {
// XXX possible reorg point. FIXME TODO
// reorg here? TODO: deal with this better.
throw Exception ( errMsg ) ;
}
// save raw header back to our buffer
rawHeader = p - > headerVerifier . lastHeaderProcessed ( ) . second ;
}
2019-12-11 11:32:46 +02:00
{ // add txnum -> txhash association to the TxNumsFile...
2019-12-11 16:30:13 +02:00
auto batch = p - > txNumsFile - > beginBatchAppend ( ) ; // may throw if io error in c'tor here.
2019-12-11 11:49:45 +02:00
QString errStr ;
2019-12-11 11:32:46 +02:00
for ( const auto & txInfo : ppb - > txInfos ) {
2019-12-11 16:30:13 +02:00
if ( ! batch . append ( txInfo . hash , & errStr ) ) // does not throw here, but we do.
2019-12-11 11:49:45 +02:00
throw InternalError ( QString ( " Batch append for txNums failed: %1. \n \n "
" Database is now likely corrupted. Please delete the datadir and resynch. \n " )
. arg ( errStr ) ) ;
2019-12-07 20:34:19 +02:00
}
2019-12-11 16:30:13 +02:00
// <-- The batch d'tor may close the app on error here with Fatal() if a low-level file error occurs now
// on header update (see: RecordFile.cpp, ~BatchAppendContext()).
2019-12-11 11:32:46 +02:00
}
p - > txNumNext + = ppb - > txInfos . size ( ) ; // update internal counter
2019-12-11 11:49:45 +02:00
if ( p - > txNumNext ! = p - > txNumsFile - > numRecords ( ) )
2019-12-11 11:32:46 +02:00
throw InternalError ( " TxNum file and internal txNumNext counter disagree! FIXME! " ) ;
2019-12-11 11:49:45 +02:00
2019-12-07 20:34:19 +02:00
2019-12-09 10:04:30 +02:00
constexpr bool debugPrt = false ;
2019-12-07 20:34:19 +02:00
// update utxoSet
{
2019-12-14 00:47:11 +02:00
// reserve space in undo, if in saveUndo mode
if ( undo ) {
undo - > addUndos . reserve ( ppb - > outputs . size ( ) ) ;
undo - > delUndos . reserve ( ppb - > inputs . size ( ) ) ;
}
2019-12-07 20:34:19 +02:00
// add outputs
for ( const auto & [ hashX , ag ] : ppb - > hashXAggregated ) {
for ( const auto oidx : ag . outs ) {
2019-12-11 17:02:42 +02:00
const auto & out = ppb - > outputs [ oidx ] ;
if ( out . spentInInputIndex . has_value ( ) ) {
2019-12-09 10:04:30 +02:00
if constexpr ( debugPrt )
2019-12-11 17:02:42 +02:00
Debug ( ) < < " Skipping output #: " < < oidx < < " for " < < ppb - > txInfos [ out . txIdx ] . hash . toHex ( ) < < " (was spent in same block tx: " < < ppb - > txInfos [ ppb - > inputs [ out . spentInInputIndex . value ( ) ] . txIdx ] . hash . toHex ( ) < < " ) " ;
2019-12-09 10:04:30 +02:00
continue ;
}
2019-12-07 20:34:19 +02:00
const TxHash & hash = ppb - > txInfos [ out . txIdx ] . hash ;
TXOInfo info ;
info . hashX = hashX ;
info . amount = out . amount ;
info . confirmedHeight = ppb - > height ;
2019-12-12 20:38:56 +02:00
info . txNum = blockTxNum0 + out . txIdx ;
2019-12-07 23:30:07 +02:00
const TXO txo { hash , out . outN } ;
2019-12-14 00:47:11 +02:00
const CompactTXO ctxo ( blockTxNum0 + out . txIdx , out . outN ) ;
utxoAddToDB ( txo , info , ctxo ) ; // add to db
if ( undo ) { // save undo info if we are in saveUndo mode
undo - > addUndos . emplace_back ( txo , info . hashX , ctxo ) ;
}
2019-12-07 20:34:19 +02:00
if constexpr ( debugPrt )
Debug ( ) < < " Added txo: " < < txo . toString ( )
< < " (txid: " < < hash . toHex ( ) < < " height: " < < ppb - > height < < " ) "
< < " amount: " < < info . amount . ToString ( ) < < " for HashX: " < < info . hashX . toHex ( ) ;
}
}
// add spends (process inputs)
std : : unordered_set < HashX , HashHasher > newHashXInputsResolved ;
unsigned inum = 0 ;
2019-12-08 02:36:46 +02:00
for ( auto & in : ppb - > inputs ) {
2019-12-09 12:10:59 +02:00
const TXO txo { in . prevoutHash , in . prevoutN } ;
2019-12-07 20:34:19 +02:00
if ( ! inum ) {
// coinbase.. skip
2019-12-09 10:04:30 +02:00
} else if ( in . parentTxOutIdx . has_value ( ) ) {
// was an input that was spent in this block so it's ok to skip.. we never added it to utxo set
if constexpr ( debugPrt )
2019-12-09 12:10:59 +02:00
Debug ( ) < < " Skipping input " < < txo . toString ( ) < < " , spent in this block (output # " < < in . parentTxOutIdx . value ( ) < < " ) " ;
} else if ( const auto opt = utxoGetFromDB ( txo ) ; opt . has_value ( ) ) {
const auto & info = opt . value ( ) ;
2019-12-07 20:34:19 +02:00
if ( info . confirmedHeight . has_value ( ) & & info . confirmedHeight . value ( ) ! = ppb - > height ) {
2019-12-12 13:15:45 +02:00
// was a prevout from a previos block.. so the ppb didn't have it in the 'involving hashx' set..
// mark the spend as having involved this hashX for this ppb now.
2019-12-12 00:07:29 +02:00
auto & ag = ppb - > hashXAggregated [ info . hashX ] ;
ag . ins . emplace_back ( inum ) ;
2019-12-07 20:34:19 +02:00
newHashXInputsResolved . insert ( info . hashX ) ;
2019-12-12 00:07:29 +02:00
// mark its txidx
2019-12-12 13:15:45 +02:00
if ( auto & vec = ag . txNumsInvolvingHashX ; vec . empty ( ) | | vec . back ( ) ! = in . txIdx )
2019-12-12 00:07:29 +02:00
vec . emplace_back ( in . txIdx ) ;
2019-12-07 20:34:19 +02:00
}
if constexpr ( debugPrt ) {
const auto dbgTxIdHex = ppb - > txHashForInputIdx ( inum ) . toHex ( ) ;
2019-12-09 12:10:59 +02:00
Debug ( ) < < " Spent " < < txo . toString ( ) < < " amount: " < < info . amount . ToString ( )
2019-12-07 20:34:19 +02:00
< < " in txid: " < < dbgTxIdHex < < " height: " < < ppb - > height
< < " input number: " < < ppb - > numForInputIdx ( inum ) . value_or ( 0xffff )
2019-12-09 12:10:59 +02:00
< < " HashX: " < < info . hashX . toHex ( ) ;
2019-12-07 20:34:19 +02:00
}
2019-12-09 12:10:59 +02:00
// delete from db
2019-12-12 20:38:56 +02:00
utxoDeleteFromDB ( txo , info . hashX , CompactTXO ( info . txNum , txo . prevoutN ) ) ; // delete from db
2019-12-14 00:47:11 +02:00
if ( undo ) { // save undo info, if we are in saveUndo mode
undo - > delUndos . emplace_back ( txo , info ) ;
}
2019-12-07 20:34:19 +02:00
} else {
QString s ;
{
const auto dbgTxIdHex = ppb - > txHashForInputIdx ( inum ) . toHex ( ) ;
QTextStream ts ( & s ) ;
ts < < " Failed to spend: " < < in . prevoutHash . toHex ( ) < < " : " < < in . prevoutN < < " (spending txid: " < < dbgTxIdHex < < " ) " ;
}
throw Exception ( s ) ;
}
+ + inum ;
}
// sort and shrink_to_fit new hashX inputs added
for ( const auto & hashX : newHashXInputsResolved ) {
auto & ag = ppb - > hashXAggregated [ hashX ] ;
std : : sort ( ag . ins . begin ( ) , ag . ins . end ( ) ) ; // make sure they are sorted
2019-12-12 13:15:45 +02:00
std : : sort ( ag . txNumsInvolvingHashX . begin ( ) , ag . txNumsInvolvingHashX . end ( ) ) ;
auto last = std : : unique ( ag . txNumsInvolvingHashX . begin ( ) , ag . txNumsInvolvingHashX . end ( ) ) ;
ag . txNumsInvolvingHashX . erase ( last , ag . txNumsInvolvingHashX . end ( ) ) ;
2019-12-07 20:34:19 +02:00
ag . ins . shrink_to_fit ( ) ;
2019-12-12 13:15:45 +02:00
ag . txNumsInvolvingHashX . shrink_to_fit ( ) ;
2019-12-07 20:34:19 +02:00
}
if constexpr ( debugPrt )
2019-12-09 12:10:59 +02:00
Debug ( ) < < " utxoset size: " < < utxoSetSize ( ) < < " block: " < < ppb - > height ;
2019-12-07 20:34:19 +02:00
}
2019-12-12 00:07:29 +02:00
{
2019-12-12 13:15:45 +02:00
// now.. update the txNumsInvolvingHashX to be offset from txNum0 for this block, and save history to db table
2019-12-12 00:07:29 +02:00
// history is hashX -> TxNumVec (serialized) as a serities of 6-bytes txNums in blockchain order as they appeared.
rocksdb : : WriteBatch batch ;
for ( auto & [ hashX , ag ] : ppb - > hashXAggregated ) {
2019-12-12 13:15:45 +02:00
for ( auto & txNum : ag . txNumsInvolvingHashX ) {
2019-12-12 00:07:29 +02:00
txNum + = blockTxNum0 ; // transform local txIdx to -> txNum (global mapping)
}
// save scripthash history for this hashX, by appending to existing history. Note that this uses
// the 'ConcatOperator' class we defined in this file, which requires rocksdb be compiled with RTTI.
2019-12-12 13:15:45 +02:00
if ( auto st = batch . Merge ( ToSlice ( hashX ) , ToSlice ( Serialize ( ag . txNumsInvolvingHashX ) ) ) ; ! st . ok ( ) )
2019-12-12 00:07:29 +02:00
throw DatabaseError ( QString ( " batch merge fail for hashX %1, block height %2: %3 " )
. arg ( QString ( hashX . toHex ( ) ) ) . arg ( ppb - > height ) . arg ( QString : : fromStdString ( st . ToString ( ) ) ) ) ;
}
if ( auto st = p - > db . shist - > Write ( p - > db . defWriteOpts , & batch ) ; ! st . ok ( ) )
throw DatabaseError ( QString ( " batch merge fail for block height %1: %2 " )
. arg ( ppb - > height ) . arg ( QString : : fromStdString ( st . ToString ( ) ) ) ) ;
}
2019-12-12 12:07:18 +02:00
{
// update BlkInfo
if ( nReserve ) {
if ( const auto size = p - > blkInfos . size ( ) ; size + 1 > p - > blkInfos . capacity ( ) )
p - > blkInfos . reserve ( size + nReserve ) ; // reserve space for new blkinfos in 1 go to save on copying
}
p - > blkInfos . emplace_back (
2019-12-11 19:57:01 +02:00
blockTxNum0 , // .txNum0
2019-12-12 12:07:18 +02:00
unsigned ( ppb - > txInfos . size ( ) )
) ;
2019-12-11 19:57:01 +02:00
2019-12-12 12:07:18 +02:00
const auto & blkInfo = p - > blkInfos . back ( ) ;
p - > blkInfosByTxNum [ blkInfo . txNum0 ] = unsigned ( p - > blkInfos . size ( ) - 1 ) ;
2019-12-07 20:34:19 +02:00
2019-12-12 12:07:18 +02:00
// save BlkInfo to db
static const QString blkInfoErrMsg ( " Error writing BlkInfo to db " ) ;
GenericDBPut ( p - > db . blkinfo . get ( ) , uint32_t ( ppb - > height ) , blkInfo , blkInfoErrMsg , p - > db . defWriteOpts ) ;
2019-12-14 00:47:11 +02:00
if ( undo ) {
// save blkInfo to undo information, if in saveUndo mode
undo - > blkInfo = p - > blkInfos . back ( ) ;
}
}
2019-12-15 09:08:31 +02:00
// save the last of the undo info, if in saveUndo mode
2019-12-14 00:47:11 +02:00
if ( undo ) {
2019-12-15 09:08:31 +02:00
const auto t0 = Util : : getTimeNS ( ) ;
2019-12-14 00:47:11 +02:00
undo - > hash = BTC : : HashRev ( rawHeader ) ;
undo - > scriptHashes = Util : : keySet < decltype ( undo - > scriptHashes ) > ( ppb - > hashXAggregated ) ;
2019-12-15 09:08:31 +02:00
static const QString errPrefix ( " Error saving undo info to undo db " ) ;
GenericDBPut ( p - > db . undo . get ( ) , uint32_t ( ppb - > height ) , * undo , errPrefix , p - > db . defWriteOpts ) ; // save undo to db
2019-12-14 00:47:11 +02:00
if constexpr ( debugPrt ) {
2019-12-14 11:42:26 +02:00
// testing undo ser/deser
Debug ( ) < < " Undo info 1: " < < undo - > toDebugString ( ) ;
QByteArray ba = Serialize ( * undo ) ;
Debug ( ) < < " Undo info 1 serSize: " < < ba . length ( ) ;
bool ok ;
auto undo2 = Deserialize < UndoInfo > ( ba , & ok ) ;
ba . fill ( ' z ' ) ; // ensure no shallow copies of buffer exist in deserialized object. if they do below tests will fail
FatalAssert ( ok & & undo2 . isValid ( ) ) < < " Deser of undo info failed! " ;
Debug ( ) < < " Undo info 2: " < < undo2 . toDebugString ( ) ;
Debug ( ) < < " Undo info 1 == undo info 2: " < < ( * undo = = undo2 ) ;
2019-12-15 09:08:31 +02:00
} else {
const auto elapsedms = ( Util : : getTimeNS ( ) - t0 ) / 1e6 ;
const size_t nTx = undo - > blkInfo . nTx , nSH = undo - > scriptHashes . size ( ) ;
Debug ( ) < < " Saved undo for block " < < undo - > height < < " , "
< < nTx < < " " < < Util : : Pluralize ( " transaction " , nTx )
< < " involving " < < nSH < < " " < < Util : : Pluralize ( " scripthash " , nSH )
< < " , in " < < QString : : number ( elapsedms , ' f ' , 2 ) < < " msec. " ;
2019-12-14 00:47:11 +02:00
}
2019-12-12 12:07:18 +02:00
}
2019-12-15 09:08:31 +02:00
{
// Unconditionally expire old undos >10 blocks ago to keep the db tidy. We need to do this for each block
// because we do not know what old undos may be lurking in the db.
// TODO: On startup, have the db scan and find the earliest undo and only take this branch if we know an old
// undo exists for a certain height!
static const QString errPrefix ( " Error deleting old/stale undo info from undo db " ) ;
if ( const auto expireUndoHeight = int ( ppb - > height ) - int ( BTC : : maxReorgDepth ) ; expireUndoHeight > = 0 )
GenericDBDelete ( p - > db . undo . get ( ) , uint32_t ( expireUndoHeight ) , errPrefix , p - > db . defWriteOpts ) ;
}
2019-12-07 20:34:19 +02:00
2019-12-11 00:05:16 +02:00
appendHeader ( rawHeader , ppb - > height ) ;
2019-12-07 20:34:19 +02:00
} catch ( const std : : exception & e ) {
errRet = e . what ( ) ;
p - > headerVerifier = verifUndo ; // undo header verifier state
}
return errRet ;
}
2019-12-12 12:07:18 +02:00
std : : optional < TxHash > Storage : : hashForTxNum ( TxNum n , bool throwIfMissing , bool * wasCached , bool skipCache ) const
2019-12-07 20:34:19 +02:00
{
2019-12-08 22:07:01 +02:00
std : : optional < TxHash > ret ;
if ( ! skipCache ) ret = p - > lruNum2Hash . tryGet ( n ) ;
2019-12-08 02:36:46 +02:00
if ( ret . has_value ( ) ) {
if ( wasCached ) * wasCached = true ;
return ret ;
} else if ( wasCached ) * wasCached = false ;
2019-12-11 11:32:46 +02:00
static const QString kErrMsg ( " Error reading TxHash for TxNum %1: %2 " ) ;
QString errStr ;
const auto bytes = p - > txNumsFile - > readRecord ( n , & errStr ) ;
if ( bytes . isEmpty ( ) ) {
errStr = kErrMsg . arg ( n ) . arg ( errStr ) ;
if ( throwIfMissing )
throw DatabaseError ( errStr ) ;
Warning ( ) < < errStr ;
} else {
ret . emplace ( bytes ) ;
}
2019-12-08 22:07:01 +02:00
if ( ! skipCache & & ret . has_value ( ) ) {
2019-12-08 23:15:42 +02:00
// save in cache
2019-12-08 02:36:46 +02:00
p - > lruNum2Hash . insert ( n , ret . value ( ) ) ;
2019-12-08 17:21:39 +02:00
}
2019-12-08 02:36:46 +02:00
return ret ;
2019-12-07 20:34:19 +02:00
}
2019-12-12 12:07:18 +02:00
std : : optional < unsigned > Storage : : heightForTxNum ( TxNum n ) const
{
SharedLockGuard g ( p - > blkInfoLock ) ;
std : : optional < unsigned > ret ;
auto it = p - > blkInfosByTxNum . upper_bound ( n ) ; // O(logN) search; find the block *AFTER* n, then go backw on to find the block in range
if ( it ! = p - > blkInfosByTxNum . begin ( ) ) {
- - it ;
const auto & bi = p - > blkInfos [ it - > second ] ;
if ( n > = bi . txNum0 & & n < bi . txNum0 + bi . nTx )
ret = it - > second ;
}
return ret ;
}
2019-12-13 00:14:09 +02:00
auto Storage : : getHistory ( const HashX & hashX ) const - > History
2019-12-12 12:07:18 +02:00
{
History ret ;
try {
static const QString err ( " Error retrieving history for a script hash " ) ;
2019-12-13 00:14:09 +02:00
auto nums_opt = GenericDBGet < TxNumVec > ( p - > db . shist . get ( ) , hashX , true , err , false , p - > db . defReadOpts ) ;
2019-12-12 12:07:18 +02:00
if ( nums_opt . has_value ( ) ) {
auto & nums = nums_opt . value ( ) ;
ret . reserve ( nums . size ( ) ) ;
for ( auto num : nums ) {
auto hash = hashForTxNum ( num ) . value ( ) ; // may throw, but that indicates some database inconsistency. we catch below
auto height = heightForTxNum ( num ) . value ( ) ; // may throw, same deal
ret . emplace_back ( HistoryItem { hash , height } ) ;
}
}
} catch ( const std : : exception & e ) {
2019-12-13 00:14:09 +02:00
Warning ( Log : : Magenta ) < < __func__ < < " : " < < e . what ( ) ;
}
return ret ;
}
auto Storage : : listUnspent ( const HashX & hashX ) const - > UnspentItems
{
UnspentItems ret ;
try {
2019-12-13 02:45:45 +02:00
std : : unique_ptr < rocksdb : : Iterator > iter ( p - > db . shunspent - > NewIterator ( p - > db . defReadOpts ) ) ;
const rocksdb : : Slice prefix = ToSlice ( hashX ) ; // points to data in hashX
// Search table for all keys that start with hashx's bytes. Note: the loop end-condition is strange.
// See: https://github.com/facebook/rocksdb/wiki/Prefix-Seek-API-Changes#transition-to-the-new-usage
rocksdb : : Slice key ;
for ( iter - > Seek ( prefix ) ; iter - > Valid ( ) & & ( key = iter - > key ( ) ) . starts_with ( prefix ) ; iter - > Next ( ) ) {
if ( key . size ( ) ! = HashLen + CompactTXO : : serSize ( ) )
// should never happen, indicates db corruption
throw InternalError ( " Key size for hashx is invalid " ) ;
const CompactTXO ctxo = CompactTXO : : fromBytes ( key . data ( ) + HashLen , CompactTXO : : serSize ( ) ) ;
if ( ! ctxo . isValid ( ) )
// should never happen, indicates db corruption
throw InternalError ( " Deserialized CompactTXO is invalid " ) ;
2019-12-13 03:04:21 +02:00
static const QString err ( " Error retrieving the utxo for an unspent item " ) ;
2019-12-13 02:45:45 +02:00
auto hash = hashForTxNum ( ctxo . txNum ( ) ) . value ( ) ; // may throw, but that indicates some database inconsistency. we catch below
auto height = heightForTxNum ( ctxo . txNum ( ) ) . value ( ) ; // may throw, same deal
auto info = GenericDBGetFailIfMissing < TXOInfo > ( p - > db . utxoset . get ( ) , TXO { hash , ctxo . N ( ) } , err , false , p - > db . defReadOpts ) ; // may throw -- indicates db inconsistency
ret . emplace_back ( UnspentItem {
{ hash , height } , // base HistoryItem
ctxo . N ( ) , // .tx_pos
info . amount , // .value
info . txNum , // .txNum
} ) ;
2019-12-13 00:14:09 +02:00
}
2019-12-13 02:45:45 +02:00
std : : sort ( ret . begin ( ) , ret . end ( ) ) ;
ret . shrink_to_fit ( ) ;
2019-12-13 00:14:09 +02:00
} catch ( const std : : exception & e ) {
Warning ( Log : : Magenta ) < < __func__ < < " : " < < e . what ( ) ;
2019-12-12 12:07:18 +02:00
}
return ret ;
}
2019-12-07 20:34:19 +02:00
2019-12-13 02:45:45 +02:00
// HistoryItem & UnspentItem -- operator< and operator== -- for sort.
bool Storage : : HistoryItem : : operator < ( const HistoryItem & o ) const noexcept {
return height = = o . height ? hash < o . hash : height < o . height ;
}
bool Storage : : HistoryItem : : operator = = ( const HistoryItem & o ) const noexcept {
return height = = o . height & & hash = = o . hash ;
}
bool Storage : : UnspentItem : : operator < ( const UnspentItem & o ) const noexcept {
if ( txNum = = o . txNum ) { // order by txNum
if ( tx_pos = = o . tx_pos ) { // then by tx_pos
// next by tx_hash, height (this branch shouldn't normally be reached with real blockchain data since
// txNum:tx_pos defines an UnspentItem completed...
if ( HistoryItem : : operator < ( o ) )
return true ;
else if ( HistoryItem : : operator = = ( o ) )
return value < o . value ;
return false ;
}
return tx_pos < o . tx_pos ;
}
return txNum < o . txNum ;
}
bool Storage : : UnspentItem : : operator = = ( const UnspentItem & o ) const noexcept {
return txNum = = o . txNum & & tx_pos = = o . tx_pos & & value = = o . value & & HistoryItem : : operator = = ( o ) ;
}
2019-11-29 14:42:17 +02:00
namespace {
2019-11-29 17:11:23 +02:00
// specializations of Serialize/Deserialize
2019-11-29 14:42:17 +02:00
template < > QByteArray Serialize ( const Meta & m )
{
QByteArray ba ;
{
QDataStream ds ( & ba , QIODevice : : WriteOnly | QIODevice : : Truncate ) ;
2019-12-03 06:38:19 +02:00
// we serialize the 'magic' value as a simple scalar as a sort of endian check for the DB
2019-12-14 09:26:20 +02:00
ds < < SerializeScalarNoCopy ( m . magic ) < < m . version < < m . chain < < m . platformBits ;
2019-11-29 14:42:17 +02:00
}
return ba ;
}
template < > Meta Deserialize ( const QByteArray & ba , bool * ok_ptr )
{
bool dummy ;
bool & ok ( ok_ptr ? * ok_ptr : dummy ) ;
ok = false ;
2019-12-03 06:38:19 +02:00
Meta m { 0 , 0 , { } } ;
2019-11-29 14:42:17 +02:00
{
QDataStream ds ( ba ) ;
2019-12-03 06:38:19 +02:00
QByteArray magicBytes ;
ds > > magicBytes ; // read magic as raw bytes.
if ( ( ok = ds . status ( ) = = QDataStream : : Status : : Ok ) ) {
m . magic = DeserializeScalar < decltype ( m . magic ) > ( magicBytes , & ok ) ;
if ( ok ) {
ds > > m . version > > m . chain ;
2019-12-14 09:26:20 +02:00
if ( ! ds . atEnd ( ) ) {
// TODO: make this field non-optional. For now we tolerate it missing since we added this field
// later and we want to be able to still test on our existing db's.
ds > > m . platformBits ;
}
2019-12-03 06:38:19 +02:00
ok = ds . status ( ) = = QDataStream : : Status : : Ok ;
}
}
2019-11-29 14:42:17 +02:00
}
return m ;
}
2019-12-07 23:30:07 +02:00
2019-12-09 12:10:59 +02:00
template < > QByteArray Serialize ( const TXO & txo ) { return txo . toBytes ( ) ; }
template < > TXO Deserialize ( const QByteArray & ba , bool * ok ) {
2019-12-14 11:42:26 +02:00
TXO ret = TXO : : fromBytes ( ba ) ; // requires exact size, fails if extra bytes at the end
2019-12-07 23:30:07 +02:00
if ( ok ) * ok = ret . isValid ( ) ;
return ret ;
}
template < > QByteArray Serialize ( const TXOInfo & inf ) { return inf . toBytes ( ) ; }
2019-12-09 12:10:59 +02:00
template < > TXOInfo Deserialize ( const QByteArray & ba , bool * ok )
2019-12-07 23:30:07 +02:00
{
2019-12-14 11:42:26 +02:00
TXOInfo ret = TXOInfo : : fromBytes ( ba ) ; // will fail if extra bytes at the end
2019-12-07 23:30:07 +02:00
if ( ok ) * ok = ret . isValid ( ) ;
return ret ;
}
2019-12-11 19:57:01 +02:00
// deep copy, raw bytes
2019-12-14 12:21:31 +02:00
template < > QByteArray Serialize ( const BlkInfo & b ) { return DeepCpy ( & b ) ; }
2019-12-14 11:42:26 +02:00
// will fail if extra bytes at the end
2019-12-11 19:57:01 +02:00
template < > BlkInfo Deserialize ( const QByteArray & ba , bool * ok ) {
BlkInfo ret ;
if ( ba . length ( ) ! = sizeof ( ret ) ) {
if ( ok ) * ok = false ;
} else {
if ( ok ) * ok = true ;
ret = * reinterpret_cast < const BlkInfo * > ( ba . constData ( ) ) ;
}
return ret ;
}
2019-12-12 00:07:29 +02:00
2019-12-14 11:42:26 +02:00
struct UndoInfoSerHeader {
static constexpr uint16_t defMagic = 0xf12c , defVer = 0x1 ;
uint16_t magic = defMagic ; ///< sanity check
uint16_t ver = defVer ; ///< sanity check
uint32_t len = 0 ; ///< the length of the entire buffer, including this struct and all data to follow. A sanity check.
uint32_t nScriptHashes = 0 , nAddUndos = 0 , nDelUndos = 0 ; ///< the number of elements in each of the 3 arrays in question.
static constexpr size_t addUndoItemSerSize = TXO : : serSize ( ) + HashLen + CompactTXO : : serSize ( ) ;
static constexpr size_t delUndoItemSerSize = TXO : : serSize ( ) + TXOInfo : : serSize ( ) ;
/// computes the total size given the ser size of the blkInfo struct. Requires that nScriptHashes, nAddUndos, and nDelUndos be already filled-in.
size_t computeTotalSize ( ) const {
const auto shSize = nScriptHashes * HashLen ;
const auto addsSize = nAddUndos * addUndoItemSerSize ;
const auto delsSize = nDelUndos * delUndoItemSerSize ;
return sizeof ( * this ) + sizeof ( UndoInfo : : height ) + HashLen + sizeof ( BlkInfo ) + shSize + addsSize + delsSize ;
}
bool isLenSane ( ) const { return size_t ( len ) = = computeTotalSize ( ) ; }
} ;
// UndoInfo
template < > QByteArray Serialize ( const UndoInfo & u ) {
UndoInfoSerHeader hdr ;
// fill these in now so that hdr.computeTotalSize works
hdr . nScriptHashes = uint32_t ( u . scriptHashes . size ( ) ) ;
hdr . nAddUndos = uint32_t ( u . addUndos . size ( ) ) ;
hdr . nDelUndos = uint32_t ( u . delUndos . size ( ) ) ;
hdr . len = uint32_t ( hdr . computeTotalSize ( ) ) ;
QByteArray ret ;
ret . reserve ( int ( hdr . len ) ) ;
// 1. header
2019-12-14 12:21:31 +02:00
ret . append ( ShallowTmp ( & hdr ) ) ;
2019-12-14 11:42:26 +02:00
// 2. .height
ret . append ( SerializeScalarNoCopy ( u . height ) ) ;
// 3. .hash
const auto chkHashLen = [ & ret ] ( const QByteArray & hash ) - > bool {
if ( hash . length ( ) ! = HashLen ) {
Warning ( ) < < " hash is not " < < HashLen < < " bytes. Serialize UndoInfo fail. FIXME! " ;
ret . clear ( ) ;
return false ;
}
return true ;
} ;
if ( ! chkHashLen ( u . hash ) ) return ret ;
ret . append ( u . hash ) ;
// 4. .blkInfo
2019-12-14 12:21:31 +02:00
const QByteArray blkInfoBytes = Serialize ( u . blkInfo ) ;
2019-12-14 11:42:26 +02:00
ret . append ( blkInfoBytes ) ;
// 5. .scriptHashes, 32 bytes each, for all in set
for ( const auto & sh : u . scriptHashes ) {
if ( UNLIKELY ( ! chkHashLen ( sh ) ) ) return ret ;
ret . append ( sh ) ;
}
// 6. .addUndos, 64 bytes each * nAddUndos
for ( const auto & [ txo , hashX , ctxo ] : u . addUndos ) {
if ( UNLIKELY ( ! chkHashLen ( hashX ) ) ) return ret ;
ret . append ( Serialize ( txo ) ) ;
ret . append ( hashX ) ;
ret . append ( Serialize ( ctxo ) ) ;
}
// 7. .delUndos, 50 bytes each * nDelUndos
for ( const auto & [ txo , txoInfo ] : u . delUndos ) {
ret . append ( Serialize ( txo ) ) ;
if ( UNLIKELY ( ! chkHashLen ( txoInfo . hashX ) ) ) return ret ;
ret . append ( Serialize ( txoInfo ) ) ;
}
assert ( ret . length ( ) = = int ( hdr . len ) ) ;
return ret ;
}
// UndoInfo -- note this will fail if the byte array has extra bytes at the end
template < > UndoInfo Deserialize ( const QByteArray & ba , bool * ok ) {
UndoInfo ret ;
const auto setOk = [ & ok , & ret ] ( bool b ) { if ( ok ) * ok = b ; if ( ! b ) ret . clear ( ) ; } ;
const auto chkAssertion = [ & setOk ] ( bool assertion , const char * extra = " " ) {
if ( UNLIKELY ( ! assertion ) ) {
Warning ( ) < < " Deserialize UndoInfo called with an invalid byte array! FIXME! " < < extra ;
setOk ( false ) ;
}
return assertion ;
} ;
if ( ! chkAssertion ( ba . size ( ) > int ( sizeof ( UndoInfoSerHeader ) ) , " Short byte count " ) )
return ret ;
// 1. .header
const UndoInfoSerHeader * hdr = reinterpret_cast < decltype ( hdr ) > ( ba . data ( ) ) ;
if ( ! chkAssertion ( int ( hdr - > len ) = = ba . size ( ) & & hdr - > magic = = hdr - > defMagic & & hdr - > ver = = hdr - > defVer
& & hdr - > isLenSane ( ) , " Header sanity check fail " ) )
return ret ;
const char * cur = ba . data ( ) + sizeof ( * hdr ) , * const end = ba . data ( ) + ba . length ( ) ;
bool myok = false ;
// 2. .height
2019-12-14 12:21:31 +02:00
ret . height = DeserializeScalar < decltype ( ret . height ) > ( ShallowTmp ( cur , sizeof ( ret . height ) ) , & myok ) ;
2019-12-14 11:42:26 +02:00
if ( ! chkAssertion ( myok & & cur < end ) )
return ret ;
cur + = sizeof ( ret . height ) ;
// 3. .hash
2019-12-14 12:21:31 +02:00
ret . hash = DeepCpy ( cur , HashLen ) ; // deep copy
2019-12-14 11:42:26 +02:00
if ( ! chkAssertion ( ret . hash . length ( ) = = HashLen & & cur < end ) )
return ret ;
cur + = HashLen ;
// 4. .blkInfo
2019-12-14 12:21:31 +02:00
ret . blkInfo = Deserialize < BlkInfo > ( ShallowTmp ( cur , sizeof ( BlkInfo ) ) , & myok ) ;
2019-12-14 11:42:26 +02:00
if ( ! chkAssertion ( myok & & cur < = end ) )
return ret ;
cur + = sizeof ( BlkInfo ) ;
// 5. .scriptHashes, 32 bytes each * hdr->nScriptHashes
ret . scriptHashes . reserve ( hdr - > nScriptHashes ) ;
for ( unsigned i = 0 ; i < hdr - > nScriptHashes ; + + i ) {
if ( ! chkAssertion ( cur + HashLen < = end ) ) return ret ;
2019-12-14 12:21:31 +02:00
ret . scriptHashes . insert ( DeepCpy ( cur , HashLen ) ) ; // deep copy
2019-12-14 11:42:26 +02:00
cur + = HashLen ;
}
// 6. .addUndos, 64 bytes each * nAddUndos
ret . addUndos . reserve ( hdr - > nAddUndos ) ;
for ( unsigned i = 0 ; i < hdr - > nAddUndos ; + + i ) {
if ( ! chkAssertion ( cur + TXO : : serSize ( ) < = end ) ) return ret ;
2019-12-14 12:21:31 +02:00
TXO txo = Deserialize < TXO > ( ShallowTmp ( cur , TXO : : serSize ( ) ) , & myok ) ;
2019-12-14 11:42:26 +02:00
cur + = TXO : : serSize ( ) ;
if ( ! chkAssertion ( myok & & cur + HashLen < = end ) ) return ret ;
2019-12-14 12:21:31 +02:00
QByteArray hashX = DeepCpy ( cur , HashLen ) ; // deep copy
2019-12-14 11:42:26 +02:00
cur + = HashLen ;
if ( ! chkAssertion ( cur + CompactTXO : : serSize ( ) < = end ) ) return ret ;
2019-12-14 12:21:31 +02:00
CompactTXO ctxo = Deserialize < CompactTXO > ( ShallowTmp ( cur , CompactTXO : : serSize ( ) ) , & myok ) ;
2019-12-14 11:42:26 +02:00
cur + = CompactTXO : : serSize ( ) ;
if ( ! chkAssertion ( myok ) ) return ret ;
ret . addUndos . emplace_back ( std : : move ( txo ) , std : : move ( hashX ) , std : : move ( ctxo ) ) ;
}
// 7. .delUndos, 50 bytes each * nDelUndos
ret . delUndos . reserve ( hdr - > nDelUndos ) ;
for ( unsigned i = 0 ; i < hdr - > nDelUndos ; + + i ) {
if ( ! chkAssertion ( cur + TXO : : serSize ( ) < = end ) ) return ret ;
2019-12-14 12:21:31 +02:00
TXO txo = Deserialize < TXO > ( ShallowTmp ( cur , TXO : : serSize ( ) ) , & myok ) ;
2019-12-14 11:42:26 +02:00
cur + = TXO : : serSize ( ) ;
if ( ! chkAssertion ( myok & & cur + TXOInfo : : serSize ( ) < = end ) ) return ret ;
2019-12-14 12:21:31 +02:00
TXOInfo info = Deserialize < TXOInfo > ( ShallowTmp ( cur , TXOInfo : : serSize ( ) ) , & myok ) ;
2019-12-14 11:42:26 +02:00
cur + = TXOInfo : : serSize ( ) ;
if ( ! chkAssertion ( myok ) ) return ret ;
ret . delUndos . emplace_back ( std : : move ( txo ) , std : : move ( info ) ) ;
}
chkAssertion ( cur = = end , " cur != end " ) ;
setOk ( true ) ;
return ret ;
}
2019-12-12 00:07:29 +02:00
template < > QByteArray Serialize ( const TxNumVec & v )
{
// this serializes a vector of TxNums to a compact representation (6 bytes, eg 48 bits per TxNum), in little endian byte order
QByteArray ret ( int ( v . size ( ) * 6 ) , Qt : : Uninitialized ) ;
uint8_t * cur = reinterpret_cast < uint8_t * > ( ret . data ( ) ) ;
for ( const auto num : v ) {
2019-12-12 20:38:56 +02:00
CompactTXO : : txNumToCompactBytes ( cur , num ) ;
2019-12-12 00:07:29 +02:00
cur + = 6 ;
}
return ret ;
}
// this deserializes a vector of TxNums from a compact representation (6 bytes, eg 48 bits per TxNum), assuming little endian byte order
2019-12-12 16:58:31 +02:00
template < > TxNumVec Deserialize ( const QByteArray & ba , bool * ok )
2019-12-12 00:07:29 +02:00
{
const size_t blen = size_t ( ba . length ( ) ) ;
const size_t N = blen / 6 ;
TxNumVec ret ;
if ( N * 6 ! = blen ) {
// wrong size, not multiple of 6; bail
if ( ok ) * ok = false ;
return ret ;
}
if ( ok ) * ok = true ;
const uint8_t * cur = reinterpret_cast < const uint8_t * > ( ba . begin ( ) ) , * end = reinterpret_cast < const uint8_t * > ( ba . end ( ) ) ;
ret . reserve ( N ) ;
for ( ; cur < end ; cur + = 6 ) {
2019-12-12 20:38:56 +02:00
ret . emplace_back ( CompactTXO : : txNumFromCompactBytes ( cur ) ) ;
2019-12-12 00:07:29 +02:00
}
return ret ;
}
2019-12-12 23:09:54 +02:00
2019-12-14 11:42:26 +02:00
template < > QByteArray Serialize ( const CompactTXO & c ) { return c . toBytes ( ) ; }
template < > CompactTXO Deserialize ( const QByteArray & b , bool * ok ) {
2019-12-12 23:09:54 +02:00
CompactTXO ret = CompactTXO : : fromBytes ( b ) ;
if ( ok ) * ok = ret . isValid ( ) ;
return ret ;
}
2019-11-29 14:42:17 +02:00
}