2019-12-28 23:46:05 +02:00
//
2019-12-28 23:48:24 +02:00
// Fulcrum - A fast & nimble SPV Server for Bitcoin Cash
2019-12-28 23:46:05 +02:00
// Copyright (C) 2019-2020 Calin A. Culianu <calin.culianu@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program (see LICENSE.txt). If not, see
// <https://www.gnu.org/licenses/>.
//
2019-11-29 14:42:17 +02:00
# include "BTC.h"
2019-12-24 13:18:44 +02:00
# include "CostCache.h"
2019-12-28 15:10:07 +02:00
# include "Mempool.h"
2019-12-22 01:20:24 +02:00
# include "Merkle.h"
2019-12-11 11:32:46 +02:00
# include "RecordFile.h"
2019-11-29 14:42:17 +02:00
# include "Storage.h"
2019-12-30 10:33:35 +02:00
# include "SubsMgr.h"
2019-11-29 14:42:17 +02:00
# 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>
2019-12-24 13:18:44 +02:00
# include <QVector> // we use this for the Height2Hash cache to save on memcopies since it's implicitly shared.
2019-11-29 14:42:17 +02:00
# 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-12-15 12:33:02 +02:00
HeaderVerificationFailure : : ~ HeaderVerificationFailure ( ) { } // weak vtable warning suppression
2019-12-15 18:19:38 +02:00
UndoInfoMissing : : ~ UndoInfoMissing ( ) { } // weak vtable warning suppression
2019-12-30 11:30:06 +02:00
HistoryTooLarge : : ~ HistoryTooLarge ( ) { } // 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-22 10:55:24 +02:00
static const bool falseMem = false , trueMem = true ;
2019-12-22 11:40:25 +02:00
static const rocksdb : : Slice kMeta { " meta " } , kDirty { " dirty " } , kUtxoCount { " utxo_count " } ,
2019-12-22 10:55:24 +02:00
kTrue ( reinterpret_cast < const char * > ( & trueMem ) , sizeof ( trueMem ) ) ,
kFalse ( reinterpret_cast < const char * > ( & falseMem ) , sizeof ( trueMem ) ) ;
2019-11-29 14:42:17 +02:00
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-22 10:55:24 +02:00
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-22 10:55:24 +02:00
template < > [[maybe_unused]] QByteArray Serialize ( const bitcoin : : Amount & ) ;
template < > bitcoin : : Amount 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-15 18:25:09 +02:00
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-16 16:42:27 +02:00
/// Helper to get db name (basename of path)
QString DBName ( const rocksdb : : DB * db ) { return QFileInfo ( QString : : fromStdString ( db - > GetName ( ) ) ) . baseName ( ) ; }
/// Helper to just get the status error string as a QString
QString StatusString ( const rocksdb : : Status & status ) { return QString : : fromStdString ( status . ToString ( ) ) ; }
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 " )
2019-12-16 16:42:27 +02:00
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : QString ( " Key not found in db %1 " ) . arg ( DBName ( db ) ) )
. arg ( StatusString ( status ) ) ) ;
2019-12-03 13:57:02 +02:00
} else if ( ! status . ok ( ) ) {
throw DatabaseError ( QString ( " %1: %2 " )
2019-12-16 16:42:27 +02:00
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : QString ( " Error reading a key from db %1 " ) . arg ( DBName ( db ) ) )
. arg ( StatusString ( status ) ) ) ;
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 " )
2019-12-16 16:42:27 +02:00
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : QString ( " Database format error in db %1 " ) . arg ( DBName ( db ) ) ) ) ;
2019-12-03 15:41:09 +02:00
}
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' " )
2019-12-16 16:42:27 +02:00
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : QString ( " Error deserializing a scalar from db %1 " ) . arg ( DBName ( db ) ) )
2019-12-03 15:41:09 +02:00
. 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 " )
2019-12-16 16:42:27 +02:00
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : QString ( " Error deserializing an object from db %1 " ) . arg ( DBName ( 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 " )
2019-12-16 16:42:27 +02:00
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : QString ( " Error writing to db %1 " ) . arg ( DBName ( db ) ) )
. arg ( StatusString ( st ) ) ) ;
}
2019-12-16 18:41:34 +02:00
/// Throws on all errors. Otherwise enqueues a write to the batch.
2019-12-16 16:42:27 +02:00
template < bool safeScalar = false , typename KeyType , typename ValueType >
void GenericBatchPut
( rocksdb : : WriteBatch & batch , const KeyType & key , const ValueType & value ,
const QString & errorMsgPrefix = QString ( ) ) ///< used to specify a custom error message in the thrown exception
{
auto st = batch . Put ( ToSlice < safeScalar > ( key ) , ToSlice < safeScalar > ( value ) ) ;
if ( ! st . ok ( ) )
throw DatabaseError ( QString ( " %1: %2 " )
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : " Error from WriteBatch::Put " )
. arg ( StatusString ( st ) ) ) ;
}
2019-12-16 18:41:34 +02:00
/// Throws on all errors. Otherwise enqueues a delete to the batch.
template < bool safeScalar = false , typename KeyType >
void GenericBatchDelete
( rocksdb : : WriteBatch & batch , const KeyType & key ,
const QString & errorMsgPrefix = QString ( ) ) ///< used to specify a custom error message in the thrown exception
{
auto st = batch . Delete ( ToSlice < safeScalar > ( key ) ) ;
if ( ! st . ok ( ) )
throw DatabaseError ( QString ( " %1: %2 " )
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : " Error from WriteBatch::Delete " )
. arg ( StatusString ( st ) ) ) ;
}
/// A convenient wrapper to db->Write(batch...) which throws on all errors.
void GenericBatchWrite ( rocksdb : : DB * db , rocksdb : : WriteBatch & batch ,
2019-12-16 16:42:27 +02:00
const QString & errorMsgPrefix = QString ( ) ,
const rocksdb : : WriteOptions & opts = rocksdb : : WriteOptions ( ) )
{
auto st = db - > Write ( opts , & batch ) ;
if ( ! st . ok ( ) )
throw DatabaseError ( QString ( " %1: %2 " )
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : QString ( " Error writing batch to db %1 " ) . arg ( DBName ( db ) ) )
. arg ( StatusString ( st ) ) ) ;
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 " )
2019-12-16 16:42:27 +02:00
. arg ( ! errorMsgPrefix . isEmpty ( ) ? errorMsgPrefix : QString ( " Error deleting a key from db %1 " ) . arg ( DBName ( db ) ) )
. arg ( StatusString ( st ) ) ) ;
2019-12-15 09:08:31 +02:00
}
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 ) { }
2019-12-15 18:19:38 +02:00
bool operator = = ( const BlkInfo & o ) const { return txNum0 = = o . txNum0 & & nTx = = o . nTx ; }
bool operator ! = ( const BlkInfo & o ) const { return ! ( * this = = o ) ; }
2019-12-12 13:20:18 +02:00
[[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-17 11:27:46 +02:00
/* NOTE: If taking multiple locks, all locks should be taken in the order they are declared, to avoid deadlocks. */
2019-12-22 11:40:25 +02:00
constexpr int blockHeaderSize ( ) { return BTC : : GetBlockHeaderSize ( ) ; }
2019-12-11 00:05:16 +02:00
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-22 11:40:25 +02:00
std : : unique_ptr < rocksdb : : DB > meta , 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-12-22 11:40:25 +02:00
std : : unique_ptr < RecordFile > headersFile ;
2019-12-11 11:32:46 +02:00
2019-12-17 11:27:46 +02:00
/// Big lock used for block/history updates. Public methods that read the history such as getHistory and listUnspent
/// take this as read-only (shared), and addBlock and undoLatestBlock take this as read/write (exclusively).
/// This is intended to be a coarse lock. Currently the update code takes this along with headerVerifierLock and
/// blkInfoLock at the same time, so it's (as of now) equivalent to either of those two locks.
/// TODO: See about removing all the other locks and keeping one general RWLock for all updates?
mutable RWLock blocksLock ;
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-15 09:43:32 +02:00
std : : atomic < uint32_t > earliestUndoHeight = UINT32_MAX ; ///< the purpose of this is to control when we issue "delete" commands to the db for deleting expired undo infos from the undo db
2019-12-29 16:04:11 +02:00
/// This cache is anticipated to see heavy use for get_history, so we may wish to make it larger. MAKE THIS CONFIGURABLE.
static constexpr size_t kMaxNum2HashMemoryBytes = 100 * 1000 * 1000 ; ///< 100MB max cache
CostCache < TxNum , TxHash > lruNum2Hash { kMaxNum2HashMemoryBytes } ;
unsigned constexpr lruNum2HashSizeCalc ( unsigned nItems = 1 ) {
return decltype ( lruNum2Hash ) : : itemOverheadBytes ( ) + ( nItems * HashLen ) ;
}
2019-12-20 19:12:05 +02:00
2019-12-24 13:18:44 +02:00
static constexpr size_t kMaxHeight2HashesMemoryBytes = 100 * 1000 * 1000 ; // 100 MiB max cache
2019-12-20 20:42:45 +02:00
/// Cache BlockHeight -> vector of txHashes for the block (in bitcoind memory order). This gets cleared by
/// undoLatestBlock. This is used by the txHashesForBlock function only (which is used by get_merkle and
2019-12-20 21:28:42 +02:00
/// id_from_pos in the protocol). TODO: MAKE THIS CACHE SIZE CONFIGURABLE.
2019-12-24 02:01:55 +02:00
/// Also TODO: Maybe use a QCache here and/or use a memcost-based limit. Right now we can't predict the memory
/// cost using this size-based cache limit provided by LRU::Cache.
2019-12-24 13:18:44 +02:00
CostCache < BlockHeight , QVector < TxHash > > lruHeight2Hashes_BitcoindMemOrder { kMaxHeight2HashesMemoryBytes } ;
/// returns the cost for a particular cache item based on the number of hashes in the vector
unsigned constexpr lruHeight2HashSizeCalc ( size_t nHashes ) {
// each cache item with nHashes takes roughly this much memory
return unsigned ( ( nHashes * ( HashLen + sizeof ( TxHash ) ) ) + decltype ( lruHeight2Hashes_BitcoindMemOrder ) : : itemOverheadBytes ( ) ) ;
}
2019-12-22 02:11:16 +02:00
/// this object is thread safe, but it needs to be initialized with headers before allowing client connections.
std : : unique_ptr < Merkle : : Cache > merkleCache ;
2019-12-23 11:12:29 +02:00
HeaderHash genesisHash ; // written-to once by either loadHeaders code or addBlock for block 0. Guarded by headerVerifierLock.
2019-12-28 15:10:07 +02:00
Mempool mempool ; ///< app-wide mempool data -- does not get saved to db. Controller.cpp writes to this
RWLock mempoolLock ;
2019-11-29 14:42:17 +02:00
} ;
2019-12-30 10:33:35 +02:00
Storage : : Storage ( const std : : shared_ptr < Options > & options_ )
: Mgr ( nullptr ) , options ( options_ ) , subsmgr ( new SubsMgr ( options , this ) ) , p ( std : : make_unique < Pvt > ( ) )
2019-11-29 14:42:17 +02:00
{
setObjectName ( " Storage " ) ;
_thread . setObjectName ( objectName ( ) ) ;
}
2019-12-30 10:33:35 +02:00
Storage : : ~ Storage ( ) { Debug ( ) < < __func__ ; cleanup ( ) ; }
2019-11-29 14:42:17 +02:00
void Storage : : startup ( )
{
2019-12-22 23:59:19 +02:00
Log ( ) < < " Loading database ... " ;
2019-12-30 10:33:35 +02:00
if ( UNLIKELY ( ! subsmgr | | ! options ) )
throw BadArgs ( " Storage instance constructed with nullptr for `options` and/or `subsmgr` -- FIXME! " ) ;
subsmgr - > startup ( ) ; // trivial, always succeeds if constructed correctly
2019-12-22 01:20:24 +02:00
{
// set up the merkle cache object
using namespace std : : placeholders ;
2019-12-22 02:11:16 +02:00
p - > merkleCache = std : : make_unique < Merkle : : Cache > ( std : : bind ( & Storage : : merkleCacheHelperFunc , this , _1 , _2 , _3 ) ) ;
2019-12-22 01:20:24 +02:00
}
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 } ,
{ " 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) " )
2019-12-16 16:42:27 +02:00
. arg ( name ) . arg ( StatusString ( s ) ) . arg ( path ) ) ;
2019-12-07 20:34:19 +02:00
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-28 23:26:16 +02:00
if ( ! p - > meta . chain . isEmpty ( ) )
Log ( ) < < " Chain: " < < p - > meta . chain ;
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
}
2019-12-22 10:55:24 +02:00
if ( isDirty ( ) ) {
throw DatabaseError ( " It appears that " APPNAME " was forcefully killed in the middle of committng a block to the db. "
" We cannot figure out where exactly in the update process " APPNAME " was killed, so we "
" cannot undo the inconsistent state caused by the unexpected shutdown. Sorry! "
" \n \n The database has been corrupted. Please delete the datadir and resynch to bitcoind. \n " ) ;
}
2019-11-29 14:42:17 +02:00
}
2019-12-22 11:40:25 +02:00
// load headers -- may throw.. this must come first
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-12-15 09:43:32 +02:00
// load check earliest undo to populate earliestUndoHeight
loadCheckEarliestUndo ( ) ;
2019-11-29 14:42:17 +02:00
start ( ) ; // starts our thread
}
void Storage : : cleanup ( )
{
stop ( ) ; // joins our thread
2019-12-30 10:33:35 +02:00
if ( subsmgr ) subsmgr - > cleanup ( ) ;
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 ( ) ;
2019-12-20 21:28:42 +02:00
QVariantMap caches ;
{
2019-12-24 13:18:44 +02:00
QVariantMap m ;
2019-12-29 16:04:11 +02:00
const auto sz = p - > lruNum2Hash . size ( ) , szBytes = p - > lruNum2Hash . totalCost ( ) ;
m [ " nItems " ] = qlonglong ( sz ) ;
m [ " Size bytes " ] = qlonglong ( szBytes ) ;
2019-12-24 13:18:44 +02:00
caches [ " LRU Cache: TxNum -> TxHash " ] = m ;
2019-12-20 21:28:42 +02:00
}
{
2019-12-24 13:18:44 +02:00
QVariantMap m ;
const unsigned nItems = p - > lruHeight2Hashes_BitcoindMemOrder . size ( ) , szBytes = p - > lruHeight2Hashes_BitcoindMemOrder . totalCost ( ) ;
m [ " nBlocks " ] = nItems ;
m [ " Size bytes " ] = szBytes ;
caches [ " LRU Cache: Block Height -> TxHashes " ] = m ;
2019-12-20 21:28:42 +02:00
}
2019-12-22 01:20:24 +02:00
{
2019-12-22 02:11:16 +02:00
const size_t nHashes = p - > merkleCache - > size ( ) , bytes = nHashes * ( HashLen + sizeof ( HeaderHash ) ) ;
2019-12-22 01:20:24 +02:00
caches [ " merkleHeaders_Size " ] = qulonglong ( nHashes ) ;
caches [ " merkleHeaders_SizeBytes " ] = qulonglong ( bytes ) ;
}
2019-12-20 21:28:42 +02:00
ret [ " caches " ] = caches ;
2019-12-12 00:23:19 +02:00
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 ;
}
2019-12-28 23:26:16 +02:00
Log ( ) < < " Chain: " < < chain ;
2019-11-30 01:33:27 +02:00
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-18 17:53:16 +02:00
auto Storage : : latestTip ( Header * hdrOut ) const - > std : : pair < int , HeaderHash > {
2019-12-11 00:05:16 +02:00
std : : pair < int , HeaderHash > ret = headerVerifier ( ) . first . lastHeaderProcessed ( ) ; // ok; lock stays locked until statement end.
2019-12-18 17:53:16 +02:00
if ( hdrOut ) * hdrOut = ret . second ; // this is not a hash but the actual block header
2019-12-11 00:05:16 +02:00
if ( ret . second . isEmpty ( ) | | ret . first < 0 ) {
ret . first = - 1 ;
ret . second . clear ( ) ;
2019-12-18 17:53:16 +02:00
if ( hdrOut ) hdrOut - > clear ( ) ;
2019-12-11 00:05:16 +02:00
} 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-15 18:19:38 +02:00
void Storage : : appendHeader ( const Header & h , BlockHeight height )
2019-11-29 14:42:17 +02:00
{
2019-12-22 11:40:25 +02:00
const auto targetHeight = p - > headersFile - > numRecords ( ) ;
2019-12-11 00:05:16 +02:00
if ( UNLIKELY ( height ! = targetHeight ) )
throw InternalError ( QString ( " Bad use of appendHeader -- expected height %1, got height %2 " ) . arg ( targetHeight ) . arg ( height ) ) ;
2019-12-22 11:40:25 +02:00
QString err ;
const auto res = p - > headersFile - > appendRecord ( h , true , & err ) ;
if ( UNLIKELY ( ! err . isEmpty ( ) ) )
throw DatabaseError ( QString ( " Failed to append header %1: %2 " ) . arg ( height ) . arg ( err ) ) ;
else if ( UNLIKELY ( ! res . has_value ( ) | | res . value ( ) ! = height ) )
throw DatabaseError ( QString ( " Failed to append header %1: returned count is bad " ) . arg ( height ) ) ;
2019-12-11 00:05:16 +02:00
}
2019-12-15 18:19:38 +02:00
void Storage : : deleteHeadersPastHeight ( BlockHeight height )
{
2019-12-22 11:40:25 +02:00
QString err ;
const auto res = p - > headersFile - > truncate ( height + 1 , & err ) ;
if ( ! err . isEmpty ( ) )
throw DatabaseError ( QString ( " Failed to truncate headers past height %1: %2 " ) . arg ( height ) . arg ( err ) ) ;
else if ( res ! = height + 1 )
throw InternalError ( " header truncate returned an unexepected value " ) ;
2019-12-15 18:19:38 +02:00
}
auto Storage : : headerForHeight ( BlockHeight height , QString * err ) - > std : : optional < Header >
2019-12-11 00:05:16 +02:00
{
std : : optional < Header > ret ;
2019-12-18 17:53:16 +02:00
if ( int ( height ) < = latestTip ( ) . first & & int ( height ) > = 0 ) {
2019-12-15 18:19:38 +02:00
ret = headerForHeight_nolock ( height , err ) ;
2019-12-18 17:53:16 +02:00
} else if ( err ) { * err = QString ( " Height %1 is out of range " ) . arg ( height ) ; }
2019-12-15 18:19:38 +02:00
return ret ;
}
auto Storage : : headerForHeight_nolock ( BlockHeight height , QString * err ) - > std : : optional < Header >
{
std : : optional < Header > ret ;
try {
2019-12-22 11:40:25 +02:00
QString err1 ;
ret . emplace ( p - > headersFile - > readRecord ( height , & err1 ) ) ;
if ( ! err1 . isEmpty ( ) ) {
2019-12-15 18:19:38 +02:00
ret . reset ( ) ;
2019-12-22 11:40:25 +02:00
throw DatabaseError ( QString ( " failed to read header %1: %2 " ) . arg ( height ) . arg ( err1 ) ) ;
2019-11-29 14:42:17 +02:00
}
2019-12-15 18:19:38 +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-22 10:55:24 +02:00
auto Storage : : headersFromHeight_nolock_nocheck ( BlockHeight height , unsigned num , QString * err ) - > std : : vector < Header >
{
if ( err ) err - > clear ( ) ;
2019-12-22 11:40:25 +02:00
std : : vector < Header > ret = p - > headersFile - > readRecords ( height , num , err ) ;
2019-12-22 10:55:24 +02:00
2019-12-22 11:40:25 +02:00
if ( ret . size ( ) ! = num & & err & & err - > isEmpty ( ) )
* err = " short header count returned from headers file " ;
2019-12-22 10:55:24 +02:00
ret . shrink_to_fit ( ) ;
return ret ;
}
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.
2019-12-15 18:19:38 +02:00
auto Storage : : headersFromHeight ( BlockHeight height , unsigned count , QString * err ) - > std : : vector < Header >
2019-12-11 00:05:16 +02:00
{
std : : vector < Header > ret ;
2019-12-18 17:53:16 +02:00
SharedLockGuard g ( p - > blocksLock ) ; // to ensure clients get a consistent view
int num = std : : min ( 1 + latestTip ( ) . first - int ( height ) , int ( count ) ) ; // note this also takes a lock briefly so we need to do this after the lockguard above
2019-12-11 00:05:16 +02:00
if ( num > 0 ) {
2019-12-22 10:55:24 +02:00
ret = headersFromHeight_nolock_nocheck ( height , count , err ) ;
2019-12-11 00:05:16 +02:00
} else if ( err ) * err = " No headers in the specified range " ;
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-12-22 11:40:25 +02:00
assert ( p - > blockHeaderSize ( ) > 0 ) ;
p - > headersFile = std : : make_unique < RecordFile > ( options - > datadir + QDir : : separator ( ) + " headers " , size_t ( p - > blockHeaderSize ( ) ) , 0x00f026a1 ) ; // may throw
2019-11-29 14:42:17 +02:00
2019-12-11 00:05:16 +02:00
Log ( ) < < " Verifying headers ... " ;
2019-12-22 11:40:25 +02:00
uint32_t num = unsigned ( p - > headersFile - > numRecords ( ) ) ;
2019-12-22 10:55:24 +02:00
std : : vector < QByteArray > hVec ;
2019-11-29 14:42:17 +02:00
const auto t0 = Util : : getTimeNS ( ) ;
{
2019-12-03 15:41:09 +02:00
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-12-11 00:05:16 +02:00
Debug ( ) < < " Verifying " < < num < < " " < < Util : : Pluralize ( " header " , num ) < < " ... " ;
2019-12-22 10:55:24 +02:00
QString err ;
hVec = headersFromHeight_nolock_nocheck ( 0 , num , & err ) ;
if ( ! err . isEmpty ( ) | | hVec . size ( ) ! = num )
throw DatabaseFormatError ( QString ( " %1. Possible databaase corruption. Delete the datadir and resynch. " ) . arg ( err . isEmpty ( ) ? " Could not read all headers " : err ) ) ;
2019-12-23 11:12:29 +02:00
2019-12-22 10:55:24 +02:00
auto [ verif , lock ] = headerVerifier ( ) ;
2019-12-23 11:12:29 +02:00
// set genesis hash
p - > genesisHash = BTC : : HashRev ( hVec . front ( ) ) ;
2019-12-11 00:05:16 +02:00
const QString errMsg ( " Error retrieving header from db " ) ;
2019-12-22 10:55:24 +02:00
err . clear ( ) ;
2019-12-11 00:05:16 +02:00
// read db
for ( uint32_t i = 0 ; i < num ; + + i ) {
2019-12-22 10:55:24 +02:00
auto & bytes = hVec [ i ] ;
2019-12-11 00:05:16 +02:00
if ( ! verif ( bytes , & err ) )
2019-12-22 10:55:24 +02:00
throw DatabaseFormatError ( QString ( " %1. Possible databaase corruption. Delete the datadir and resynch. " ) . arg ( err ) ) ;
bytes = BTC : : Hash ( bytes ) ; // replace the header in the vector with its hash because it will be needed below...
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-12-22 01:20:24 +02:00
2019-12-22 10:55:24 +02:00
if ( ! p - > merkleCache - > isInitialized ( ) & & ! hVec . empty ( ) )
p - > merkleCache - > initialize ( hVec ) ; // this may take a few seconds, and it may also throw
2019-12-22 01:20:24 +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-22 10:55:24 +02:00
if ( options - > doSlowDbChecks ) {
Log ( ) < < " CheckDB: Verifying utxo set (this may take some time) ... " ;
2019-12-09 12:10:59 +02:00
2019-12-22 10:55:24 +02:00
const auto t0 = Util : : getTimeNS ( ) ;
{
const int currentHeight = latestTip ( ) . first ;
std : : unique_ptr < rocksdb : : Iterator > iter ( p - > db . utxoset - > NewIterator ( p - > db . defReadOpts ) ) ;
if ( ! iter ) throw DatabaseError ( " Unable to obtain an iterator to the utxo set db " ) ;
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. "
" \n \n Delete the datadir and resynch to bitcoind. \n " ) ;
}
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. "
" \n \n Delete the datadir and resynch to bitcoind. \n " )
. arg ( txo . toString ( ) ) ) ;
// 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 " ) ;
QByteArray tmpBa ;
if ( bool fail1 = false , fail2 = false , fail3 = false , fail4 = false ;
( fail1 = ( info . confirmedHeight . has_value ( ) & & int ( info . confirmedHeight . value ( ) ) > currentHeight ) )
| | ( fail2 = info . txNum > = p - > txNumNext )
| | ( fail3 = ( tmpBa = GenericDBGet < QByteArray > ( p - > db . shunspent . get ( ) , shuKey , true , errPrefix , false , p - > db . defReadOpts ) . value_or ( " " ) ) . isEmpty ( ) )
| | ( fail4 = ( info . amount ! = Deserialize < bitcoin : : Amount > ( tmpBa ) ) ) ) {
// TODO: reorg? Inconsisent db? FIXME
QString msg ;
{
QTextStream ts ( & msg ) ;
ts < < " Inconsistent database: txo " < < txo . toString ( ) < < " at height: "
< < info . confirmedHeight . value ( ) ;
if ( fail1 ) {
ts < < " > current height: " < < currentHeight < < " . " ;
} else if ( fail2 ) {
ts < < " . TxNum: " < < info . txNum < < " >= " < < p - > txNumNext < < " . " ;
} else if ( fail3 ) {
ts < < " . Failed to find ctxo " < < ctxo . toString ( ) < < " in the scripthash_unspent db. " ;
} else if ( fail4 ) {
ts < < " . Utxo amount does not match the ctxo amount in the scripthash_unspent db. " ;
}
ts < < " \n \n The database has been corrupted. Please delete the datadir and resynch to bitcoind. \n " ;
}
throw DatabaseError ( msg ) ;
}
if ( 0 = = + + p - > utxoCt % 100000 ) {
* ( 0 = = p - > utxoCt % 2500000 ? std : : make_unique < Log > ( ) : std : : make_unique < Debug > ( ) ) < < " CheckDB: Verified " < < p - > utxoCt < < " utxos ... " ;
2019-12-08 01:00:40 +02:00
}
}
2019-12-22 10:55:24 +02:00
if ( const auto metact = readUtxoCtFromDB ( ) ; p - > utxoCt ! = metact )
throw DatabaseError ( QString ( " UTXO count in meta table (%1) does not match the actual number of UTXOs in the utxoset (%2). "
" \n \n The database has been corrupted. Please delete the datadir and resynch to bitcoind. \n " )
. arg ( metact ) . arg ( p - > utxoCt . load ( ) ) ) ;
2019-12-08 01:00:40 +02:00
}
2019-12-22 10:55:24 +02:00
const auto elapsed = Util : : getTimeNS ( ) ;
Debug ( ) < < " CheckDB: Verified utxos in " < < QString : : number ( ( elapsed - t0 ) / 1e6 , ' f ' , 3 ) < < " msec " ;
} else {
p - > utxoCt = readUtxoCtFromDB ( ) ;
2019-12-09 12:10:59 +02:00
}
2019-12-22 10:55:24 +02:00
if ( const auto ct = utxoSetSize ( ) ; ct )
Log ( ) < < " UTXO set: " < < ct < < Util : : Pluralize ( " utxo " , ct )
< < " , " < < QString : : number ( utxoSetSizeMiB ( ) , ' f ' , 3 ) < < " MiB " ;
2019-12-09 12:10:59 +02:00
}
2019-12-08 01:00:40 +02:00
2019-12-15 09:43:32 +02:00
void Storage : : loadCheckEarliestUndo ( )
{
FatalAssert ( ! ! p - > db . undo ) < < __FUNCTION__ < < " : Undo db is not open " ;
const auto t0 = Util : : getTimeNS ( ) ;
int ctr = 0 ;
{
std : : unique_ptr < rocksdb : : Iterator > iter ( p - > db . undo - > NewIterator ( p - > db . defReadOpts ) ) ;
if ( ! iter ) throw DatabaseError ( " Unable to obtain an iterator to the undo db " ) ;
for ( iter - > SeekToFirst ( ) ; iter - > Valid ( ) ; iter - > Next ( ) ) {
const auto keySlice = iter - > key ( ) ;
if ( keySlice . size ( ) ! = sizeof ( uint32_t ) )
throw DatabaseFormatError ( " Unexpected key in undo database. We expect only 32-bit unsigned ints! " ) ;
const uint32_t height = DeserializeScalar < uint32_t > ( FromSlice ( keySlice ) ) ;
if ( height < p - > earliestUndoHeight ) p - > earliestUndoHeight = height ;
+ + ctr ;
}
}
if ( ctr ) {
Debug ( ) < < " Undo db contains " < < ctr < < " entries, earliest is " < < p - > earliestUndoHeight . load ( ) < < " , "
< < QString : : number ( ( Util : : getTimeNS ( ) - t0 ) / 1e6 , ' f ' , 2 ) < < " msec elapsed. " ;
}
}
2019-12-09 13:42:11 +02:00
2019-12-16 18:41:34 +02:00
struct Storage : : UTXOBatch : : P {
rocksdb : : WriteBatch utxosetBatch ; ///< batch writes/deletes end up in the utxoset db (keyed off TXO)
rocksdb : : WriteBatch shunspentBatch ; ///< batch writes/deletes end up in the shunspent db (keyed off HashX+CompactTXO)
int addCt = 0 , rmCt = 0 ;
bool defunct = false ;
} ;
Storage : : UTXOBatch : : UTXOBatch ( ) : p ( new P ) { }
Storage : : UTXOBatch : : UTXOBatch ( UTXOBatch & & o ) { p . swap ( o . p ) ; }
void Storage : : issueUpdates ( UTXOBatch & b )
2019-12-09 12:10:59 +02:00
{
2019-12-16 18:41:34 +02:00
static const QString errMsg1 ( " Error issuing batch write to utxoset db for a utxo update " ) ,
errMsg2 ( " Error issuing batch write to scripthash_unspent db for a utxo update " ) ;
if ( UNLIKELY ( b . p - > defunct ) )
throw InternalError ( " Misuse of Storage::issueUpdates. Cannot issue the same updates using the same context more than once. FIXME! " ) ;
assert ( bool ( p - > db . utxoset ) & & bool ( p - > db . shunspent ) ) ;
GenericBatchWrite ( p - > db . utxoset . get ( ) , b . p - > utxosetBatch , errMsg1 , p - > db . defWriteOpts ) ; // may throw
GenericBatchWrite ( p - > db . shunspent . get ( ) , b . p - > shunspentBatch , errMsg2 , p - > db . defWriteOpts ) ; // may throw
p - > utxoCt + = b . p - > addCt - b . p - > rmCt ; // tally up adds and deletes
b . p - > defunct = true ;
}
2019-12-12 20:38:56 +02:00
2019-12-16 18:41:34 +02:00
namespace {
inline QByteArray mkShunspentKey ( const QByteArray & hashX , const CompactTXO & ctxo ) {
// we do it this way for performance:
const int hxlen = hashX . length ( ) ;
2019-12-20 14:30:39 +02:00
assert ( hxlen = = HashLen ) ;
2019-12-16 18:41:34 +02:00
QByteArray key ( hxlen + int ( ctxo . serSize ( ) ) , Qt : : Uninitialized ) ;
std : : memcpy ( key . data ( ) , hashX . constData ( ) , size_t ( hxlen ) ) ;
ctxo . toBytesInPlace ( key . data ( ) + hxlen , ctxo . serSize ( ) ) ;
return key ;
2019-12-09 12:10:59 +02:00
}
}
2019-12-16 18:41:34 +02:00
void Storage : : UTXOBatch : : add ( const TXO & txo , const TXOInfo & info , const CompactTXO & ctxo )
2019-12-09 12:10:59 +02:00
{
2019-12-16 18:41:34 +02:00
{
// Update db utxoset, keyed off txo -> txoinfo
static const QString errMsgPrefix ( " Failed to add a utxo to the utxo batch " ) ;
GenericBatchPut ( p - > utxosetBatch , txo , info , errMsgPrefix ) ; // may throw on failure
}
{
// 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 (32) but suffixed with the
2019-12-18 17:53:16 +02:00
// serialized CompactTXO bytes (8). Each entry's data is a 8-byte int64_t of the amount of the utxo to save
// on lookup cost for getBalance().
2019-12-16 18:41:34 +02:00
static const QString errMsgPrefix ( " Failed to add an entry to the scripthash_unspent batch " ) ;
2019-12-22 10:55:24 +02:00
GenericBatchPut ( p - > shunspentBatch ,
mkShunspentKey ( info . hashX , ctxo ) ,
int64_t ( info . amount / info . amount . satoshi ( ) ) , ///< we do it this way because it avoids a memcpy. this is the right way: Serialize(info.amount)
2019-12-18 17:53:16 +02:00
errMsgPrefix ) ; // may throw, which is what we want
2019-12-16 18:41:34 +02:00
}
+ + p - > addCt ;
2019-12-09 12:10:59 +02:00
}
2019-12-16 18:41:34 +02:00
void Storage : : UTXOBatch : : remove ( const TXO & txo , const HashX & hashX , const CompactTXO & ctxo )
2019-12-09 12:10:59 +02:00
{
2019-12-13 02:45:45 +02:00
{
2019-12-16 18:41:34 +02:00
// enqueue delete from utxoset db -- may throw.
static const QString errMsgPrefix ( " Failed to issue a batch delete for a utxo " ) ;
GenericBatchDelete ( p - > utxosetBatch , txo , errMsgPrefix ) ;
2019-12-08 01:00:40 +02:00
}
2019-12-12 20:38:56 +02:00
{
2019-12-16 18:41:34 +02:00
// enqueue delete from scripthash_unspent db
static const QString errMsgPrefix ( " Failed to issue a batch delete for a utxo to the scripthash_unspent db " ) ;
GenericBatchDelete ( p - > shunspentBatch , mkShunspentKey ( hashX , ctxo ) , errMsgPrefix ) ;
2019-12-12 20:38:56 +02:00
}
2019-12-16 18:41:34 +02:00
+ + p - > rmCt ;
}
/// 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 ) ) ;
static const QString errMsgPrefix ( " Failed to read a utxo from the utxo db " ) ;
return GenericDBGet < TXOInfo > ( p - > db . utxoset . get ( ) , txo , ! throwIfMissing , errMsgPrefix , false , p - > db . defReadOpts ) ;
2019-12-09 12:10:59 +02:00
}
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-31 13:14:27 +02:00
void Storage : : addBlock ( PreProcessedBlockPtr ppb , bool saveUndo , unsigned nReserve , bool notifySubs )
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 ) {
2019-12-31 13:14:27 +02:00
undo = std : : make_unique < UndoInfo > ( ) ;
2019-12-14 00:47:11 +02:00
undo - > height = ppb - > height ;
}
2019-12-31 13:14:27 +02:00
using NotifySet = std : : unordered_set < HashX , HashHasher > ;
std : : unique_ptr < NotifySet > notify ;
if ( notifySubs )
notify = std : : make_unique < NotifySet > ( ) ;
// note we don't reserve here -- we will reserve at the end when we run through the hashXAggregated set one final time...
2019-12-15 09:14:56 +02:00
// take all locks now.. since this is a Big Deal. TODO: add more locks here?
2019-12-28 15:10:07 +02:00
std : : scoped_lock guard ( p - > blocksLock , p - > headerVerifierLock , p - > blkInfoLock , p - > mempoolLock ) ;
p - > mempool . clear ( ) ; // just make sure the mempool is clean
2019-12-15 09:14:56 +02:00
2019-12-15 12:33:02 +02:00
const auto verifUndo = p - > headerVerifier ; // keep a copy of verifier state for undo purposes in case this fails
// This object ensures that if an exception is thrown while we are in the below code, we undo the header verifier
// and return it to its previous state. Note the defer'd functor is called with the above scoped_lock held.
Defer undoVerifierOnScopeEnd ( [ & verifUndo , this ] { p - > headerVerifier = verifUndo ; } ) ;
2019-12-07 20:34:19 +02:00
2019-12-15 12:33:02 +02:00
// code in the below block may throw -- exceptions are propagated out to caller.
{
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 ) ) {
2019-12-15 12:33:02 +02:00
// XXX possible reorg point. Caller will/should roll back the db state via issuing calls to undoLatestBlock()
throw HeaderVerificationFailure ( errMsg ) ;
2019-12-07 20:34:19 +02:00
}
2019-12-15 12:33:02 +02:00
// save raw header back to our buffer -- this will be used at the end of this function to add it to the db
// after everything completes successfully.
2019-12-07 20:34:19 +02:00
rawHeader = p - > headerVerifier . lastHeaderProcessed ( ) . second ;
}
2019-12-22 10:55:24 +02:00
setDirty ( true ) ; // <-- no turning back. if the app crashes unexpectedly while this is set, on next restart it will refuse to run and insist on a clean resynch.
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-15 23:40:52 +02:00
throw InternalError ( QString ( " Batch append for txNums failed: %1. " ) . 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
2019-12-16 18:41:34 +02:00
// update utxoSet & scritphash history
2019-12-07 20:34:19 +02:00
{
2019-12-16 18:41:34 +02:00
std : : unordered_set < HashX , HashHasher > newHashXInputsResolved ;
2019-12-31 13:14:27 +02:00
newHashXInputsResolved . reserve ( 1024 ) ; ///< todo: tune this magic number?
2019-12-14 00:47:11 +02:00
2019-12-16 18:41:34 +02:00
{
// utxo batch block (updtes utxoset & scripthash_unspent tables)
UTXOBatch utxoBatch ;
2019-12-07 20:34:19 +02:00
2019-12-16 18:41:34 +02:00
// reserve space in undo, if in saveUndo mode
if ( undo ) {
undo - > addUndos . reserve ( ppb - > outputs . size ( ) ) ;
undo - > delUndos . reserve ( ppb - > inputs . size ( ) ) ;
}
// add outputs
for ( const auto & [ hashX , ag ] : ppb - > hashXAggregated ) {
for ( const auto oidx : ag . outs ) {
const auto & out = ppb - > outputs [ oidx ] ;
if ( out . spentInInputIndex . has_value ( ) ) {
if constexpr ( debugPrt )
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 ( ) < < " ) " ;
continue ;
}
const TxHash & hash = ppb - > txInfos [ out . txIdx ] . hash ;
TXOInfo info ;
info . hashX = hashX ;
info . amount = out . amount ;
info . confirmedHeight = ppb - > height ;
info . txNum = blockTxNum0 + out . txIdx ;
const TXO txo { hash , out . outN } ;
const CompactTXO ctxo ( info . txNum , txo . prevoutN ) ;
utxoBatch . add ( 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-09 10:04:30 +02:00
if constexpr ( debugPrt )
2019-12-16 18:41:34 +02:00
Debug ( ) < < " Added txo: " < < txo . toString ( )
< < " (txid: " < < hash . toHex ( ) < < " height: " < < ppb - > height < < " ) "
< < " amount: " < < info . amount . ToString ( ) < < " for HashX: " < < info . hashX . toHex ( ) ;
2019-12-14 00:47:11 +02:00
}
2019-12-07 20:34:19 +02:00
}
2019-12-12 00:07:29 +02:00
2019-12-16 18:41:34 +02:00
// add spends (process inputs)
unsigned inum = 0 ;
for ( auto & in : ppb - > inputs ) {
const TXO txo { in . prevoutHash , in . prevoutN } ;
if ( ! inum ) {
// coinbase.. skip
} 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 )
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 ( ) ;
if ( info . confirmedHeight . has_value ( ) & & info . confirmedHeight . value ( ) ! = ppb - > height ) {
// 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.
auto & ag = ppb - > hashXAggregated [ info . hashX ] ;
ag . ins . emplace_back ( inum ) ;
newHashXInputsResolved . insert ( info . hashX ) ;
// mark its txidx
if ( auto & vec = ag . txNumsInvolvingHashX ; vec . empty ( ) | | vec . back ( ) ! = in . txIdx )
vec . emplace_back ( in . txIdx ) ;
}
if constexpr ( debugPrt ) {
const auto dbgTxIdHex = ppb - > txHashForInputIdx ( inum ) . toHex ( ) ;
Debug ( ) < < " Spent " < < txo . toString ( ) < < " amount: " < < info . amount . ToString ( )
< < " in txid: " < < dbgTxIdHex < < " height: " < < ppb - > height
< < " input number: " < < ppb - > numForInputIdx ( inum ) . value_or ( 0xffff )
< < " HashX: " < < info . hashX . toHex ( ) ;
}
// delete from db
utxoBatch . remove ( txo , info . hashX , CompactTXO ( info . txNum , txo . prevoutN ) ) ; // delete from db
if ( undo ) { // save undo info, if we are in saveUndo mode
undo - > delUndos . emplace_back ( txo , info ) ;
}
} 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 InternalError ( s ) ;
2019-12-07 20:34:19 +02:00
}
2019-12-16 18:41:34 +02:00
+ + inum ;
2019-12-07 20:34:19 +02:00
}
2019-12-16 18:41:34 +02:00
// commit the utxoset updates now.. this issues the writes to the db and also updates
// p->utxoCt. This may throw.
issueUpdates ( utxoBatch ) ;
2019-12-07 20:34:19 +02:00
}
// 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.
2019-12-31 13:14:27 +02:00
if ( notify )
// first, reserve space for notifications
notify - > reserve ( ppb - > hashXAggregated . size ( ) ) ;
2019-12-12 00:07:29 +02:00
rocksdb : : WriteBatch batch ;
for ( auto & [ hashX , ag ] : ppb - > hashXAggregated ) {
2019-12-31 13:14:27 +02:00
if ( notify ) notify - > insert ( hashX ) ; // fast O(1) insertion because we reserved the right size above.
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 " )
2019-12-16 16:42:27 +02:00
. arg ( QString ( hashX . toHex ( ) ) ) . arg ( ppb - > height ) . arg ( StatusString ( st ) ) ) ;
2019-12-12 00:07:29 +02:00
}
if ( auto st = p - > db . shist - > Write ( p - > db . defWriteOpts , & batch ) ; ! st . ok ( ) )
throw DatabaseError ( QString ( " batch merge fail for block height %1: %2 " )
2019-12-16 16:42:27 +02:00
. arg ( ppb - > height ) . arg ( StatusString ( st ) ) ) ;
2019-12-12 00:07:29 +02:00
}
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-15 09:43:32 +02:00
if ( ppb - > height < p - > earliestUndoHeight ) {
// remember earliest for delete clause below...
p - > earliestUndoHeight = ppb - > height ;
}
2019-12-15 09:08:31 +02:00
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:43:32 +02:00
// Expire old undos >10 blocks ago to keep the db tidy. We only do this if we know there is an old
// undo for said height in db.
2019-12-24 21:15:48 +02:00
if ( const auto expireUndoHeight = int ( ppb - > height ) - int ( configuredUndoDepth ( ) ) ;
2019-12-15 09:43:32 +02:00
expireUndoHeight > = 0 & & unsigned ( expireUndoHeight ) > = p - > earliestUndoHeight ) {
2019-12-15 20:52:21 +02:00
// FIXME -- this runs for every block in between the last undo save and current tip.
// If the node was off for a while then restarted this just hits the db with useless deletes for non-existant
// keys as we catch up. It's not the end of the world, as each call here is on the order of microseconds..
// but perhaps we need to see about fixing this to not do that.
2019-12-15 09:08:31 +02:00
static const QString errPrefix ( " Error deleting old/stale undo info from undo db " ) ;
2019-12-15 09:43:32 +02:00
GenericDBDelete ( p - > db . undo . get ( ) , uint32_t ( expireUndoHeight ) , errPrefix , p - > db . defWriteOpts ) ;
p - > earliestUndoHeight = unsigned ( expireUndoHeight + 1 ) ;
2019-12-15 20:52:21 +02:00
if constexpr ( debugPrt ) Debug ( ) < < " Deleted undo for block " < < expireUndoHeight < < " , earliest now " < < p - > earliestUndoHeight . load ( ) ;
2019-12-15 09:08:31 +02:00
}
2019-12-07 20:34:19 +02:00
2019-12-11 00:05:16 +02:00
appendHeader ( rawHeader , ppb - > height ) ;
2019-12-23 11:12:29 +02:00
if ( UNLIKELY ( ppb - > height = = 0 ) ) {
// update genesis hash now if block 0 -- this info is used by rpc method server.features
p - > genesisHash = BTC : : HashRev ( rawHeader ) ; // this variable is guarded by p->headerVerifierLock
}
2019-12-22 10:55:24 +02:00
saveUtxoCt ( ) ;
setDirty ( false ) ;
2019-12-15 18:19:38 +02:00
undoVerifierOnScopeEnd . disable ( ) ; // indicate to the "Defer" object declared at the top of this function that it shouldn't undo anything anymore as we are happy now with the db state now.
2019-12-31 13:14:27 +02:00
} /// release locks
// now, do notifications
if ( notify & & subsmgr & & ! notify - > empty ( ) )
subsmgr - > enqueueNotifications ( std : : move ( * notify ) ) ;
2019-12-07 20:34:19 +02:00
}
2019-12-31 13:14:27 +02:00
BlockHeight Storage : : undoLatestBlock ( bool notifySubs )
2019-12-15 18:19:38 +02:00
{
2019-12-31 13:14:27 +02:00
BlockHeight prevHeight { 0 } ;
std : : unique_ptr < UndoInfo : : ScriptHashSet > notify ;
if ( notifySubs )
notify = std : : make_unique < UndoInfo : : ScriptHashSet > ( ) ;
2019-12-28 15:10:07 +02:00
2019-12-31 13:14:27 +02:00
{
// take all locks now.. since this is a Big Deal. TODO: add more locks here?
std : : scoped_lock guard ( p - > blocksLock , p - > headerVerifierLock , p - > blkInfoLock , p - > mempoolLock ) ;
2019-12-15 18:19:38 +02:00
2019-12-31 13:14:27 +02:00
p - > mempool . clear ( ) ; // make sure mempool is clean
2019-12-15 18:19:38 +02:00
2019-12-31 13:14:27 +02:00
const auto t0 = Util : : getTimeNS ( ) ;
const auto [ tip , header ] = p - > headerVerifier . lastHeaderProcessed ( ) ;
if ( tip < = 0 | | header . length ( ) ! = p - > blockHeaderSize ( ) ) throw UndoInfoMissing ( " No header to undo " ) ;
prevHeight = unsigned ( tip - 1 ) ;
Header prevHeader ;
{
// grab previous header now
QString err ;
auto opt = headerForHeight_nolock ( prevHeight , & err ) ;
if ( ! opt . has_value ( ) ) throw UndoInfoMissing ( err ) ;
prevHeader = opt . value ( ) ;
}
const QString errMsg1 = QString ( " Unable to retrieve undo info for %1 " ) . arg ( tip ) ;
auto undoOpt = GenericDBGet < UndoInfo > ( p - > db . undo . get ( ) , uint32_t ( tip ) , true , errMsg1 , false , p - > db . defReadOpts ) ;
if ( ! undoOpt . has_value ( ) )
throw UndoInfoMissing ( errMsg1 ) ;
auto & undo = undoOpt . value ( ) ; // non-const because we swap out its scripthashes potentially below if notifySubs == true
// ensure undo info sanity
if ( ! undo . isValid ( ) | | undo . height ! = unsigned ( tip ) | | undo . hash ! = BTC : : HashRev ( header )
| | prevHeight + 1 > = p - > blkInfos . size ( ) | | p - > blkInfos . empty ( ) | | p - > blkInfos . back ( ) ! = undo . blkInfo )
throw DatabaseFormatError ( QString ( " The undo information for height %1 was successfully retrieved from the "
" database, but it failed an internal consistency check. " ) . arg ( tip ) ) ;
{
// all sanity check passed. Now, undo things in reverse order of what we did in addBlock above, rougly speaking
// first, undo the header
p - > headerVerifier . reset ( prevHeight + 1 , prevHeader ) ;
setDirty ( true ) ; // <-- no turning back. we clear this flag at the end
deleteHeadersPastHeight ( prevHeight ) ; // commit change to db
p - > merkleCache - > truncate ( prevHeight + 1 ) ; // this takes a length, not a height, which is always +1 the height
// undo the blkInfo from the back
p - > blkInfos . pop_back ( ) ;
p - > blkInfosByTxNum . erase ( undo . blkInfo . txNum0 ) ;
GenericDBDelete ( p - > db . blkinfo . get ( ) , uint32_t ( undo . height ) , " Failed to delete blkInfo in undoLatestBlock " ) ;
// clear num2hash cache
p - > lruNum2Hash . clear ( ) ;
// remove block from txHashes cache
p - > lruHeight2Hashes_BitcoindMemOrder . remove ( undo . height ) ;
const auto txNum0 = undo . blkInfo . txNum0 ;
// undo the scripthash histories
for ( const auto & sh : undo . scriptHashes ) {
const QString shHex = Util : : ToHexFast ( sh ) ;
const auto vec = GenericDBGetFailIfMissing < TxNumVec > ( p - > db . shist . get ( ) , sh , QString ( " Undo failed because we failed to retrieve the scripthash history for %1 " ) . arg ( shHex ) , false , p - > db . defReadOpts ) ;
TxNumVec newVec ;
newVec . reserve ( vec . size ( ) ) ;
for ( const auto txNum : vec ) {
if ( txNum < txNum0 ) {
// accept only stuff in history that's before txNum0 for this block, filter out everything else
newVec . push_back ( txNum ) ;
}
}
const QString errMsg = QString ( " Undo failed because we failed to write the new scripthash history for %1 " ) . arg ( shHex ) ;
if ( ! newVec . empty ( ) ) {
// The below is entirely unnecessary as the txnums should be already sorted and unique in the db data.
// We are doing this here to illustrate that this invariant in the data is very important.
// Block undo is intended to be an infrequent process (and thus not especially performance-critical),
// so this does no harm.
std : : sort ( newVec . begin ( ) , newVec . end ( ) ) ;
auto last = std : : unique ( newVec . begin ( ) , newVec . end ( ) ) ;
newVec . erase ( last , newVec . end ( ) ) ;
}
if ( ! newVec . empty ( ) ) {
// the sh still has some history, write it to db
GenericDBPut ( p - > db . shist . get ( ) , sh , newVec , errMsg , p - > db . defWriteOpts ) ;
} else {
// the sh in question lost all its history as a result of undo, just delete it from db to save space
GenericDBDelete ( p - > db . shist . get ( ) , sh , errMsg , p - > db . defWriteOpts ) ;
2019-12-15 20:52:21 +02:00
}
}
2019-12-31 13:14:27 +02:00
{
// UTXO set update
UTXOBatch utxoBatch ;
2019-12-15 20:52:21 +02:00
2019-12-31 13:14:27 +02:00
// now, undo the utxo deletions by re-adding them
for ( const auto & [ txo , info ] : undo . delUndos ) {
// note that deletions may have an info with a txnum before this block, for obvious reasons
utxoBatch . add ( txo , info , CompactTXO ( info . txNum , txo . prevoutN ) ) ; // may throw
}
// now, undo the utxo additions by deleting them
for ( const auto & [ txo , hashx , ctxo ] : undo . addUndos ) {
assert ( ctxo . txNum ( ) > = txNum0 ) ; // all of the additions must have been in this block or newer
utxoBatch . remove ( txo , hashx , ctxo ) ; // may throw
}
2019-12-15 20:52:21 +02:00
2019-12-31 13:14:27 +02:00
issueUpdates ( utxoBatch ) ; // may throw, updates p->utxoCt and issues write to db.
2019-12-16 18:41:34 +02:00
}
2019-12-31 13:14:27 +02:00
if ( p - > earliestUndoHeight > = undo . height )
// oops, we're out of undos now!
p - > earliestUndoHeight = UINT_MAX ;
GenericDBDelete ( p - > db . undo . get ( ) , uint32_t ( undo . height ) ) ; // make sure to delete this undo info since it was just applied.
2019-12-15 18:19:38 +02:00
2019-12-31 13:14:27 +02:00
// lastly, truncate the tx num file and re-set txNumNext to point to this block's txNum0 (thereby recycling it)
assert ( long ( p - > txNumNext ) - long ( txNum0 ) = = long ( undo . blkInfo . nTx ) ) ;
p - > txNumNext = txNum0 ;
if ( QString err ; p - > txNumsFile - > truncate ( txNum0 , & err ) ! = txNum0 | | ! err . isEmpty ( ) ) {
throw InternalError ( QString ( " Failed to truncate txNumsFile to %1: %2 " ) . arg ( txNum0 ) . arg ( err ) ) ;
}
2019-12-15 20:52:21 +02:00
2019-12-31 13:14:27 +02:00
saveUtxoCt ( ) ;
setDirty ( false ) ; // phew. done.
2019-12-22 10:55:24 +02:00
2019-12-31 13:14:27 +02:00
if ( notify )
notify - > swap ( undo . scriptHashes ) ;
}
2019-12-15 18:19:38 +02:00
2019-12-31 13:14:27 +02:00
const size_t nTx = undo . blkInfo . nTx , nSH = undo . scriptHashes . size ( ) ;
const auto elapsedms = ( Util : : getTimeNS ( ) - t0 ) / 1e6 ;
Log ( ) < < " Applied undo for block " < < undo . height < < " hash " < < Util : : ToHexFast ( undo . hash ) < < " , "
< < nTx < < " " < < Util : : Pluralize ( " transaction " , nTx )
< < " involving " < < nSH < < " " < < Util : : Pluralize ( " scripthash " , nSH )
< < " , in " < < QString : : number ( elapsedms , ' f ' , 2 ) < < " msec, new height now: " < < prevHeight ;
} // release locks
// now, do notifications
if ( notify & & subsmgr & & ! notify - > empty ( ) )
subsmgr - > enqueueNotifications ( std : : move ( * notify ) ) ;
2019-12-15 18:19:38 +02:00
return prevHeight ;
}
2019-12-22 10:55:24 +02:00
void Storage : : setDirty ( bool dirtyFlag )
{
static const QString errPrefix ( " Error saving dirty flag to the meta db " ) ;
const auto & val = dirtyFlag ? kTrue : kFalse ;
GenericDBPut ( p - > db . meta . get ( ) , kDirty , val , errPrefix , p - > db . defWriteOpts ) ;
}
bool Storage : : isDirty ( ) const
{
static const QString errPrefix ( " Error reading dirty flag from the meta db " ) ;
return GenericDBGet < bool > ( p - > db . meta . get ( ) , kDirty , true , errPrefix , false , p - > db . defReadOpts ) . value_or ( false ) ;
}
void Storage : : saveUtxoCt ( )
{
static const QString errPrefix ( " Error writing the utxo count to the meta db " ) ;
const int64_t ct = p - > utxoCt . load ( ) ;
GenericDBPut ( p - > db . meta . get ( ) , kUtxoCount , ct , errPrefix , p - > db . defWriteOpts ) ;
}
int64_t Storage : : readUtxoCtFromDB ( ) const
{
static const QString errPrefix ( " Error reading the utxo count from the meta db " ) ;
return GenericDBGet < int64_t > ( p - > db . meta . get ( ) , kUtxoCount , true , errPrefix , false , p - > db . defReadOpts ) . value_or ( 0LL ) ;
}
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 ;
2019-12-29 16:04:11 +02:00
if ( ! skipCache ) ret = p - > lruNum2Hash . object ( 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-29 16:04:11 +02:00
p - > lruNum2Hash . insert ( n , ret . value ( ) , p - > lruNum2HashSizeCalc ( ) ) ;
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-19 14:29:48 +02:00
std : : optional < TxHash > Storage : : hashForHeightAndPos ( BlockHeight height , unsigned posInBlock ) const
{
std : : optional < TxHash > ret ;
TxNum txNum = 0 ;
SharedLockGuard ( p - > blocksLock ) ; // guarantee a consistent view (so that data doesn't mutate from underneath us)
{
SharedLockGuard g ( p - > blkInfoLock ) ;
if ( height > = p - > blkInfos . size ( ) )
return ret ;
const BlkInfo & bi = p - > blkInfos [ height ] ;
if ( posInBlock > = bi . nTx )
return ret ;
txNum = bi . txNum0 + posInBlock ;
}
ret = hashForTxNum ( txNum ) ;
return ret ;
}
2019-12-12 12:07:18 +02:00
2019-12-20 20:42:45 +02:00
// NOTE: the returned vector has hashes in bitcoind memory order (unlike every other function in this file!)
std : : vector < TxHash > Storage : : txHashesForBlockInBitcoindMemoryOrder ( BlockHeight height ) const
2019-12-20 19:12:05 +02:00
{
std : : vector < TxHash > ret ;
std : : pair < TxNum , size_t > startCount { 0 , 0 } ;
SharedLockGuard ( p - > blocksLock ) ; // guarantee a consistent view (so that data doesn't mutate from underneath us)
{
// check cache
2019-12-24 13:18:44 +02:00
auto opt = p - > lruHeight2Hashes_BitcoindMemOrder . object ( height ) ;
2019-12-20 19:12:05 +02:00
if ( opt . has_value ( ) ) {
// cache hit! return the cached item
2019-12-24 13:18:44 +02:00
auto & vec = opt . value ( ) ;
// convert from QVector to std::vector -- TODO: see if we can make the whole call path use QVector to avoid
// these copies.
ret . reserve ( size_t ( vec . size ( ) ) ) ;
ret . insert ( ret . end ( ) , vec . begin ( ) , vec . end ( ) ) ; // We do it this way because QVector::toStdVector() doesn't reserve() first :/
2019-12-20 19:12:05 +02:00
return ret ;
}
}
{
SharedLockGuard g ( p - > blkInfoLock ) ;
if ( height > = p - > blkInfos . size ( ) )
return ret ;
const BlkInfo & bi = p - > blkInfos [ height ] ;
startCount = { bi . txNum0 , bi . nTx } ;
}
QString err ;
auto vec = p - > txNumsFile - > readRecords ( startCount . first , startCount . second , & err ) ;
if ( vec . size ( ) ! = startCount . second | | ! err . isEmpty ( ) ) {
Warning ( ) < < " Failed to read " < < startCount . second < < " txNums for height " < < height < < " . " < < err ;
return ret ;
}
2019-12-20 20:42:45 +02:00
Util : : reverseEachItem ( vec ) ; // reverse each hash to make them all be in bitcoind memory order.
2019-12-20 19:12:05 +02:00
ret . swap ( vec ) ;
{
// put result in cache
2019-12-24 13:18:44 +02:00
p - > lruHeight2Hashes_BitcoindMemOrder . insert ( height , QVector < TxHash > : : fromStdVector ( ret ) , p - > lruHeight2HashSizeCalc ( ret . size ( ) ) ) ;
2019-12-20 19:12:05 +02:00
}
return ret ;
}
2019-12-28 15:10:07 +02:00
auto Storage : : getHistory ( const HashX & hashX , bool conf , bool unconf ) const - > History
2019-12-12 12:07:18 +02:00
{
History ret ;
2019-12-18 17:53:16 +02:00
if ( hashX . length ( ) ! = HashLen )
return ret ;
2019-12-12 12:07:18 +02:00
try {
2019-12-17 11:27:46 +02:00
SharedLockGuard g ( p - > blocksLock ) ; // makes sure history doesn't mutate from underneath our feet
2019-12-28 15:10:07 +02:00
if ( conf ) {
static const QString err ( " Error retrieving history for a script hash " ) ;
auto nums_opt = GenericDBGet < TxNumVec > ( p - > db . shist . get ( ) , hashX , true , err , false , p - > db . defReadOpts ) ;
if ( nums_opt . has_value ( ) ) {
auto & nums = nums_opt . value ( ) ;
2019-12-30 11:30:06 +02:00
if ( UNLIKELY ( nums . size ( ) > MaxHistory ) ) {
throw HistoryTooLarge ( QString ( " History for scripthash %1 exceeds MaxHistory %2 with %3 items! " )
. arg ( QString ( hashX . toHex ( ) ) ) . arg ( MaxHistory ) . arg ( nums . size ( ) ) ) ;
}
2019-12-28 15:10:07 +02:00
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 , int ( height ) , { } } ) ;
}
}
}
if ( unconf ) {
auto [ mempool , lock ] = this - > mempool ( ) ;
if ( auto it = mempool . hashXTxs . find ( hashX ) ; it ! = mempool . hashXTxs . end ( ) ) {
2019-12-31 04:17:06 +02:00
const auto & txvec = it - > second ;
const size_t total = ret . size ( ) + txvec . size ( ) ;
2019-12-30 11:30:06 +02:00
if ( UNLIKELY ( total > MaxHistory ) ) {
throw HistoryTooLarge ( QString ( " History for scripthash %1 exceeds MaxHistory %2 with %3 items! " )
. arg ( QString ( hashX . toHex ( ) ) ) . arg ( MaxHistory ) . arg ( total ) ) ;
}
ret . reserve ( total ) ;
2019-12-31 04:17:06 +02:00
for ( const auto & tx : txvec )
2019-12-28 15:10:07 +02:00
ret . emplace_back ( HistoryItem { tx - > hash , tx - > ancestorCount > 1 ? - 1 : 0 , tx - > fee } ) ;
2019-12-12 12:07:18 +02:00
}
}
} 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 ;
2019-12-18 17:53:16 +02:00
if ( hashX . length ( ) ! = HashLen )
return ret ;
2019-12-13 00:14:09 +02:00
try {
2019-12-17 11:27:46 +02:00
constexpr size_t iota = 10 ; // we initially reserve this many items in the returned array in order to prevent redundant allocations in the common case.
{
// take shared lock (ensure history doesn't mutate from underneath our feet)
SharedLockGuard g ( p - > blocksLock ) ;
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 ;
bool didReserveIota = false ;
2019-12-28 23:26:16 +02:00
TxNum maxTxNumSeen = 0 ;
2019-12-30 11:30:06 +02:00
std : : list < const CompactTXO > ctxoList ;
size_t ctxoListSize = 0 ;
// we do it this way as two separate loops in order to avoid the expensive heightForTxNum lookups below in
// the case where the history is huge.
2019-12-17 11:27:46 +02:00
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-30 11:30:06 +02:00
ctxoList . emplace_back ( ctxo ) ;
if ( UNLIKELY ( + + ctxoListSize > MaxHistory ) ) {
throw HistoryTooLarge ( QString ( " Unspent history too large for %1, exceeds MaxHistory of %2 " )
. arg ( QString ( hashX . toHex ( ) ) ) . arg ( MaxHistory ) ) ;
}
}
for ( const auto & ctxo : ctxoList ) {
2019-12-17 11:27:46 +02:00
static const QString err ( " Error retrieving the utxo for an unspent item " ) ;
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
if ( ! didReserveIota ) {
// small performance tweak -- reserve iota (10) initially since most unspent lists are <10 in db.
didReserveIota = true ;
ret . reserve ( iota ) ;
}
2019-12-28 23:26:16 +02:00
maxTxNumSeen = std : : max ( info . txNum , maxTxNumSeen ) ;
2019-12-17 11:27:46 +02:00
ret . emplace_back ( UnspentItem {
2019-12-28 15:10:07 +02:00
{ hash , int ( height ) , { } } , // base HistoryItem
2019-12-17 11:27:46 +02:00
ctxo . N ( ) , // .tx_pos
info . amount , // .value
info . txNum , // .txNum
} ) ;
}
2019-12-28 15:10:07 +02:00
{
// grab mempool utxos for scripthash
auto [ mempool , lock ] = this - > mempool ( ) ; // shared lock
if ( auto it = mempool . hashXTxs . find ( hashX ) ; it ! = mempool . hashXTxs . end ( ) ) {
2019-12-31 04:17:06 +02:00
const auto & txvec = it - > second ;
for ( const auto & tx : txvec ) {
2019-12-28 15:10:07 +02:00
if ( ! tx ) {
// defensive programming. should never happen
Warning ( ) < < " Cannot find tx for sh " < < hashX . toHex ( ) < < " . FIXME!! " ;
continue ;
}
2019-12-31 16:22:52 +02:00
if ( auto it2 = tx - > hashXs . find ( hashX ) ; LIKELY ( it2 ! = tx - > hashXs . end ( ) ) ) {
2019-12-28 15:10:07 +02:00
const auto & ioinfo = it2 - > second ;
for ( const auto ionum : ioinfo . utxo ) {
2019-12-31 16:22:52 +02:00
if ( decltype ( tx - > txos . cbegin ( ) ) it3 ;
LIKELY ( ionum < tx - > txos . size ( ) & & ( it3 = tx - > txos . cbegin ( ) + ionum ) - > isValid ( ) ) )
{
2019-12-28 15:10:07 +02:00
ret . emplace_back ( UnspentItem {
{ tx - > hash , 0 /* always put 0 for height here */ , tx - > fee } , // base HistoryItem
ionum , // .tx_pos
2019-12-31 16:22:52 +02:00
it3 - > amount , // .value
2019-12-28 23:26:16 +02:00
TxNum ( 1 ) + maxTxNumSeen + TxNum ( tx - > ancestorCount ) , // .txNum (this is fudged for sorting at the end properly)
2019-12-28 15:10:07 +02:00
} ) ;
2019-12-30 11:30:06 +02:00
if ( UNLIKELY ( ret . size ( ) > MaxHistory ) ) {
throw HistoryTooLarge ( QString ( " Unspent history too large for %1, exceeds MaxHistory of %2 " )
. arg ( QString ( hashX . toHex ( ) ) ) . arg ( MaxHistory ) ) ;
}
2019-12-28 15:10:07 +02:00
} else {
// this should never happen!
Warning ( ) < < " Cannot find txo " < < ionum < < " for sh " < < hashX . toHex ( ) < < " in tx " < < tx - > hash . toHex ( ) ;
continue ;
}
}
2019-12-31 16:22:52 +02:00
} else {
// defensive programming. should never happen
Warning ( ) < < " Cannot find scripthash " < < hashX . toHex ( ) < < " in tx 'hashX -> IOInfo' map for tx " < < tx - > hash . toHex ( ) < < " . FIXME! " ;
2019-12-28 15:10:07 +02:00
}
}
}
} // release mempool lock
} // release blocks lock
2019-12-13 02:45:45 +02:00
std : : sort ( ret . begin ( ) , ret . end ( ) ) ;
2019-12-17 11:27:46 +02:00
if ( const auto sz = ret . size ( ) , cap = ret . capacity ( ) ; cap - sz > iota & & double ( cap ) / double ( sz ) > 1.20 )
// we only do this if we're wasting enough space (at least iota, and at least 20% space wasted),
// otherwise we don't bother since this returned object is fairly ephemeral and for smallish disparities
// between capacity and size, it's fine.
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-18 17:53:16 +02:00
}
return ret ;
}
2019-12-28 16:13:24 +02:00
auto Storage : : getBalance ( const HashX & hashX ) const - > std : : pair < bitcoin : : Amount , bitcoin : : Amount >
2019-12-18 17:53:16 +02:00
{
2019-12-28 16:13:24 +02:00
std : : pair < bitcoin : : Amount , bitcoin : : Amount > ret ;
2019-12-18 17:53:16 +02:00
if ( hashX . length ( ) ! = HashLen )
return ret ;
try {
// take shared lock (ensure history doesn't mutate from underneath our feet)
SharedLockGuard g ( p - > blocksLock ) ;
2019-12-28 16:13:24 +02:00
{
// confirmed -- read from db using an iterator
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 ( QString ( " Key size for scripthash %1 is invalid " ) . arg ( QString ( hashX . toHex ( ) ) ) ) ;
const CompactTXO ctxo = CompactTXO : : fromBytes ( key . data ( ) + HashLen , CompactTXO : : serSize ( ) ) ;
if ( ! ctxo . isValid ( ) )
// should never happen, indicates db corruption
throw InternalError ( QString ( " Deserialized CompactTXO is invalid for scripthash %1 " ) . arg ( QString ( hashX . toHex ( ) ) ) ) ;
bool ok ;
const bitcoin : : Amount amount = Deserialize < bitcoin : : Amount > ( FromSlice ( iter - > value ( ) ) , & ok ) ;
if ( UNLIKELY ( ! ok ) )
throw InternalError ( QString ( " Bad amount in db for ctxo %1 (%2) " ).arg(ctxo.toString()).arg(QString(hashX.toHex()))) ;
if ( UNLIKELY ( ! bitcoin : : MoneyRange ( amount ) ) )
throw InternalError ( QString ( " Out-of-range amount in db for ctxo %1: %2 " ) . arg ( ctxo . toString ( ) ) . arg ( amount / amount . satoshi ( ) ) ) ;
ret . first + = amount ; // tally the result
}
if ( UNLIKELY ( ! bitcoin : : MoneyRange ( ret . first ) ) ) {
ret . first = bitcoin : : Amount : : zero ( ) ;
throw InternalError ( QString ( " Out-of-range total in db for getBalance on scripthash: %1 " ) . arg ( QString ( hashX . toHex ( ) ) ) ) ;
}
2019-12-18 17:53:16 +02:00
}
2019-12-28 16:13:24 +02:00
{
// unconfirmed -- check mempool
auto [ mempool , lock ] = this - > mempool ( ) ; // shared (read only) lock is held until scope end
if ( auto it = mempool . hashXTxs . find ( hashX ) ; it ! = mempool . hashXTxs . end ( ) ) {
// for all tx's involving scripthash
bitcoin : : Amount utxos , spends ;
for ( const auto & tx : it - > second ) {
2019-12-28 16:22:55 +02:00
assert ( bool ( tx ) ) ;
2019-12-28 16:13:24 +02:00
auto it2 = tx - > hashXs . find ( hashX ) ;
if ( UNLIKELY ( it2 = = tx - > hashXs . end ( ) ) ) {
2019-12-28 16:22:55 +02:00
throw InternalError ( QString ( " scripthash %1 lists tx %2, which then lacks the IOInfo for said hashX! FIXM! " )
2019-12-28 16:13:24 +02:00
. arg ( QString ( hashX . toHex ( ) ) ) . arg ( QString ( tx - > hash . toHex ( ) ) ) ) ;
}
auto & info = it2 - > second ;
for ( const auto & [ txo , txoinfo ] : info . confirmedSpends )
spends + = txoinfo . amount ;
for ( const auto ionum : info . utxo ) {
2019-12-31 16:22:52 +02:00
if ( decltype ( tx - > txos . cbegin ( ) ) it3 ; UNLIKELY ( ionum > = tx - > txos . size ( )
| | ! ( it3 = tx - > txos . cbegin ( ) + ionum ) - > isValid ( ) ) )
{
throw InternalError ( QString ( " scripthash %1 lists tx %2, which then lacks a valid TXO IONum %3 for said hashX! FIXME! " )
2019-12-28 16:13:24 +02:00
. arg ( QString ( hashX . toHex ( ) ) ) . arg ( QString ( tx - > hash . toHex ( ) ) ) . arg ( ionum ) ) ;
2019-12-31 16:22:52 +02:00
} else {
utxos + = it3 - > amount ;
}
2019-12-28 16:13:24 +02:00
}
}
ret . second = utxos - spends ; // note this may not be MoneyRange (may be negative), which is ok.
}
2019-12-18 17:53:16 +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-22 01:20:24 +02:00
std : : vector < QByteArray > Storage : : merkleCacheHelperFunc ( unsigned int start , unsigned int count , QString * err )
{
2019-12-23 09:12:17 +02:00
auto vec = headersFromHeight_nolock_nocheck ( start , count , err ) ; // despite the name of this function, it does take a small lock internally and is thread-safe. we cannot use the public one as that would potentially cause a deadlock here
2019-12-22 01:20:24 +02:00
for ( auto & ba : vec )
ba = BTC : : Hash ( ba ) ;
return vec ;
}
2019-12-22 02:11:16 +02:00
void Storage : : updateMerkleCache ( unsigned int height )
{
if ( ! p - > merkleCache - > isInitialized ( ) & & height ) {
try {
p - > merkleCache - > initialize ( height + 1 ) ; // this may take a few seconds
} catch ( const std : : exception & e ) {
Error ( ) < < e . what ( ) ;
}
}
}
Merkle : : BranchAndRootPair Storage : : headerBranchAndRoot ( unsigned height , unsigned cp_height )
{
// TODO: there is a potential, non-fatal race condition here where the caller tried to verify that cp_height is
// above chainTip height, but it's possible for a reorg to happen and for that to no longer be valid by the time
// the call path gets to this point. That's fine -- an exception will be thrown. This is only ever called by
// code that catches exceptions.
assert ( p - > merkleCache ) ;
return p - > merkleCache - > branchAndRoot ( cp_height + 1 , height ) ;
}
2019-12-23 11:12:29 +02:00
auto Storage : : genesisHash ( ) const - > HeaderHash
{
SharedLockGuard g ( p - > headerVerifierLock ) ;
return p - > genesisHash ;
}
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-12-28 15:10:07 +02:00
auto Storage : : mempool ( ) const - > std : : pair < const Mempool & , SharedLockGuard >
{
return { p - > mempool , SharedLockGuard { p - > mempoolLock } } ;
}
auto Storage : : mutableMempool ( ) - > std : : pair < Mempool & , ExclusiveLockGuard >
{
return { p - > mempool , ExclusiveLockGuard { p - > mempoolLock } } ;
}
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-12-22 10:55:24 +02:00
template < > QByteArray Serialize ( const bitcoin : : Amount & a ) { return SerializeScalar ( a / a . satoshi ( ) ) ; }
template < > bitcoin : : Amount Deserialize ( const QByteArray & ba , bool * ok ) {
const int64_t amt = DeserializeScalar < int64_t > ( ba , ok ) ;
return amt * bitcoin : : Amount : : satoshi ( ) ;
}
2019-12-15 18:25:09 +02:00
} // end anon namespace