2019-04-12 06:36:43 +02:00
// Copyright (c) 2014-2019, The Monero Project
2018-04-10 06:49:20 +02:00
// Copyright (c) 2018, The Loki Project
2018-02-26 17:39:40 +01:00
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2018-10-24 02:32:37 +02:00
# ifdef _WIN32
# define __STDC_FORMAT_MACROS // NOTE(loki): Explicitly define the PRIu64 macro on Mingw
# endif
2018-02-26 17:39:40 +01:00
# include <boost/range/adaptor/transformed.hpp>
# include <boost/algorithm/string.hpp>
2018-03-18 11:55:21 +01:00
# include <boost/archive/portable_binary_iarchive.hpp>
# include <boost/archive/portable_binary_oarchive.hpp>
# include "common/unordered_containers_boost_serialization.h"
2018-02-26 17:39:40 +01:00
# include "common/command_line.h"
# include "common/varint.h"
2018-03-18 11:55:21 +01:00
# include "serialization/crypto.h"
# include "cryptonote_basic/cryptonote_boost_serialization.h"
2018-02-26 17:39:40 +01:00
# include "cryptonote_core/cryptonote_core.h"
2018-12-19 02:25:48 +01:00
# include "blockchain_objects.h"
2018-02-26 17:39:40 +01:00
# include "blockchain_db/blockchain_db.h"
# include "blockchain_db/db_types.h"
# include "wallet/ringdb.h"
# include "version.h"
2018-04-10 06:49:20 +02:00
# undef LOKI_DEFAULT_LOG_CATEGORY
# define LOKI_DEFAULT_LOG_CATEGORY "bcutil"
2018-02-26 17:39:40 +01:00
namespace po = boost : : program_options ;
using namespace epee ;
using namespace cryptonote ;
2018-08-12 23:43:33 +02:00
static const char zerokey [ 8 ] = { 0 } ;
static const MDB_val zerokval = { sizeof ( zerokey ) , ( void * ) zerokey } ;
static uint64_t records_per_sync = 200 ;
static uint64_t db_flags = 0 ;
static MDB_dbi dbi_relative_rings ;
static MDB_dbi dbi_outputs ;
static MDB_dbi dbi_processed_txidx ;
static MDB_dbi dbi_spent ;
2018-11-24 11:52:20 +01:00
static MDB_dbi dbi_per_amount ;
2018-08-12 23:43:33 +02:00
static MDB_dbi dbi_ring_instances ;
2018-08-12 11:47:03 +02:00
static MDB_dbi dbi_stats ;
2018-08-12 23:43:33 +02:00
static MDB_env * env = NULL ;
2018-02-26 17:39:40 +01:00
struct output_data
{
uint64_t amount ;
2018-08-29 18:57:12 +02:00
uint64_t offset ;
output_data ( ) : amount ( 0 ) , offset ( 0 ) { }
output_data ( uint64_t a , uint64_t i ) : amount ( a ) , offset ( i ) { }
bool operator = = ( const output_data & other ) const { return other . amount = = amount & & other . offset = = offset ; }
2018-02-26 17:39:40 +01:00
} ;
2018-03-18 11:55:21 +01:00
2018-08-12 23:43:33 +02:00
//
// relative_rings: key_image -> vector<uint64_t>
// outputs: 128 bits -> set of key images
// processed_txidx: string -> uint64_t
2018-08-29 18:57:12 +02:00
// spent: amount -> offset
2018-08-12 23:43:33 +02:00
// ring_instances: vector<uint64_t> -> uint64_t
2018-08-12 11:47:03 +02:00
// stats: string -> arbitrary
2018-08-12 23:43:33 +02:00
//
static bool parse_db_sync_mode ( std : : string db_sync_mode )
2018-02-26 17:39:40 +01:00
{
2018-08-12 23:43:33 +02:00
std : : vector < std : : string > options ;
boost : : trim ( db_sync_mode ) ;
boost : : split ( options , db_sync_mode , boost : : is_any_of ( " : " ) ) ;
for ( const auto & option : options )
MDEBUG ( " option: " < < option ) ;
// default to fast:async:1
uint64_t DEFAULT_FLAGS = DBF_FAST ;
if ( options . size ( ) = = 0 )
2018-02-26 17:39:40 +01:00
{
2018-08-12 23:43:33 +02:00
// default to fast:async:1
db_flags = DEFAULT_FLAGS ;
}
bool safemode = false ;
if ( options . size ( ) > = 1 )
2018-02-26 17:39:40 +01:00
{
2018-08-12 23:43:33 +02:00
if ( options [ 0 ] = = " safe " )
2018-02-26 17:39:40 +01:00
{
2018-08-12 23:43:33 +02:00
safemode = true ;
db_flags = DBF_SAFE ;
2018-02-26 17:39:40 +01:00
}
2018-08-12 23:43:33 +02:00
else if ( options [ 0 ] = = " fast " )
2018-04-19 10:36:31 +02:00
{
2018-08-12 23:43:33 +02:00
db_flags = DBF_FAST ;
2018-04-19 10:36:31 +02:00
}
2018-08-12 23:43:33 +02:00
else if ( options [ 0 ] = = " fastest " )
{
db_flags = DBF_FASTEST ;
records_per_sync = 1000 ; // default to fastest:async:1000
}
else
db_flags = DEFAULT_FLAGS ;
}
2018-03-18 11:55:21 +01:00
2018-08-12 23:43:33 +02:00
if ( options . size ( ) > = 2 & & ! safemode )
2018-03-18 11:55:21 +01:00
{
2018-08-12 23:43:33 +02:00
char * endptr ;
uint64_t bps = strtoull ( options [ 1 ] . c_str ( ) , & endptr , 0 ) ;
if ( * endptr = = ' \0 ' )
records_per_sync = bps ;
2018-03-18 11:55:21 +01:00
}
2018-08-12 23:43:33 +02:00
return true ;
}
2018-03-18 11:55:21 +01:00
2018-02-26 17:39:40 +01:00
static std : : string get_default_db_path ( )
{
boost : : filesystem : : path dir = tools : : get_default_data_dir ( ) ;
2018-04-10 06:49:20 +02:00
// remove .loki, replace with .shared-ringdb
2018-02-26 17:39:40 +01:00
dir = dir . remove_filename ( ) ;
dir / = " .shared-ringdb " ;
return dir . string ( ) ;
}
2018-08-12 23:43:33 +02:00
static std : : string get_cache_filename ( boost : : filesystem : : path filename )
{
if ( ! boost : : filesystem : : is_directory ( filename ) )
filename . remove_filename ( ) ;
return filename . string ( ) ;
}
static int compare_hash32 ( const MDB_val * a , const MDB_val * b )
{
const uint32_t * va = ( const uint32_t * ) a - > mv_data ;
const uint32_t * vb = ( const uint32_t * ) b - > mv_data ;
for ( int n = 7 ; n > = 0 ; n - - )
{
if ( va [ n ] = = vb [ n ] )
continue ;
return va [ n ] < vb [ n ] ? - 1 : 1 ;
}
return 0 ;
}
2018-08-12 07:54:38 +02:00
int compare_uint64 ( const MDB_val * a , const MDB_val * b )
{
const uint64_t va = * ( const uint64_t * ) a - > mv_data ;
const uint64_t vb = * ( const uint64_t * ) b - > mv_data ;
return ( va < vb ) ? - 1 : va > vb ;
}
2018-08-12 23:43:33 +02:00
static int compare_double64 ( const MDB_val * a , const MDB_val * b )
{
const uint64_t va = * ( const uint64_t * ) a - > mv_data ;
const uint64_t vb = * ( const uint64_t * ) b - > mv_data ;
if ( va = = vb )
{
const uint64_t va = ( ( const uint64_t * ) a - > mv_data ) [ 1 ] ;
const uint64_t vb = ( ( const uint64_t * ) b - > mv_data ) [ 1 ] ;
return va < vb ? - 1 : va > vb ;
}
return va < vb ? - 1 : va > vb ;
}
static int resize_env ( const char * db_path )
{
MDB_envinfo mei ;
MDB_stat mst ;
int ret ;
size_t needed = 1000ul * 1024 * 1024 ; // at least 1000 MB
ret = mdb_env_info ( env , & mei ) ;
if ( ret )
return ret ;
ret = mdb_env_stat ( env , & mst ) ;
if ( ret )
return ret ;
uint64_t size_used = mst . ms_psize * mei . me_last_pgno ;
uint64_t mapsize = mei . me_mapsize ;
if ( size_used + needed > mei . me_mapsize )
{
try
{
boost : : filesystem : : path path ( db_path ) ;
boost : : filesystem : : space_info si = boost : : filesystem : : space ( path ) ;
if ( si . available < needed )
{
MERROR ( " !! WARNING: Insufficient free space to extend database !!: " < < ( si . available > > 20L ) < < " MB available " ) ;
return ENOSPC ;
}
}
catch ( . . . )
{
// print something but proceed.
MWARNING ( " Unable to query free disk space. " ) ;
}
mapsize + = needed ;
}
return mdb_env_set_mapsize ( env , mapsize ) ;
}
static void init ( std : : string cache_filename )
{
MDB_txn * txn ;
bool tx_active = false ;
int dbr ;
2018-10-18 20:05:51 +02:00
MINFO ( " Creating spent output cache in " < < cache_filename ) ;
2018-08-12 23:43:33 +02:00
tools : : create_directories_if_necessary ( cache_filename ) ;
int flags = 0 ;
if ( db_flags & DBF_FAST )
flags | = MDB_NOSYNC ;
if ( db_flags & DBF_FASTEST )
flags | = MDB_NOSYNC | MDB_WRITEMAP | MDB_MAPASYNC ;
dbr = mdb_env_create ( & env ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LDMB environment: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-11-24 11:52:20 +01:00
dbr = mdb_env_set_maxdbs ( env , 7 ) ;
2018-08-12 23:43:33 +02:00
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to set max env dbs: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
const std : : string actual_filename = get_cache_filename ( cache_filename ) ;
dbr = mdb_env_open ( env , actual_filename . c_str ( ) , flags , 0664 ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open rings database file ' "
+ actual_filename + " ': " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_txn_begin ( env , NULL , 0 , & txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
epee : : misc_utils : : auto_scope_leave_caller txn_dtor = epee : : misc_utils : : create_scope_leave_handler ( [ & ] ( ) { if ( tx_active ) mdb_txn_abort ( txn ) ; } ) ;
tx_active = true ;
dbr = mdb_dbi_open ( txn , " relative_rings " , MDB_CREATE | MDB_INTEGERKEY , & dbi_relative_rings ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB dbi: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
mdb_set_compare ( txn , dbi_relative_rings , compare_hash32 ) ;
dbr = mdb_dbi_open ( txn , " outputs " , MDB_CREATE | MDB_INTEGERKEY , & dbi_outputs ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB dbi: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
mdb_set_compare ( txn , dbi_outputs , compare_double64 ) ;
dbr = mdb_dbi_open ( txn , " processed_txidx " , MDB_CREATE , & dbi_processed_txidx ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB dbi: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_dbi_open ( txn , " spent " , MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED , & dbi_spent ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB dbi: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-29 18:57:12 +02:00
mdb_set_dupsort ( txn , dbi_spent , compare_uint64 ) ;
2018-08-12 23:43:33 +02:00
2018-11-24 11:52:20 +01:00
dbr = mdb_dbi_open ( txn , " per_amount " , MDB_CREATE | MDB_INTEGERKEY , & dbi_per_amount ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB dbi: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
mdb_set_compare ( txn , dbi_per_amount , compare_uint64 ) ;
2018-08-12 23:43:33 +02:00
dbr = mdb_dbi_open ( txn , " ring_instances " , MDB_CREATE , & dbi_ring_instances ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB dbi: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-12 11:47:03 +02:00
dbr = mdb_dbi_open ( txn , " stats " , MDB_CREATE , & dbi_stats ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB dbi: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-12 23:43:33 +02:00
dbr = mdb_txn_commit ( txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to commit txn creating/opening database: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
tx_active = false ;
}
static void close ( )
{
if ( env )
{
mdb_dbi_close ( env , dbi_relative_rings ) ;
mdb_dbi_close ( env , dbi_outputs ) ;
mdb_dbi_close ( env , dbi_processed_txidx ) ;
2018-11-24 11:52:20 +01:00
mdb_dbi_close ( env , dbi_per_amount ) ;
2018-08-12 23:43:33 +02:00
mdb_dbi_close ( env , dbi_spent ) ;
mdb_dbi_close ( env , dbi_ring_instances ) ;
2018-08-12 11:47:03 +02:00
mdb_dbi_close ( env , dbi_stats ) ;
2018-08-12 23:43:33 +02:00
mdb_env_close ( env ) ;
env = NULL ;
}
}
2018-08-12 23:45:10 +02:00
static std : : string compress_ring ( const std : : vector < uint64_t > & ring , std : : string s = " " )
2018-08-12 23:43:33 +02:00
{
2018-08-19 18:52:56 +02:00
const size_t sz = s . size ( ) ;
s . resize ( s . size ( ) + 12 * ring . size ( ) ) ;
char * ptr = ( char * ) s . data ( ) + sz ;
2018-08-12 23:43:33 +02:00
for ( uint64_t out : ring )
2018-08-19 18:52:56 +02:00
tools : : write_varint ( ptr , out ) ;
if ( ptr > s . data ( ) + sz + 12 * ring . size ( ) )
throw std : : runtime_error ( " varint output overflow " ) ;
s . resize ( ptr - s . data ( ) ) ;
2018-08-12 23:43:33 +02:00
return s ;
}
2018-08-12 23:45:10 +02:00
static std : : string compress_ring ( uint64_t amount , const std : : vector < uint64_t > & ring )
{
2018-08-19 18:52:56 +02:00
char s [ 12 ] , * ptr = s ;
tools : : write_varint ( ptr , amount ) ;
if ( ptr > s + sizeof ( s ) )
throw std : : runtime_error ( " varint output overflow " ) ;
return compress_ring ( ring , std : : string ( s , ptr - s ) ) ;
2018-08-12 23:45:10 +02:00
}
2018-08-12 23:43:33 +02:00
static std : : vector < uint64_t > decompress_ring ( const std : : string & s )
{
std : : vector < uint64_t > ring ;
int read = 0 ;
for ( std : : string : : const_iterator i = s . begin ( ) ; i ! = s . cend ( ) ; std : : advance ( i , read ) )
{
uint64_t out ;
std : : string tmp ( i , s . cend ( ) ) ;
read = tools : : read_varint ( tmp . begin ( ) , tmp . end ( ) , out ) ;
CHECK_AND_ASSERT_THROW_MES ( read > 0 & & read < = 256 , " Internal error decompressing ring " ) ;
ring . push_back ( out ) ;
}
return ring ;
}
2018-08-12 07:54:38 +02:00
static bool for_all_transactions ( const std : : string & filename , uint64_t & start_idx , uint64_t & n_txes , const std : : function < bool ( const cryptonote : : transaction_prefix & ) > & f )
2018-02-26 17:39:40 +01:00
{
MDB_env * env ;
MDB_dbi dbi ;
MDB_txn * txn ;
MDB_cursor * cur ;
int dbr ;
bool tx_active = false ;
2018-08-12 07:54:38 +02:00
MDB_val k ;
MDB_val v ;
2018-02-26 17:39:40 +01:00
dbr = mdb_env_create ( & env ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to create LDMB environment: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_env_set_maxdbs ( env , 2 ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to set max env dbs: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
const std : : string actual_filename = filename ;
dbr = mdb_env_open ( env , actual_filename . c_str ( ) , 0 , 0664 ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to open rings database file ' "
+ actual_filename + " ': " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-12 11:50:48 +02:00
dbr = mdb_txn_begin ( env , NULL , MDB_RDONLY , & txn ) ;
2018-02-26 17:39:40 +01:00
if ( dbr ) throw std : : runtime_error ( " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
epee : : misc_utils : : auto_scope_leave_caller txn_dtor = epee : : misc_utils : : create_scope_leave_handler ( [ & ] ( ) { if ( tx_active ) mdb_txn_abort ( txn ) ; } ) ;
tx_active = true ;
dbr = mdb_dbi_open ( txn , " txs_pruned " , MDB_INTEGERKEY , & dbi ) ;
if ( dbr )
dbr = mdb_dbi_open ( txn , " txs " , MDB_INTEGERKEY , & dbi ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to open LMDB dbi: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_cursor_open ( txn , dbi , & cur ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to create LMDB cursor: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-12 07:54:38 +02:00
MDB_stat stat ;
dbr = mdb_stat ( txn , dbi , & stat ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to query m_block_info: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
n_txes = stat . ms_entries ;
2018-02-26 17:39:40 +01:00
bool fret = true ;
2018-03-18 11:55:21 +01:00
k . mv_size = sizeof ( uint64_t ) ;
k . mv_data = & start_idx ;
MDB_cursor_op op = MDB_SET ;
2018-02-26 17:39:40 +01:00
while ( 1 )
{
int ret = mdb_cursor_get ( cur , & k , & v , op ) ;
op = MDB_NEXT ;
if ( ret = = MDB_NOTFOUND )
break ;
if ( ret )
throw std : : runtime_error ( " Failed to enumerate transactions: " + std : : string ( mdb_strerror ( ret ) ) ) ;
2018-03-18 11:55:21 +01:00
if ( k . mv_size ! = sizeof ( uint64_t ) )
throw std : : runtime_error ( " Bad key size " ) ;
const uint64_t idx = * ( uint64_t * ) k . mv_data ;
if ( idx < start_idx )
continue ;
2018-02-26 17:39:40 +01:00
cryptonote : : transaction_prefix tx ;
blobdata bd ;
bd . assign ( reinterpret_cast < char * > ( v . mv_data ) , v . mv_size ) ;
std : : stringstream ss ;
ss < < bd ;
binary_archive < false > ba ( ss ) ;
bool r = do_serialize ( ba , tx ) ;
CHECK_AND_ASSERT_MES ( r , false , " Failed to parse transaction from blob " ) ;
2018-03-18 11:55:21 +01:00
start_idx = * ( uint64_t * ) k . mv_data ;
2018-02-26 17:39:40 +01:00
if ( ! f ( tx ) ) {
fret = false ;
break ;
}
}
mdb_cursor_close ( cur ) ;
2018-09-29 22:17:00 +02:00
dbr = mdb_txn_commit ( txn ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to commit db transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-02-26 17:39:40 +01:00
tx_active = false ;
mdb_dbi_close ( env , dbi ) ;
mdb_env_close ( env ) ;
return fret ;
}
2018-08-18 14:40:26 +02:00
static uint64_t find_first_diverging_transaction ( const std : : string & first_filename , const std : : string & second_filename )
{
MDB_env * env [ 2 ] ;
MDB_dbi dbi [ 2 ] ;
MDB_txn * txn [ 2 ] ;
MDB_cursor * cur [ 2 ] ;
int dbr ;
bool tx_active [ 2 ] = { false , false } ;
uint64_t n_txes [ 2 ] ;
MDB_val k ;
MDB_val v [ 2 ] ;
epee : : misc_utils : : auto_scope_leave_caller txn_dtor [ 2 ] ;
for ( int i = 0 ; i < 2 ; + + i )
{
dbr = mdb_env_create ( & env [ i ] ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to create LDMB environment: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_env_set_maxdbs ( env [ i ] , 2 ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to set max env dbs: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
const std : : string actual_filename = i ? second_filename : first_filename ;
dbr = mdb_env_open ( env [ i ] , actual_filename . c_str ( ) , 0 , 0664 ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to open rings database file ' "
+ actual_filename + " ': " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_txn_begin ( env [ i ] , NULL , MDB_RDONLY , & txn [ i ] ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
txn_dtor [ i ] = epee : : misc_utils : : create_scope_leave_handler ( [ & , i ] ( ) { if ( tx_active [ i ] ) mdb_txn_abort ( txn [ i ] ) ; } ) ;
tx_active [ i ] = true ;
dbr = mdb_dbi_open ( txn [ i ] , " txs_pruned " , MDB_INTEGERKEY , & dbi [ i ] ) ;
if ( dbr )
dbr = mdb_dbi_open ( txn [ i ] , " txs " , MDB_INTEGERKEY , & dbi [ i ] ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to open LMDB dbi: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_cursor_open ( txn [ i ] , dbi [ i ] , & cur [ i ] ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to create LMDB cursor: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
MDB_stat stat ;
dbr = mdb_stat ( txn [ i ] , dbi [ i ] , & stat ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to query m_block_info: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
n_txes [ i ] = stat . ms_entries ;
}
if ( n_txes [ 0 ] = = 0 | | n_txes [ 1 ] = = 0 )
throw std : : runtime_error ( " No transaction in the database " ) ;
uint64_t lo = 0 , hi = std : : min ( n_txes [ 0 ] , n_txes [ 1 ] ) - 1 ;
while ( lo < = hi )
{
uint64_t mid = ( lo + hi ) / 2 ;
k . mv_size = sizeof ( uint64_t ) ;
k . mv_data = ( void * ) & mid ;
dbr = mdb_cursor_get ( cur [ 0 ] , & k , & v [ 0 ] , MDB_SET ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to query transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_cursor_get ( cur [ 1 ] , & k , & v [ 1 ] , MDB_SET ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to query transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
if ( v [ 0 ] . mv_size = = v [ 1 ] . mv_size & & ! memcmp ( v [ 0 ] . mv_data , v [ 1 ] . mv_data , v [ 0 ] . mv_size ) )
lo = mid + 1 ;
else
hi = mid - 1 ;
}
for ( int i = 0 ; i < 2 ; + + i )
{
mdb_cursor_close ( cur [ i ] ) ;
2018-09-29 22:17:00 +02:00
dbr = mdb_txn_commit ( txn [ i ] ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to query transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-18 14:40:26 +02:00
tx_active [ i ] = false ;
mdb_dbi_close ( env [ i ] , dbi [ i ] ) ;
mdb_env_close ( env [ i ] ) ;
}
return hi ;
}
2018-04-19 10:36:31 +02:00
static std : : vector < uint64_t > canonicalize ( const std : : vector < uint64_t > & v )
{
std : : vector < uint64_t > c ;
c . reserve ( v . size ( ) ) ;
c . push_back ( v [ 0 ] ) ;
for ( size_t n = 1 ; n < v . size ( ) ; + + n )
{
if ( v [ n ] ! = 0 )
c . push_back ( v [ n ] ) ;
}
if ( c . size ( ) < v . size ( ) )
{
MINFO ( " Ring has duplicate member(s): " < <
boost : : join ( v | boost : : adaptors : : transformed ( [ ] ( uint64_t out ) { return std : : to_string ( out ) ; } ) , " " ) ) ;
}
return c ;
}
2018-08-12 23:46:02 +02:00
static uint64_t get_num_spent_outputs ( )
2018-08-12 23:43:33 +02:00
{
MDB_txn * txn ;
bool tx_active = false ;
int dbr = mdb_txn_begin ( env , NULL , MDB_RDONLY , & txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
epee : : misc_utils : : auto_scope_leave_caller txn_dtor = epee : : misc_utils : : create_scope_leave_handler ( [ & ] ( ) { if ( tx_active ) mdb_txn_abort ( txn ) ; } ) ;
tx_active = true ;
MDB_cursor * cur ;
2018-08-12 23:46:02 +02:00
dbr = mdb_cursor_open ( txn , dbi_spent , & cur ) ;
2018-08-12 23:43:33 +02:00
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open cursor for spent outputs: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
MDB_val k , v ;
2018-08-29 18:57:12 +02:00
mdb_size_t count = 0 , tmp ;
MDB_cursor_op op = MDB_FIRST ;
while ( 1 )
2018-08-12 23:43:33 +02:00
{
2018-08-29 18:57:12 +02:00
dbr = mdb_cursor_get ( cur , & k , & v , op ) ;
op = MDB_NEXT_NODUP ;
if ( dbr = = MDB_NOTFOUND )
break ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to get first/next spent output: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_cursor_count ( cur , & tmp ) ;
2018-08-12 23:43:33 +02:00
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to count entries: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-29 18:57:12 +02:00
count + = tmp ;
2018-08-12 23:43:33 +02:00
}
mdb_cursor_close ( cur ) ;
dbr = mdb_txn_commit ( txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to commit txn: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
tx_active = false ;
return count ;
}
2018-10-08 00:20:27 +02:00
static bool add_spent_output ( MDB_cursor * cur , const output_data & od )
2018-08-12 23:43:33 +02:00
{
2018-08-29 18:57:12 +02:00
MDB_val k = { sizeof ( od . amount ) , ( void * ) & od . amount } ;
MDB_val v = { sizeof ( od . offset ) , ( void * ) & od . offset } ;
2018-10-08 00:20:27 +02:00
int dbr = mdb_cursor_put ( cur , & k , & v , MDB_NODUPDATA ) ;
if ( dbr = = MDB_KEYEXIST )
return false ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to add spent output: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
return true ;
2018-08-12 23:43:33 +02:00
}
2018-08-19 18:52:56 +02:00
static bool is_output_spent ( MDB_cursor * cur , const output_data & od )
2018-08-12 23:43:33 +02:00
{
2018-08-29 18:57:12 +02:00
MDB_val k = { sizeof ( od . amount ) , ( void * ) & od . amount } ;
MDB_val v = { sizeof ( od . offset ) , ( void * ) & od . offset } ;
int dbr = mdb_cursor_get ( cur , & k , & v , MDB_GET_BOTH ) ;
2018-08-12 23:43:33 +02:00
CHECK_AND_ASSERT_THROW_MES ( ! dbr | | dbr = = MDB_NOTFOUND , " Failed to get spent output: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
bool spent = dbr = = 0 ;
return spent ;
}
2018-08-12 23:46:02 +02:00
static std : : vector < output_data > get_spent_outputs ( MDB_txn * txn )
2018-08-12 23:43:33 +02:00
{
MDB_cursor * cur ;
2018-08-12 23:46:02 +02:00
int dbr = mdb_cursor_open ( txn , dbi_spent , & cur ) ;
2018-08-12 23:43:33 +02:00
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open cursor for spent outputs: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
MDB_val k , v ;
2018-09-14 19:11:11 +02:00
mdb_size_t count = 0 ;
2018-08-12 23:43:33 +02:00
dbr = mdb_cursor_get ( cur , & k , & v , MDB_FIRST ) ;
if ( dbr ! = MDB_NOTFOUND )
{
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to get first spent output: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_cursor_count ( cur , & count ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to count entries: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
}
std : : vector < output_data > outs ;
outs . reserve ( count ) ;
while ( 1 )
{
2018-08-29 18:57:12 +02:00
outs . push_back ( { * ( const uint64_t * ) k . mv_data , * ( const uint64_t * ) v . mv_data } ) ;
2018-08-12 23:43:33 +02:00
dbr = mdb_cursor_get ( cur , & k , & v , MDB_NEXT ) ;
if ( dbr = = MDB_NOTFOUND )
break ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to get next spent output: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
}
mdb_cursor_close ( cur ) ;
return outs ;
}
2018-11-24 11:52:20 +01:00
static void get_per_amount_outputs ( MDB_txn * txn , uint64_t amount , uint64_t & total , uint64_t & spent )
{
MDB_cursor * cur ;
int dbr = mdb_cursor_open ( txn , dbi_per_amount , & cur ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open cursor for per amount outputs: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
MDB_val k , v ;
mdb_size_t count = 0 ;
k . mv_size = sizeof ( uint64_t ) ;
k . mv_data = ( void * ) & amount ;
dbr = mdb_cursor_get ( cur , & k , & v , MDB_SET ) ;
if ( dbr = = MDB_NOTFOUND )
{
total = spent = 0 ;
}
else
{
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to get per amount outputs: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
total = ( ( const uint64_t * ) v . mv_data ) [ 0 ] ;
spent = ( ( const uint64_t * ) v . mv_data ) [ 1 ] ;
}
mdb_cursor_close ( cur ) ;
}
static void inc_per_amount_outputs ( MDB_txn * txn , uint64_t amount , uint64_t total , uint64_t spent )
{
MDB_cursor * cur ;
int dbr = mdb_cursor_open ( txn , dbi_per_amount , & cur ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open cursor for per amount outputs: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
MDB_val k , v ;
mdb_size_t count = 0 ;
k . mv_size = sizeof ( uint64_t ) ;
k . mv_data = ( void * ) & amount ;
dbr = mdb_cursor_get ( cur , & k , & v , MDB_SET ) ;
if ( dbr = = 0 )
{
total + = ( ( const uint64_t * ) v . mv_data ) [ 0 ] ;
spent + = ( ( const uint64_t * ) v . mv_data ) [ 1 ] ;
}
else
{
CHECK_AND_ASSERT_THROW_MES ( dbr = = MDB_NOTFOUND , " Failed to get per amount outputs: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
}
uint64_t data [ 2 ] = { total , spent } ;
v . mv_size = 2 * sizeof ( uint64_t ) ;
v . mv_data = ( void * ) data ;
dbr = mdb_cursor_put ( cur , & k , & v , 0 ) ;
mdb_cursor_close ( cur ) ;
}
2018-08-12 23:43:33 +02:00
static uint64_t get_processed_txidx ( const std : : string & name )
{
MDB_txn * txn ;
bool tx_active = false ;
int dbr = mdb_txn_begin ( env , NULL , MDB_RDONLY , & txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
epee : : misc_utils : : auto_scope_leave_caller txn_dtor = epee : : misc_utils : : create_scope_leave_handler ( [ & ] ( ) { if ( tx_active ) mdb_txn_abort ( txn ) ; } ) ;
tx_active = true ;
uint64_t height = 0 ;
MDB_val k , v ;
k . mv_data = ( void * ) name . c_str ( ) ;
k . mv_size = name . size ( ) ;
dbr = mdb_get ( txn , dbi_processed_txidx , & k , & v ) ;
if ( dbr ! = MDB_NOTFOUND )
{
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to get processed height: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
height = * ( const uint64_t * ) v . mv_data ;
}
dbr = mdb_txn_commit ( txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to commit txn: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
tx_active = false ;
return height ;
}
static void set_processed_txidx ( MDB_txn * txn , const std : : string & name , uint64_t height )
{
MDB_val k , v ;
k . mv_data = ( void * ) name . c_str ( ) ;
k . mv_size = name . size ( ) ;
v . mv_data = ( void * ) & height ;
v . mv_size = sizeof ( height ) ;
int dbr = mdb_put ( txn , dbi_processed_txidx , & k , & v , 0 ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to set processed height: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
}
static bool get_relative_ring ( MDB_txn * txn , const crypto : : key_image & ki , std : : vector < uint64_t > & ring )
{
MDB_val k , v ;
k . mv_data = ( void * ) & ki ;
k . mv_size = sizeof ( ki ) ;
int dbr = mdb_get ( txn , dbi_relative_rings , & k , & v ) ;
if ( dbr = = MDB_NOTFOUND )
return false ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to get relative ring: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
ring = decompress_ring ( std : : string ( ( const char * ) v . mv_data , v . mv_size ) ) ;
return true ;
}
static void set_relative_ring ( MDB_txn * txn , const crypto : : key_image & ki , const std : : vector < uint64_t > & ring )
{
const std : : string sring = compress_ring ( ring ) ;
MDB_val k , v ;
k . mv_data = ( void * ) & ki ;
k . mv_size = sizeof ( ki ) ;
v . mv_data = ( void * ) sring . c_str ( ) ;
v . mv_size = sring . size ( ) ;
int dbr = mdb_put ( txn , dbi_relative_rings , & k , & v , 0 ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to set relative ring: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
}
static std : : string keep_under_511 ( const std : : string & s )
{
if ( s . size ( ) < = 511 )
return s ;
crypto : : hash hash ;
crypto : : cn_fast_hash ( s . data ( ) , s . size ( ) , hash ) ;
return std : : string ( ( const char * ) & hash , 32 ) ;
}
2018-08-12 23:45:10 +02:00
static uint64_t get_ring_instances ( MDB_txn * txn , uint64_t amount , const std : : vector < uint64_t > & ring )
2018-08-12 23:43:33 +02:00
{
2018-08-12 23:45:10 +02:00
const std : : string sring = keep_under_511 ( compress_ring ( amount , ring ) ) ;
2018-08-12 23:43:33 +02:00
MDB_val k , v ;
k . mv_data = ( void * ) sring . data ( ) ;
k . mv_size = sring . size ( ) ;
int dbr = mdb_get ( txn , dbi_ring_instances , & k , & v ) ;
if ( dbr = = MDB_NOTFOUND )
return 0 ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to get ring instances: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
return * ( const uint64_t * ) v . mv_data ;
}
2018-08-14 12:57:39 +02:00
static uint64_t get_ring_subset_instances ( MDB_txn * txn , uint64_t amount , const std : : vector < uint64_t > & ring )
{
uint64_t instances = get_ring_instances ( txn , amount , ring ) ;
if ( ring . size ( ) > 11 )
return instances ;
uint64_t extra = 0 ;
2018-08-19 18:52:56 +02:00
std : : vector < uint64_t > subset ;
subset . reserve ( ring . size ( ) ) ;
2018-09-29 22:17:00 +02:00
for ( uint64_t mask = 1 ; mask < ( ( ( uint64_t ) 1 ) < < ring . size ( ) ) - 1 ; + + mask )
2018-08-14 12:57:39 +02:00
{
2018-08-19 18:52:56 +02:00
subset . resize ( 0 ) ;
2018-08-14 12:57:39 +02:00
for ( size_t i = 0 ; i < ring . size ( ) ; + + i )
if ( ( mask > > i ) & 1 )
subset . push_back ( ring [ i ] ) ;
extra + = get_ring_instances ( txn , amount , subset ) ;
}
return instances + extra ;
}
2018-08-19 18:52:56 +02:00
static uint64_t inc_ring_instances ( MDB_txn * txn , uint64_t amount , const std : : vector < uint64_t > & ring )
2018-08-12 23:43:33 +02:00
{
2018-08-12 23:45:10 +02:00
const std : : string sring = keep_under_511 ( compress_ring ( amount , ring ) ) ;
2018-08-12 23:43:33 +02:00
MDB_val k , v ;
k . mv_data = ( void * ) sring . data ( ) ;
k . mv_size = sring . size ( ) ;
2018-08-19 18:52:56 +02:00
int dbr = mdb_get ( txn , dbi_ring_instances , & k , & v ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr | | dbr = = MDB_NOTFOUND , " Failed to get ring instances: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
uint64_t count ;
if ( dbr = = MDB_NOTFOUND )
count = 1 ;
else
count = 1 + * ( const uint64_t * ) v . mv_data ;
2018-08-12 23:43:33 +02:00
v . mv_data = & count ;
v . mv_size = sizeof ( count ) ;
2018-08-19 18:52:56 +02:00
dbr = mdb_put ( txn , dbi_ring_instances , & k , & v , 0 ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to set ring instances: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
return count ;
2018-08-12 23:43:33 +02:00
}
static std : : vector < crypto : : key_image > get_key_images ( MDB_txn * txn , const output_data & od )
{
MDB_val k , v ;
k . mv_data = ( void * ) & od ;
k . mv_size = sizeof ( od ) ;
int dbr = mdb_get ( txn , dbi_outputs , & k , & v ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr | | dbr = = MDB_NOTFOUND , " Failed to get output: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
if ( dbr = = MDB_NOTFOUND )
return { } ;
CHECK_AND_ASSERT_THROW_MES ( v . mv_size % 32 = = 0 , " Unexpected record size " ) ;
std : : vector < crypto : : key_image > key_images ;
key_images . reserve ( v . mv_size / 32 ) ;
const crypto : : key_image * ki = ( const crypto : : key_image * ) v . mv_data ;
for ( size_t n = 0 ; n < v . mv_size / 32 ; + + n )
key_images . push_back ( * ki + + ) ;
return key_images ;
}
static void add_key_image ( MDB_txn * txn , const output_data & od , const crypto : : key_image & ki )
{
MDB_val k , v ;
k . mv_data = ( void * ) & od ;
k . mv_size = sizeof ( od ) ;
int dbr = mdb_get ( txn , dbi_outputs , & k , & v ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr | | dbr = = MDB_NOTFOUND , " Failed to get output " ) ;
std : : string data ;
if ( ! dbr )
{
CHECK_AND_ASSERT_THROW_MES ( v . mv_size % 32 = = 0 , " Unexpected record size " ) ;
data = std : : string ( ( const char * ) v . mv_data , v . mv_size ) ;
}
data + = std : : string ( ( const char * ) & ki , sizeof ( ki ) ) ;
v . mv_data = ( void * ) data . data ( ) ;
v . mv_size = data . size ( ) ;
dbr = mdb_put ( txn , dbi_outputs , & k , & v , 0 ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to set outputs: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
}
2018-08-12 11:47:03 +02:00
static bool get_stat ( MDB_txn * txn , const char * key , uint64_t & data )
{
MDB_val k , v ;
k . mv_data = ( void * ) key ;
k . mv_size = strlen ( key ) ;
int dbr = mdb_get ( txn , dbi_stats , & k , & v ) ;
if ( dbr = = MDB_NOTFOUND )
return false ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to get stat record " ) ;
CHECK_AND_ASSERT_THROW_MES ( v . mv_size = = sizeof ( uint64_t ) , " Unexpected record size " ) ;
data = * ( const uint64_t * ) v . mv_data ;
return true ;
}
static void set_stat ( MDB_txn * txn , const char * key , uint64_t data )
{
MDB_val k , v ;
k . mv_data = ( void * ) key ;
k . mv_size = strlen ( key ) ;
v . mv_data = ( void * ) & data ;
v . mv_size = sizeof ( uint64_t ) ;
int dbr = mdb_put ( txn , dbi_stats , & k , & v , 0 ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to set stat record " ) ;
}
static void inc_stat ( MDB_txn * txn , const char * key )
{
uint64_t data ;
if ( ! get_stat ( txn , key , data ) )
data = 0 ;
+ + data ;
set_stat ( txn , key , data ) ;
}
2018-08-12 07:54:38 +02:00
static void open_db ( const std : : string & filename , MDB_env * * env , MDB_txn * * txn , MDB_cursor * * cur , MDB_dbi * dbi )
{
tools : : create_directories_if_necessary ( filename ) ;
int flags = MDB_RDONLY ;
if ( db_flags & DBF_FAST )
flags | = MDB_NOSYNC ;
if ( db_flags & DBF_FASTEST )
flags | = MDB_NOSYNC | MDB_WRITEMAP | MDB_MAPASYNC ;
int dbr = mdb_env_create ( env ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LDMB environment: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_env_set_maxdbs ( * env , 1 ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to set max env dbs: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
const std : : string actual_filename = filename ;
MINFO ( " Opening monero blockchain at " < < actual_filename ) ;
dbr = mdb_env_open ( * env , actual_filename . c_str ( ) , flags , 0664 ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open rings database file ' "
+ actual_filename + " ': " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_txn_begin ( * env , NULL , MDB_RDONLY , txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_dbi_open ( * txn , " output_amounts " , MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED , dbi ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB dbi: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
mdb_set_dupsort ( * txn , * dbi , compare_uint64 ) ;
dbr = mdb_cursor_open ( * txn , * dbi , cur ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LMDB cursor: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
}
static void close_db ( MDB_env * env , MDB_txn * txn , MDB_cursor * cur , MDB_dbi dbi )
{
mdb_txn_abort ( txn ) ;
mdb_cursor_close ( cur ) ;
mdb_dbi_close ( env , dbi ) ;
mdb_env_close ( env ) ;
}
2018-08-12 11:47:03 +02:00
static void get_num_outputs ( MDB_txn * txn , MDB_cursor * cur , MDB_dbi dbi , uint64_t & pre_rct , uint64_t & rct )
{
uint64_t amount = 0 ;
MDB_val k = { sizeof ( amount ) , ( void * ) & amount } , v ;
int dbr = mdb_cursor_get ( cur , & k , & v , MDB_SET ) ;
if ( dbr = = MDB_NOTFOUND )
{
rct = 0 ;
}
else
{
if ( dbr ) throw std : : runtime_error ( " Record 0 not found: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
mdb_size_t count = 0 ;
dbr = mdb_cursor_count ( cur , & count ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to count records: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
rct = count ;
}
MDB_stat s ;
dbr = mdb_stat ( txn , dbi , & s ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to count records: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
if ( s . ms_entries < rct ) throw std : : runtime_error ( " Inconsistent records: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
pre_rct = s . ms_entries - rct ;
}
2018-08-12 07:54:38 +02:00
static crypto : : hash get_genesis_block_hash ( const std : : string & filename )
{
MDB_env * env ;
MDB_dbi dbi ;
MDB_txn * txn ;
int dbr ;
bool tx_active = false ;
dbr = mdb_env_create ( & env ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to create LDMB environment: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_env_set_maxdbs ( env , 1 ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to set max env dbs: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
const std : : string actual_filename = filename ;
dbr = mdb_env_open ( env , actual_filename . c_str ( ) , 0 , 0664 ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to open rings database file ' "
+ actual_filename + " ': " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_txn_begin ( env , NULL , MDB_RDONLY , & txn ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
epee : : misc_utils : : auto_scope_leave_caller txn_dtor = epee : : misc_utils : : create_scope_leave_handler ( [ & ] ( ) { if ( tx_active ) mdb_txn_abort ( txn ) ; } ) ;
tx_active = true ;
dbr = mdb_dbi_open ( txn , " block_info " , MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED , & dbi ) ;
mdb_set_dupsort ( txn , dbi , compare_uint64 ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to open LMDB dbi: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
uint64_t zero = 0 ;
MDB_val k = { sizeof ( uint64_t ) , ( void * ) & zero } , v ;
dbr = mdb_get ( txn , dbi , & k , & v ) ;
if ( dbr ) throw std : : runtime_error ( " Failed to retrieve genesis block: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
crypto : : hash genesis_block_hash = * ( const crypto : : hash * ) ( ( ( const uint64_t * ) v . mv_data ) + 5 ) ;
mdb_dbi_close ( env , dbi ) ;
mdb_txn_abort ( txn ) ;
mdb_env_close ( env ) ;
tx_active = false ;
return genesis_block_hash ;
}
2018-08-29 18:57:12 +02:00
static std : : vector < std : : pair < uint64_t , uint64_t > > load_outputs ( const std : : string & filename )
{
std : : vector < std : : pair < uint64_t , uint64_t > > outputs ;
uint64_t amount = std : : numeric_limits < uint64_t > : : max ( ) ;
FILE * f ;
f = fopen ( filename . c_str ( ) , " r " ) ;
if ( ! f )
{
MERROR ( " Failed to load outputs from " < < filename < < " : " < < strerror ( errno ) ) ;
return { } ;
}
while ( 1 )
{
char s [ 256 ] ;
2018-10-10 01:30:31 +02:00
if ( ! fgets ( s , sizeof ( s ) , f ) )
{
MERROR ( " Error reading from " < < filename < < " : " < < strerror ( errno ) ) ;
break ;
}
2018-08-29 18:57:12 +02:00
if ( feof ( f ) )
break ;
const size_t len = strlen ( s ) ;
if ( len > 0 & & s [ len - 1 ] = = ' \n ' )
s [ len - 1 ] = 0 ;
if ( ! s [ 0 ] )
continue ;
std : : pair < uint64_t , uint64_t > output ;
uint64_t offset , num_offsets ;
if ( sscanf ( s , " @% " PRIu64 , & amount ) = = 1 )
{
continue ;
}
if ( amount = = std : : numeric_limits < uint64_t > : : max ( ) )
{
MERROR ( " Bad format in " < < filename ) ;
continue ;
}
if ( sscanf ( s , " % " PRIu64 " *% " PRIu64 , & offset , & num_offsets ) = = 2 & & num_offsets < std : : numeric_limits < uint64_t > : : max ( ) - offset )
{
while ( num_offsets - - > 0 )
outputs . push_back ( std : : make_pair ( amount , offset + + ) ) ;
}
else if ( sscanf ( s , " % " PRIu64 , & offset ) = = 1 )
{
outputs . push_back ( std : : make_pair ( amount , offset ) ) ;
}
else
{
MERROR ( " Bad format in " < < filename ) ;
continue ;
}
}
fclose ( f ) ;
return outputs ;
}
static bool export_spent_outputs ( MDB_cursor * cur , const std : : string & filename )
{
FILE * f = fopen ( filename . c_str ( ) , " w " ) ;
if ( ! f )
{
MERROR ( " Failed to open " < < filename < < " : " < < strerror ( errno ) ) ;
return false ;
}
uint64_t pending_amount = std : : numeric_limits < uint64_t > : : max ( ) ;
std : : vector < uint64_t > pending_offsets ;
MDB_val k , v ;
MDB_cursor_op op = MDB_FIRST ;
while ( 1 )
{
int dbr = mdb_cursor_get ( cur , & k , & v , op ) ;
if ( dbr = = MDB_NOTFOUND )
break ;
op = MDB_NEXT ;
if ( dbr )
{
fclose ( f ) ;
MERROR ( " Failed to enumerate spent outputs: " < < mdb_strerror ( dbr ) ) ;
return false ;
}
const uint64_t amount = * ( const uint64_t * ) k . mv_data ;
const uint64_t offset = * ( const uint64_t * ) v . mv_data ;
if ( ! pending_offsets . empty ( ) & & ( amount ! = pending_amount | | pending_offsets . back ( ) + 1 ! = offset ) )
{
if ( pending_offsets . size ( ) = = 1 )
fprintf ( f , " % " PRIu64 " \n " , pending_offsets . front ( ) ) ;
else
2018-09-14 19:11:58 +02:00
fprintf ( f , " % " PRIu64 " *%zu \n " , pending_offsets . front ( ) , pending_offsets . size ( ) ) ;
2018-08-29 18:57:12 +02:00
pending_offsets . clear ( ) ;
}
if ( pending_amount ! = amount )
{
fprintf ( f , " @% " PRIu64 " \n " , amount ) ;
pending_amount = amount ;
}
pending_offsets . push_back ( offset ) ;
}
if ( ! pending_offsets . empty ( ) )
{
if ( pending_offsets . size ( ) = = 1 )
fprintf ( f , " % " PRIu64 " \n " , pending_offsets . front ( ) ) ;
else
2018-09-14 19:11:58 +02:00
fprintf ( f , " % " PRIu64 " *%zu \n " , pending_offsets . front ( ) , pending_offsets . size ( ) ) ;
2018-08-29 18:57:12 +02:00
pending_offsets . clear ( ) ;
}
fclose ( f ) ;
return true ;
}
2018-02-26 17:39:40 +01:00
int main ( int argc , char * argv [ ] )
{
TRY_ENTRY ( ) ;
epee : : string_tools : : set_module_name_and_folder ( argv [ 0 ] ) ;
std : : string default_db_type = " lmdb " ;
std : : string available_dbs = cryptonote : : blockchain_db_types ( " , " ) ;
available_dbs = " available: " + available_dbs ;
uint32_t log_level = 0 ;
tools : : on_startup ( ) ;
boost : : filesystem : : path output_file_path ;
po : : options_description desc_cmd_only ( " Command line options " ) ;
po : : options_description desc_cmd_sett ( " Command line options and settings options " ) ;
2018-08-12 07:54:38 +02:00
const command_line : : arg_descriptor < std : : string > arg_blackball_db_dir = {
2018-10-18 20:05:51 +02:00
" spent-output-db-dir " , " Specify spent output database directory " ,
2018-02-26 17:39:40 +01:00
get_default_db_path ( ) ,
} ;
const command_line : : arg_descriptor < std : : string > arg_log_level = { " log-level " , " 0-4 or categories " , " " } ;
const command_line : : arg_descriptor < std : : string > arg_database = {
" database " , available_dbs . c_str ( ) , default_db_type
} ;
const command_line : : arg_descriptor < bool > arg_rct_only = { " rct-only " , " Only work on ringCT outputs " , false } ;
2018-08-14 12:57:39 +02:00
const command_line : : arg_descriptor < bool > arg_check_subsets = { " check-subsets " , " Check ring subsets (very expensive) " , false } ;
2018-08-19 18:52:56 +02:00
const command_line : : arg_descriptor < bool > arg_verbose = { " verbose " , " Verbose output) " , false } ;
2018-04-10 06:49:20 +02:00
const command_line : : arg_descriptor < std : : vector < std : : string > > arg_inputs = { " inputs " , " Path to Loki DB, and path to any fork DBs " } ;
2018-08-12 23:43:33 +02:00
const command_line : : arg_descriptor < std : : string > arg_db_sync_mode = {
" db-sync-mode "
, " Specify sync option, using format [safe|fast|fastest]:[nrecords_per_sync]. "
, " fast:1000 "
} ;
2018-08-29 18:57:12 +02:00
const command_line : : arg_descriptor < std : : string > arg_extra_spent_list = { " extra-spent-list " , " Optional list of known spent outputs " , " " } ;
const command_line : : arg_descriptor < std : : string > arg_export = { " export " , " Filename to export the backball list to " } ;
2018-08-30 11:11:53 +02:00
const command_line : : arg_descriptor < bool > arg_force_chain_reaction_pass = { " force-chain-reaction-pass " , " Run the chain reaction pass even if no new blockchain data was processed " } ;
2018-02-26 17:39:40 +01:00
command_line : : add_arg ( desc_cmd_sett , arg_blackball_db_dir ) ;
command_line : : add_arg ( desc_cmd_sett , arg_log_level ) ;
command_line : : add_arg ( desc_cmd_sett , arg_database ) ;
command_line : : add_arg ( desc_cmd_sett , arg_rct_only ) ;
2018-08-14 12:57:39 +02:00
command_line : : add_arg ( desc_cmd_sett , arg_check_subsets ) ;
2018-08-19 18:52:56 +02:00
command_line : : add_arg ( desc_cmd_sett , arg_verbose ) ;
2018-08-12 23:43:33 +02:00
command_line : : add_arg ( desc_cmd_sett , arg_db_sync_mode ) ;
2018-08-29 18:57:12 +02:00
command_line : : add_arg ( desc_cmd_sett , arg_extra_spent_list ) ;
command_line : : add_arg ( desc_cmd_sett , arg_export ) ;
2018-08-30 11:11:53 +02:00
command_line : : add_arg ( desc_cmd_sett , arg_force_chain_reaction_pass ) ;
2018-02-26 17:39:40 +01:00
command_line : : add_arg ( desc_cmd_sett , arg_inputs ) ;
command_line : : add_arg ( desc_cmd_only , command_line : : arg_help ) ;
po : : options_description desc_options ( " Allowed options " ) ;
desc_options . add ( desc_cmd_only ) . add ( desc_cmd_sett ) ;
po : : positional_options_description positional_options ;
positional_options . add ( arg_inputs . name , - 1 ) ;
po : : variables_map vm ;
bool r = command_line : : handle_error_helper ( desc_options , [ & ] ( )
{
auto parser = po : : command_line_parser ( argc , argv ) . options ( desc_options ) . positional ( positional_options ) ;
po : : store ( parser . run ( ) , vm ) ;
po : : notify ( vm ) ;
return true ;
} ) ;
if ( ! r )
return 1 ;
if ( command_line : : get_arg ( vm , command_line : : arg_help ) )
{
2018-04-10 06:49:20 +02:00
std : : cout < < " Loki ' " < < LOKI_RELEASE_NAME < < " ' (v " < < LOKI_VERSION_FULL < < " ) " < < ENDL < < ENDL ;
2018-02-26 17:39:40 +01:00
std : : cout < < desc_options < < std : : endl ;
return 1 ;
}
2019-01-18 00:47:04 +01:00
mlog_configure ( mlog_get_default_log_path ( " loki-blockchain-mark-spent-outputs.log " ) , true ) ;
2018-02-26 17:39:40 +01:00
if ( ! command_line : : is_arg_defaulted ( vm , arg_log_level ) )
mlog_set_log ( command_line : : get_arg ( vm , arg_log_level ) . c_str ( ) ) ;
else
mlog_set_log ( std : : string ( std : : to_string ( log_level ) + " ,bcutil:INFO " ) . c_str ( ) ) ;
LOG_PRINT_L0 ( " Starting... " ) ;
output_file_path = command_line : : get_arg ( vm , arg_blackball_db_dir ) ;
bool opt_rct_only = command_line : : get_arg ( vm , arg_rct_only ) ;
2018-08-14 12:57:39 +02:00
bool opt_check_subsets = command_line : : get_arg ( vm , arg_check_subsets ) ;
2018-08-19 18:52:56 +02:00
bool opt_verbose = command_line : : get_arg ( vm , arg_verbose ) ;
2018-08-30 11:11:53 +02:00
bool opt_force_chain_reaction_pass = command_line : : get_arg ( vm , arg_force_chain_reaction_pass ) ;
2018-08-29 18:57:12 +02:00
std : : string opt_export = command_line : : get_arg ( vm , arg_export ) ;
std : : string extra_spent_list = command_line : : get_arg ( vm , arg_extra_spent_list ) ;
std : : vector < std : : pair < uint64_t , uint64_t > > extra_spent_outputs = extra_spent_list . empty ( ) ? std : : vector < std : : pair < uint64_t , uint64_t > > ( ) : load_outputs ( extra_spent_list ) ;
2018-02-26 17:39:40 +01:00
std : : string db_type = command_line : : get_arg ( vm , arg_database ) ;
if ( ! cryptonote : : blockchain_valid_db_type ( db_type ) )
{
std : : cerr < < " Invalid database type: " < < db_type < < std : : endl ;
return 1 ;
}
2018-08-12 23:43:33 +02:00
std : : string db_sync_mode = command_line : : get_arg ( vm , arg_db_sync_mode ) ;
if ( ! parse_db_sync_mode ( db_sync_mode ) )
{
MERROR ( " Invalid db sync mode: " < < db_sync_mode ) ;
return 1 ;
}
2018-02-26 17:39:40 +01:00
const std : : vector < std : : string > inputs = command_line : : get_arg ( vm , arg_inputs ) ;
if ( inputs . empty ( ) )
{
LOG_PRINT_L0 ( " No inputs given " ) ;
return 1 ;
}
2018-06-29 06:47:00 +02:00
std : : vector < Blockchain * > core_storage ( inputs . size ( ) ) ;
2018-02-26 17:39:40 +01:00
for ( size_t n = 0 ; n < inputs . size ( ) ; + + n )
{
2018-12-19 02:25:48 +01:00
blockchain_objects_t * blockchain_objects = new blockchain_objects_t ( ) ;
2018-06-29 06:47:00 +02:00
core_storage [ n ] = & ( blockchain_objects - > m_blockchain ) ;
2018-10-10 01:30:31 +02:00
}
2018-02-26 17:39:40 +01:00
2018-10-18 20:05:51 +02:00
const std : : string cache_dir = ( output_file_path / " spent-outputs-cache " ) . string ( ) ;
2018-08-12 23:43:33 +02:00
init ( cache_dir ) ;
2018-02-26 17:39:40 +01:00
2018-10-18 20:05:51 +02:00
LOG_PRINT_L0 ( " Scanning for spent outputs... " ) ;
2018-02-26 17:39:40 +01:00
size_t done = 0 ;
2018-08-12 23:46:02 +02:00
const uint64_t start_blackballed_outputs = get_num_spent_outputs ( ) ;
2018-02-26 17:39:40 +01:00
2018-08-12 07:54:38 +02:00
tools : : ringdb ringdb ( output_file_path . string ( ) , epee : : string_tools : : pod_to_hex ( get_genesis_block_hash ( inputs [ 0 ] ) ) ) ;
2018-02-26 17:39:40 +01:00
2018-07-30 12:14:02 +02:00
bool stop_requested = false ;
tools : : signal_handler : : install ( [ & stop_requested ] ( int type ) {
stop_requested = true ;
} ) ;
2018-02-26 17:39:40 +01:00
2018-08-12 23:43:33 +02:00
int dbr = resize_env ( cache_dir . c_str ( ) ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to resize LMDB database: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-12 07:54:38 +02:00
// open first db
MDB_env * env0 ;
MDB_txn * txn0 ;
MDB_dbi dbi0 ;
MDB_cursor * cur0 ;
open_db ( inputs [ 0 ] , & env0 , & txn0 , & cur0 , & dbi0 ) ;
2018-03-18 11:55:21 +01:00
2018-08-29 18:57:12 +02:00
if ( ! extra_spent_outputs . empty ( ) )
2018-03-18 11:55:21 +01:00
{
2018-08-29 18:57:12 +02:00
MINFO ( " Adding " < < extra_spent_outputs . size ( ) < < " extra spent outputs " ) ;
MDB_txn * txn ;
int dbr = mdb_txn_begin ( env , NULL , 0 , & txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
MDB_cursor * cur ;
dbr = mdb_cursor_open ( txn , dbi_spent , & cur ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB cursor: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
std : : vector < std : : pair < uint64_t , uint64_t > > blackballs ;
for ( const std : : pair < uint64_t , uint64_t > & output : extra_spent_outputs )
2018-03-18 11:55:21 +01:00
{
2018-08-29 18:57:12 +02:00
if ( ! is_output_spent ( cur , output_data ( output . first , output . second ) ) )
{
blackballs . push_back ( output ) ;
2018-10-08 00:20:27 +02:00
if ( add_spent_output ( cur , output_data ( output . first , output . second ) ) )
inc_stat ( txn , output . first ? " pre-rct-extra " : " rct-ring-extra " ) ;
2018-08-29 18:57:12 +02:00
}
2018-03-18 11:55:21 +01:00
}
2018-08-29 18:57:12 +02:00
if ( ! blackballs . empty ( ) )
2018-03-18 11:55:21 +01:00
{
2018-08-29 18:57:12 +02:00
ringdb . blackball ( blackballs ) ;
blackballs . clear ( ) ;
2018-03-18 11:55:21 +01:00
}
2018-08-29 18:57:12 +02:00
mdb_cursor_close ( cur ) ;
dbr = mdb_txn_commit ( txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to commit txn creating/opening database: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-03-18 11:55:21 +01:00
}
2018-07-30 12:14:02 +02:00
2018-02-26 17:39:40 +01:00
for ( size_t n = 0 ; n < inputs . size ( ) ; + + n )
{
2018-03-18 11:55:21 +01:00
const std : : string canonical = boost : : filesystem : : canonical ( inputs [ n ] ) . string ( ) ;
2018-08-12 23:43:33 +02:00
uint64_t start_idx = get_processed_txidx ( canonical ) ;
2018-08-18 14:40:26 +02:00
if ( n > 0 & & start_idx = = 0 )
{
start_idx = find_first_diverging_transaction ( inputs [ 0 ] , inputs [ n ] ) ;
LOG_PRINT_L0 ( " First diverging transaction at " < < start_idx ) ;
}
2018-03-18 11:55:21 +01:00
LOG_PRINT_L0 ( " Reading blockchain from " < < inputs [ n ] < < " from " < < start_idx ) ;
2018-08-12 23:43:33 +02:00
MDB_txn * txn ;
int dbr = mdb_txn_begin ( env , NULL , 0 , & txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-19 18:52:56 +02:00
MDB_cursor * cur ;
dbr = mdb_cursor_open ( txn , dbi_spent , & cur ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB cursor: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-12 23:43:33 +02:00
size_t records = 0 ;
2018-08-12 07:54:38 +02:00
const std : : string filename = inputs [ n ] ;
2018-08-29 18:57:12 +02:00
std : : vector < std : : pair < uint64_t , uint64_t > > blackballs ;
2018-08-12 07:54:38 +02:00
uint64_t n_txes ;
for_all_transactions ( filename , start_idx , n_txes , [ & ] ( const cryptonote : : transaction_prefix & tx ) - > bool
2018-02-26 17:39:40 +01:00
{
2018-08-12 23:43:33 +02:00
std : : cout < < " \r " < < start_idx < < " / " < < n_txes < < " \r " < < std : : flush ;
2018-11-24 11:52:20 +01:00
const bool miner_tx = tx . vin . size ( ) = = 1 & & tx . vin [ 0 ] . type ( ) = = typeid ( txin_gen ) ;
2018-02-26 17:39:40 +01:00
for ( const auto & in : tx . vin )
{
if ( in . type ( ) ! = typeid ( txin_to_key ) )
continue ;
const auto & txin = boost : : get < txin_to_key > ( in ) ;
if ( opt_rct_only & & txin . amount ! = 0 )
continue ;
const std : : vector < uint64_t > absolute = cryptonote : : relative_output_offsets_to_absolute ( txin . key_offsets ) ;
if ( n = = 0 )
for ( uint64_t out : absolute )
2018-08-12 23:43:33 +02:00
add_key_image ( txn , output_data ( txin . amount , out ) , txin . k_image ) ;
2018-02-26 17:39:40 +01:00
2018-08-12 23:43:33 +02:00
std : : vector < uint64_t > relative_ring ;
2018-04-19 10:36:31 +02:00
std : : vector < uint64_t > new_ring = canonicalize ( txin . key_offsets ) ;
2018-02-26 17:39:40 +01:00
const uint32_t ring_size = txin . key_offsets . size ( ) ;
2018-08-19 18:52:56 +02:00
const uint64_t instances = inc_ring_instances ( txn , txin . amount , new_ring ) ;
2018-11-24 11:52:20 +01:00
uint64_t pa_total = 0 , pa_spent = 0 ;
if ( ! opt_rct_only )
get_per_amount_outputs ( txn , txin . amount , pa_total , pa_spent ) ;
2018-08-12 07:54:38 +02:00
if ( n = = 0 & & ring_size = = 1 )
2018-02-26 17:39:40 +01:00
{
2018-08-29 18:57:12 +02:00
const std : : pair < uint64_t , uint64_t > output = std : : make_pair ( txin . amount , absolute [ 0 ] ) ;
2018-08-19 18:52:56 +02:00
if ( opt_verbose )
{
2018-10-18 20:05:51 +02:00
MINFO ( " Marking output " < < output . first < < " / " < < output . second < < " as spent, due to being used in a 1-ring " ) ;
2018-08-19 18:52:56 +02:00
std : : cout < < " \r " < < start_idx < < " / " < < n_txes < < " \r " < < std : : flush ;
}
2018-08-29 18:57:12 +02:00
blackballs . push_back ( output ) ;
2018-10-08 00:20:27 +02:00
if ( add_spent_output ( cur , output_data ( txin . amount , absolute [ 0 ] ) ) )
inc_stat ( txn , txin . amount ? " pre-rct-ring-size-1 " : " rct-ring-size-1 " ) ;
2018-02-26 17:39:40 +01:00
}
2018-08-12 07:54:38 +02:00
else if ( n = = 0 & & instances = = new_ring . size ( ) )
2018-04-19 10:36:31 +02:00
{
for ( size_t o = 0 ; o < new_ring . size ( ) ; + + o )
{
2018-08-29 18:57:12 +02:00
const std : : pair < uint64_t , uint64_t > output = std : : make_pair ( txin . amount , absolute [ o ] ) ;
2018-08-19 18:52:56 +02:00
if ( opt_verbose )
{
2018-10-18 20:05:51 +02:00
MINFO ( " Marking output " < < output . first < < " / " < < output . second < < " as spent, due to being used in " < < new_ring . size ( ) < < " identical " < < new_ring . size ( ) < < " -rings " ) ;
2018-08-19 18:52:56 +02:00
std : : cout < < " \r " < < start_idx < < " / " < < n_txes < < " \r " < < std : : flush ;
}
2018-08-29 18:57:12 +02:00
blackballs . push_back ( output ) ;
2018-10-08 19:36:51 +02:00
if ( add_spent_output ( cur , output_data ( txin . amount , absolute [ o ] ) ) )
2018-10-08 00:20:27 +02:00
inc_stat ( txn , txin . amount ? " pre-rct-duplicate-rings " : " rct-duplicate-rings " ) ;
2018-04-19 10:36:31 +02:00
}
2018-02-26 17:39:40 +01:00
}
2018-11-24 11:52:20 +01:00
else if ( n = = 0 & & ! opt_rct_only & & pa_spent + 1 = = pa_total )
{
for ( size_t o = 0 ; o < pa_total ; + + o )
{
const std : : pair < uint64_t , uint64_t > output = std : : make_pair ( txin . amount , o ) ;
if ( opt_verbose )
{
MINFO ( " Marking output " < < output . first < < " / " < < output . second < < " as spent, due to as many outputs of that amount being spent as exist so far " ) ;
std : : cout < < " \r " < < start_idx < < " / " < < n_txes < < " \r " < < std : : flush ;
}
blackballs . push_back ( output ) ;
if ( add_spent_output ( cur , output_data ( txin . amount , o ) ) )
inc_stat ( txn , txin . amount ? " pre-rct-full-count " : " rct-full-count " ) ;
}
}
2018-08-14 12:57:39 +02:00
else if ( n = = 0 & & opt_check_subsets & & get_ring_subset_instances ( txn , txin . amount , new_ring ) > = new_ring . size ( ) )
2018-02-26 17:39:40 +01:00
{
2018-08-14 12:57:39 +02:00
for ( size_t o = 0 ; o < new_ring . size ( ) ; + + o )
{
2018-08-29 18:57:12 +02:00
const std : : pair < uint64_t , uint64_t > output = std : : make_pair ( txin . amount , absolute [ o ] ) ;
2018-08-19 18:52:56 +02:00
if ( opt_verbose )
{
2018-10-18 20:05:51 +02:00
MINFO ( " Marking output " < < output . first < < " / " < < output . second < < " as spent, due to being used in " < < new_ring . size ( ) < < " subsets of " < < new_ring . size ( ) < < " -rings " ) ;
2018-08-19 18:52:56 +02:00
std : : cout < < " \r " < < start_idx < < " / " < < n_txes < < " \r " < < std : : flush ;
}
2018-08-29 18:57:12 +02:00
blackballs . push_back ( output ) ;
2018-10-08 00:20:27 +02:00
if ( add_spent_output ( cur , output_data ( txin . amount , absolute [ o ] ) ) )
inc_stat ( txn , txin . amount ? " pre-rct-subset-rings " : " rct-subset-rings " ) ;
2018-08-14 12:57:39 +02:00
}
}
2018-08-12 07:54:38 +02:00
else if ( n > 0 & & get_relative_ring ( txn , txin . k_image , relative_ring ) )
2018-02-26 17:39:40 +01:00
{
2018-08-12 07:54:38 +02:00
MDEBUG ( " Key image " < < txin . k_image < < " already seen: rings " < <
2018-08-12 23:43:33 +02:00
boost : : join ( relative_ring | boost : : adaptors : : transformed ( [ ] ( uint64_t out ) { return std : : to_string ( out ) ; } ) , " " ) < <
2018-02-26 17:39:40 +01:00
" , " < < boost : : join ( txin . key_offsets | boost : : adaptors : : transformed ( [ ] ( uint64_t out ) { return std : : to_string ( out ) ; } ) , " " ) ) ;
2018-08-12 23:43:33 +02:00
std : : cout < < " \r " < < start_idx < < " / " < < n_txes < < " \r " < < std : : flush ;
if ( relative_ring ! = txin . key_offsets )
2018-02-26 17:39:40 +01:00
{
2018-08-12 07:54:38 +02:00
MDEBUG ( " Rings are different " ) ;
2018-08-12 23:43:33 +02:00
std : : cout < < " \r " < < start_idx < < " / " < < n_txes < < " \r " < < std : : flush ;
const std : : vector < uint64_t > r0 = cryptonote : : relative_output_offsets_to_absolute ( relative_ring ) ;
2018-02-26 17:39:40 +01:00
const std : : vector < uint64_t > r1 = cryptonote : : relative_output_offsets_to_absolute ( txin . key_offsets ) ;
std : : vector < uint64_t > common ;
for ( uint64_t out : r0 )
{
if ( std : : find ( r1 . begin ( ) , r1 . end ( ) , out ) ! = r1 . end ( ) )
common . push_back ( out ) ;
}
if ( common . empty ( ) )
{
MERROR ( " Rings for the same key image are disjoint " ) ;
2018-08-12 23:43:33 +02:00
std : : cout < < " \r " < < start_idx < < " / " < < n_txes < < " \r " < < std : : flush ;
2018-02-26 17:39:40 +01:00
}
else if ( common . size ( ) = = 1 )
{
2018-08-29 18:57:12 +02:00
const std : : pair < uint64_t , uint64_t > output = std : : make_pair ( txin . amount , common [ 0 ] ) ;
2018-08-19 18:52:56 +02:00
if ( opt_verbose )
{
2018-10-18 20:05:51 +02:00
MINFO ( " Marking output " < < output . first < < " / " < < output . second < < " as spent, due to being used in rings with a single common element " ) ;
2018-08-19 18:52:56 +02:00
std : : cout < < " \r " < < start_idx < < " / " < < n_txes < < " \r " < < std : : flush ;
}
2018-08-29 18:57:12 +02:00
blackballs . push_back ( output ) ;
2018-10-08 00:20:27 +02:00
if ( add_spent_output ( cur , output_data ( txin . amount , common [ 0 ] ) ) )
inc_stat ( txn , txin . amount ? " pre-rct-key-image-attack " : " rct-key-image-attack " ) ;
2018-02-26 17:39:40 +01:00
}
else
{
2018-08-12 07:54:38 +02:00
MDEBUG ( " The intersection has more than one element, it's still ok " ) ;
2018-08-12 23:43:33 +02:00
std : : cout < < " \r " < < start_idx < < " / " < < n_txes < < " \r " < < std : : flush ;
2018-02-26 17:39:40 +01:00
for ( const auto & out : r0 )
if ( std : : find ( common . begin ( ) , common . end ( ) , out ) ! = common . end ( ) )
new_ring . push_back ( out ) ;
new_ring = cryptonote : : absolute_output_offsets_to_relative ( new_ring ) ;
}
}
}
2018-08-12 07:54:38 +02:00
if ( n = = 0 )
2018-11-24 11:52:20 +01:00
{
2018-08-12 07:54:38 +02:00
set_relative_ring ( txn , txin . k_image , new_ring ) ;
2018-11-24 11:52:20 +01:00
if ( ! opt_rct_only )
inc_per_amount_outputs ( txn , txin . amount , 0 , 1 ) ;
}
2018-08-12 23:43:33 +02:00
}
2018-08-12 23:46:02 +02:00
set_processed_txidx ( txn , canonical , start_idx + 1 ) ;
2018-11-24 11:52:20 +01:00
if ( ! opt_rct_only )
{
for ( const auto & out : tx . vout )
{
uint64_t amount = out . amount ;
2019-06-11 20:53:46 +02:00
if ( miner_tx & & tx . version > = cryptonote : : txversion : : v2_ringct )
2018-11-24 11:52:20 +01:00
amount = 0 ;
if ( opt_rct_only & & amount ! = 0 )
continue ;
if ( out . target . type ( ) ! = typeid ( txout_to_key ) )
continue ;
inc_per_amount_outputs ( txn , amount , 1 , 0 ) ;
}
}
2018-08-12 23:43:33 +02:00
+ + records ;
if ( records > = records_per_sync )
{
2018-08-19 18:52:56 +02:00
if ( ! blackballs . empty ( ) )
{
ringdb . blackball ( blackballs ) ;
blackballs . clear ( ) ;
}
mdb_cursor_close ( cur ) ;
2018-08-12 23:43:33 +02:00
dbr = mdb_txn_commit ( txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to commit txn creating/opening database: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
int dbr = resize_env ( cache_dir . c_str ( ) ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to resize LMDB database: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
dbr = mdb_txn_begin ( env , NULL , 0 , & txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-19 18:52:56 +02:00
dbr = mdb_cursor_open ( txn , dbi_spent , & cur ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB cursor: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-12 23:43:33 +02:00
records = 0 ;
2018-02-26 17:39:40 +01:00
}
2018-08-12 23:43:33 +02:00
2018-07-30 12:14:02 +02:00
if ( stop_requested )
{
2018-08-14 17:06:16 +02:00
MINFO ( " Stopping scan... " ) ;
2018-07-30 12:14:02 +02:00
return false ;
}
2018-02-26 17:39:40 +01:00
return true ;
} ) ;
2018-08-19 18:52:56 +02:00
mdb_cursor_close ( cur ) ;
2018-08-12 23:43:33 +02:00
dbr = mdb_txn_commit ( txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to commit txn creating/opening database: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
LOG_PRINT_L0 ( " blockchain from " < < inputs [ n ] < < " processed till tx idx " < < start_idx ) ;
2018-07-30 12:14:02 +02:00
if ( stop_requested )
break ;
2018-02-26 17:39:40 +01:00
}
2018-08-12 23:46:02 +02:00
std : : vector < output_data > work_spent ;
2018-08-14 17:06:16 +02:00
if ( stop_requested )
goto skip_secondary_passes ;
2018-08-30 11:11:53 +02:00
if ( opt_force_chain_reaction_pass | | get_num_spent_outputs ( ) > start_blackballed_outputs )
2018-08-12 23:46:02 +02:00
{
MDB_txn * txn ;
dbr = mdb_txn_begin ( env , NULL , MDB_RDONLY , & txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
work_spent = get_spent_outputs ( txn ) ;
mdb_txn_abort ( txn ) ;
}
while ( ! work_spent . empty ( ) )
2018-02-26 17:39:40 +01:00
{
2018-08-12 23:46:02 +02:00
LOG_PRINT_L0 ( " Secondary pass on " < < work_spent . size ( ) < < " spent outputs " ) ;
2018-02-26 17:39:40 +01:00
2018-08-12 23:43:33 +02:00
int dbr = resize_env ( cache_dir . c_str ( ) ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to resize LMDB database: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-07-30 18:22:01 +02:00
2018-08-12 23:43:33 +02:00
MDB_txn * txn ;
dbr = mdb_txn_begin ( env , NULL , 0 , & txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-19 18:52:56 +02:00
MDB_cursor * cur ;
dbr = mdb_cursor_open ( txn , dbi_spent , & cur ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB cursor: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-08-12 23:43:33 +02:00
2018-08-29 18:57:12 +02:00
std : : vector < std : : pair < uint64_t , uint64_t > > blackballs ;
2018-08-12 23:46:02 +02:00
std : : vector < output_data > scan_spent = std : : move ( work_spent ) ;
work_spent . clear ( ) ;
for ( const output_data & od : scan_spent )
2018-02-26 17:39:40 +01:00
{
2018-08-12 23:43:33 +02:00
std : : vector < crypto : : key_image > key_images = get_key_images ( txn , od ) ;
for ( const crypto : : key_image & ki : key_images )
2018-02-26 17:39:40 +01:00
{
2018-08-12 23:43:33 +02:00
std : : vector < uint64_t > relative_ring ;
CHECK_AND_ASSERT_THROW_MES ( get_relative_ring ( txn , ki , relative_ring ) , " Relative ring not found " ) ;
std : : vector < uint64_t > absolute = cryptonote : : relative_output_offsets_to_absolute ( relative_ring ) ;
2018-02-26 17:39:40 +01:00
size_t known = 0 ;
uint64_t last_unknown = 0 ;
for ( uint64_t out : absolute )
{
output_data new_od ( od . amount , out ) ;
2018-08-19 18:52:56 +02:00
if ( is_output_spent ( cur , new_od ) )
2018-02-26 17:39:40 +01:00
+ + known ;
else
last_unknown = out ;
}
if ( known = = absolute . size ( ) - 1 )
{
2018-08-29 18:57:12 +02:00
const std : : pair < uint64_t , uint64_t > output = std : : make_pair ( od . amount , last_unknown ) ;
2018-08-19 18:52:56 +02:00
if ( opt_verbose )
{
2018-10-18 20:05:51 +02:00
MINFO ( " Marking output " < < output . first < < " / " < < output . second < < " as spent, due to being used in a " < <
2018-08-19 18:52:56 +02:00
absolute . size ( ) < < " -ring where all other outputs are known to be spent " ) ;
}
2018-08-29 18:57:12 +02:00
blackballs . push_back ( output ) ;
2018-10-08 00:20:27 +02:00
if ( add_spent_output ( cur , output_data ( od . amount , last_unknown ) ) )
inc_stat ( txn , od . amount ? " pre-rct-chain-reaction " : " rct-chain-reaction " ) ;
2018-08-12 23:46:02 +02:00
work_spent . push_back ( output_data ( od . amount , last_unknown ) ) ;
2018-02-26 17:39:40 +01:00
}
}
2018-08-14 17:06:16 +02:00
if ( stop_requested )
{
MINFO ( " Stopping secondary passes. Secondary passes are not incremental, they will re-run fully. " ) ;
2018-09-14 22:08:52 +02:00
return 0 ;
2018-08-14 17:06:16 +02:00
}
2018-03-18 11:55:21 +01:00
}
2018-08-12 23:43:33 +02:00
if ( ! blackballs . empty ( ) )
2018-03-18 11:55:21 +01:00
{
2018-08-12 23:43:33 +02:00
ringdb . blackball ( blackballs ) ;
blackballs . clear ( ) ;
2018-03-18 11:55:21 +01:00
}
2018-08-19 18:52:56 +02:00
mdb_cursor_close ( cur ) ;
2018-08-12 23:43:33 +02:00
dbr = mdb_txn_commit ( txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to commit txn creating/opening database: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
2018-03-18 11:55:21 +01:00
}
2018-08-14 17:06:16 +02:00
skip_secondary_passes :
2018-08-12 23:46:02 +02:00
uint64_t diff = get_num_spent_outputs ( ) - start_blackballed_outputs ;
2018-10-18 20:05:51 +02:00
LOG_PRINT_L0 ( std : : to_string ( diff ) < < " new outputs marked as spent, " < < get_num_spent_outputs ( ) < < " total outputs marked as spent " ) ;
2018-08-12 11:47:03 +02:00
MDB_txn * txn ;
dbr = mdb_txn_begin ( env , NULL , MDB_RDONLY , & txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
uint64_t pre_rct = 0 , rct = 0 ;
get_num_outputs ( txn0 , cur0 , dbi0 , pre_rct , rct ) ;
MINFO ( " Total pre-rct outputs: " < < pre_rct ) ;
MINFO ( " Total rct outputs: " < < rct ) ;
static const struct { const char * key ; uint64_t base ; } stat_keys [ ] = {
{ " pre-rct-ring-size-1 " , pre_rct } , { " rct-ring-size-1 " , rct } ,
{ " pre-rct-duplicate-rings " , pre_rct } , { " rct-duplicate-rings " , rct } ,
2018-08-14 12:57:39 +02:00
{ " pre-rct-subset-rings " , pre_rct } , { " rct-subset-rings " , rct } ,
2018-11-24 11:52:20 +01:00
{ " pre-rct-full-count " , pre_rct } , { " rct-full-count " , rct } ,
2018-08-12 11:47:03 +02:00
{ " pre-rct-key-image-attack " , pre_rct } , { " rct-key-image-attack " , rct } ,
2018-08-29 18:57:12 +02:00
{ " pre-rct-extra " , pre_rct } , { " rct-ring-extra " , rct } ,
2018-08-12 11:47:03 +02:00
{ " pre-rct-chain-reaction " , pre_rct } , { " rct-chain-reaction " , rct } ,
} ;
for ( const auto & key : stat_keys )
{
uint64_t data ;
if ( ! get_stat ( txn , key . key , data ) )
data = 0 ;
float percent = key . base ? 100.0f * data / key . base : 0.0f ;
MINFO ( key . key < < " : " < < data < < " ( " < < percent < < " %) " ) ;
}
mdb_txn_abort ( txn ) ;
2018-08-29 18:57:12 +02:00
if ( ! opt_export . empty ( ) )
{
MDB_txn * txn ;
int dbr = mdb_txn_begin ( env , NULL , 0 , & txn ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to create LMDB transaction: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
MDB_cursor * cur ;
dbr = mdb_cursor_open ( txn , dbi_spent , & cur ) ;
CHECK_AND_ASSERT_THROW_MES ( ! dbr , " Failed to open LMDB cursor: " + std : : string ( mdb_strerror ( dbr ) ) ) ;
export_spent_outputs ( cur , opt_export ) ;
mdb_cursor_close ( cur ) ;
mdb_txn_abort ( txn ) ;
2018-03-18 11:55:21 +01:00
}
2018-10-18 20:05:51 +02:00
LOG_PRINT_L0 ( " Blockchain spent output data exported OK " ) ;
2018-08-12 07:54:38 +02:00
close_db ( env0 , txn0 , cur0 , dbi0 ) ;
2018-08-12 23:43:33 +02:00
close ( ) ;
2018-02-26 17:39:40 +01:00
return 0 ;
2018-08-12 23:43:33 +02:00
CATCH_ENTRY ( " Error " , 1 ) ;
2018-02-26 17:39:40 +01:00
}