diff --git a/libraries/blockchain/CMakeLists.txt b/libraries/blockchain/CMakeLists.txt index dd5fcb93cfc..011140882c6 100644 --- a/libraries/blockchain/CMakeLists.txt +++ b/libraries/blockchain/CMakeLists.txt @@ -1,6 +1,6 @@ file(GLOB HEADERS "include/eosio/blockchain/*.hpp") -add_library( eosio_blockchain controller.cpp database.cpp) +add_library( eosio_blockchain name.cpp generic_table.cpp db.cpp controller.cpp ) target_link_libraries( eosio_blockchain fc chainbase eos_types ) target_include_directories( eosio_blockchain PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/inlcude" "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../wasm-jit/Include" ) diff --git a/libraries/blockchain/db.cpp b/libraries/blockchain/db.cpp new file mode 100644 index 00000000000..a1408522553 --- /dev/null +++ b/libraries/blockchain/db.cpp @@ -0,0 +1,239 @@ +#include + +namespace eosio { namespace blockchain { + +struct environment_check { + environment_check() { + memset( &compiler_version, 0, sizeof( compiler_version ) ); + memcpy( &compiler_version, __VERSION__, std::min( strlen(__VERSION__), 256 ) ); +#ifndef NDEBUG + debug = true; +#endif +#ifdef __APPLE__ + apple = true; +#endif +#ifdef WIN32 + windows = true; +#endif + } + friend bool operator == ( const environment_check& a, const environment_check& b ) { + return std::make_tuple( a.compiler_version, a.debug, a.apple, a.windows ) + == std::make_tuple( b.compiler_version, b.debug, b.apple, b.windows ); + } + + boost::array compiler_version; + bool debug = false; + bool apple = false; + bool windows = false; +}; + +database::database( const path& dir, uint64_t shared_file_size ) { + _data_dir = dir; + bfs::create_directories(dir); + auto abs_path = bfs::absolute( dir / "shared_memory.bin" ); + + if( bfs::exists( abs_path ) ) + { + if( true /*write*/ ) + { + auto existing_file_size = bfs::file_size( abs_path ); + if( shared_file_size > existing_file_size ) + { + if( !bip::managed_mapped_file::grow( abs_path.generic_string().c_str(), shared_file_size - existing_file_size ) ) + BOOST_THROW_EXCEPTION( std::runtime_error( "could not grow database file to requested size." ) ); + } + + _segment.reset( new bip::managed_mapped_file( bip::open_only, + abs_path.generic_string().c_str() + ) ); + + auto env = _segment->find< environment_check >( "environment" ); + if( !env.first || !( *env.first == environment_check()) ) { + BOOST_THROW_EXCEPTION( std::runtime_error( "database created by a different compiler, build, or operating system" ) ); + } + + _scopes = _segment->find< scopes_type >( "scopes" ).first; + if( !_scopes ) { + BOOST_THROW_EXCEPTION( std::runtime_error( "unable to find 'scopes' in shared memory file" ) ); + } + _block_undo_history = _segment->find< shared_deque >( "block_undo_stack" ).first; + if( !_block_undo_history ) { + BOOST_THROW_EXCEPTION( std::runtime_error( "unable to find 'block_undo_stack' in shared memory file" ) ); + } + + } + } else { // no shared memory file exists + FC_ASSERT( shared_file_size > 0, "attempting to create a database with size 0" ); + ilog( "creating new database file: ${db}", ("db", abs_path.generic_string()) ); + _segment.reset( new bip::managed_mapped_file( bip::create_only, + abs_path.generic_string().c_str(), shared_file_size + ) ); + _segment->find_or_construct< environment_check >( "environment" )(); + + _scopes = _segment->find_or_construct< scopes_type >( "scopes" )( _segment->get_segment_manager() ); + _block_undo_history = _segment->find_or_construct< shared_deque >( "block_undo_stack" )( _segment->get_segment_manager() ); + } +} + +database::~database() { +} // ~database + +/** + * @return -1 if there is no undo history, otherwise returns the first block for which there exists an undo state + */ +block_num_type database::last_reversible_block() const { + if( _block_undo_history->size() == 0 ) return block_num_type(-1); + return _block_undo_history->front()._block_num; +} + +/** + * Starts a new block, if a current undo history exists it must be a sequential increment on the current block number, + * otherwise, if the last_reversible_block == max_block_num then it can be any block number. + */ +block_handle database::start_block( block_num_type num ) { + if( last_reversible_block() != max_block_num ) { + auto& head_udb = _block_undo_history->back(); + FC_ASSERT( head_udb._block_num + 1 == num, "new blocks must have sequential block numbers" ); + } + _block_undo_history->emplace_back( _segment->get_segment_manager() ); + auto& udb = _block_undo_history->back(); + udb._block_num = num; + + return block_handle( *this, udb ); +} /// start_block + +cycle_handle block_handle::start_cycle() { + _undo_block._cycle_undo_history.emplace_back( _db.get_allocator() ); + return cycle_handle( *this, _undo_block._cycle_undo_history.back() ); +} + + +cycle_handle::cycle_handle( const block_handle& blk, undo_cycle& cyc ) +:_db(blk._db),_undo_block(blk._undo_block),_undo_cycle(cyc){} + +shard_handle cycle_handle::start_shard( const set& write, const set& read ) { + /// TODO: verify there are no conflicts in scopes, then create new shard + auto itr = _undo_cycle._shards.emplace( _undo_cycle._shards.begin(), _db.get_allocator(), write, read ); + return shard_handle( *this, *itr ); +} + +shard_handle::shard_handle( cycle_handle& c, shard& s ) +:_db(c._db),_shard(s) { +} + +transaction_handle shard_handle::start_transaction() { + auto& hist = _shard._transaction_history; + auto itr = hist.emplace( hist.end(), _db.get_allocator(), std::ref(_shard) ); + + return transaction_handle( *this, *itr ); +} + +shard::shard( allocator a, const set& w, const set& r ) +:_write_scope(w.begin(),w.end(),a),_read_scope(r.begin(),r.end(),a),_transaction_history(a){} + + +transaction_handle::transaction_handle( shard_handle& sh, undo_transaction& udt ) +:_db(sh._db),_undo_trx(udt) { +} + +scope_handle transaction_handle::create_scope( scope_name s ) { + /// TODO: this method is only valid if we have scope-scope in the shard + auto existing_scope = _db.find_scope( s ); + FC_ASSERT( !existing_scope, "scope ${s} already exists", ("s",s) ); + + auto new_scope = _db.create_scope(s); + _undo_trx.new_scopes.push_back(s); + + return scope_handle( *this, *new_scope ); +} + + +scope_handle::scope_handle( transaction_handle& trx, scope& s ) +:_scope(s),_trx(trx){} + + + +scope* database::create_scope( scope_name n ) { + auto result = _scopes->insert( pair( n, get_allocator() ) ); + FC_ASSERT( result.second, "unable to insert new scope ${n}", ("n",n) ); + wlog( "created scope ${s}", ("s",name(n))); + return &result.first->second; +} +void database::delete_scope( scope_name n ) { + // auto itr = _scopes->find(n); + // FC_ASSERT( itr != _scopes->end(), "unable to find scope to delete it" ); + _scopes->erase(n); + idump((name(n))); +} + +const scope* database::find_scope( scope_name n )const { + auto itr = _scopes->find( n ); + if( itr == _scopes->end() ) return nullptr; + return &itr->second; +} // find_scope + +const scope& database::get_scope( scope_name n )const { + auto ptr = find_scope( n ); + FC_ASSERT( ptr != nullptr, "unable to find expected scope ${n}", ("n",name(n)) ); + return *ptr; +} + + +const table* scope::find_table( table_name t )const { + auto itr = _tables.find(t); + if( itr == _tables.end() ) return nullptr; + return itr->second.get(); +} + +void transaction_handle::on_create_table( table& t ) { + FC_ASSERT( _undo_trx.new_tables.insert(&t).second, "unable to insert table" ); +} + +void transaction_handle::undo() { + _applied = true; + _db.undo( _undo_trx ); +} + +void block_handle::undo() { + _db.undo( _undo_block ); +} + +/** + * After calling this @udt will be invalid + */ +void database::undo( undo_transaction& udt ) { + /// this transaction should be part of a shard, undoing it should remove the udt from _transaction_history + FC_ASSERT( &udt._shard->_transaction_history.back() == &udt, "must undo last transaction in shard first" ); + + for( const auto& item : udt.new_tables ) { + auto& t = *item; + auto& s = *t._scope; + s._tables.erase( t._name ); + get_abstract_table( t ).destruct( t, get_allocator() ); + } + udt.new_tables.clear(); + + for( const auto& s: udt.new_scopes ) { + FC_ASSERT( get_scope(s)._tables.size() == 0, "all tables should be deleted before deleting scope" ); + delete_scope( s ); + } + + /// frees the memory referenced by udt + udt._shard->_transaction_history.pop_back(); +} + +void database::undo( undo_block& udb ) { + auto& last = _block_undo_history->back(); + FC_ASSERT( &last == &udb, "attempt to undo block other than head" ); + for( auto& c : udb._cycle_undo_history ) { + for( auto& s : c._shards ) { + while( s._transaction_history.size() ) { + undo( s._transaction_history.back() ); + } + } + } + wlog( "poping block ${b}", ("b", udb._block_num) ); + _block_undo_history->pop_back(); +} + +} } // eosio::blockchain diff --git a/libraries/blockchain/include/eosio/blockchain/database.hpp b/libraries/blockchain/include/eosio/blockchain/database.hpp index 008374492fe..f4a22771522 100644 --- a/libraries/blockchain/include/eosio/blockchain/database.hpp +++ b/libraries/blockchain/include/eosio/blockchain/database.hpp @@ -1,169 +1,20 @@ #pragma once -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - +#include namespace eosio { namespace blockchain { - namespace bip = boost::interprocess; - namespace bfs = boost::filesystem; - using bip::offset_ptr; - using fc::optional; - - template - using allocator = bip::allocator; - - template - using shared_deque = bip::deque >; - - template - using shared_vector = std::vector >; - - template - using shared_set = bip::set< T, std::less, allocator >; - - template - using shared_list = bip::list< T, allocator >; - - template - using shared_map = bip::map< Key, Value, std::less, allocator< pair > >; - - typedef bip::basic_string< char, std::char_traits< char >, allocator< char > > shared_string; - - - template - using shared_multi_index_container = boost::multi_index_container >; - typedef uint64_t scope_name; typedef uint64_t table_name; - /** - * Object ID type that includes the type of the object it references - */ - template - struct oid { - oid( int64_t i = 0 ):_id(i){} - - oid& operator++() { ++_id; return *this; } - - friend bool operator < ( const oid& a, const oid& b ) { return a._id < b._id; } - friend bool operator > ( const oid& a, const oid& b ) { return a._id > b._id; } - friend bool operator == ( const oid& a, const oid& b ) { return a._id == b._id; } - friend bool operator != ( const oid& a, const oid& b ) { return a._id != b._id; } - friend std::ostream& operator<<(std::ostream& s, const oid& id) { - s << boost::core::demangle(typeid(oid).name()) << '(' << id._id << ')'; return s; - } - - int64_t _id = 0; - }; - - template - struct object { - typedef oid id_type; - enum type_id_enum { - type_id = TypeNumber - }; - }; - /** this class is ment to be specified to enable lookup of index type by object type using - * the SET_INDEX_TYPE macro. - **/ - template - struct get_index_type {}; - /** - * This macro must be used at global scope and OBJECT_TYPE and INDEX_TYPE must be fully qualified - */ - #define EOSIO_SET_INDEX_TYPE( OBJECT_TYPE, INDEX_TYPE ) \ - namespace eosio { namespace blockchain { template<> struct get_index_type { typedef INDEX_TYPE type; }; } } - - #define EOSIO_DEFAULT_CONSTRUCTOR( OBJECT_TYPE ) \ - template \ - OBJECT_TYPE( Constructor&& c, Allocator&& ) { c(*this); } - - - /** - * This backups up the state of generic_table so that it can be restored after any - * edits are made. - */ - template< typename ValueType > - struct table_undo_state - { - typedef typename ValueType::id_type id_type; - typedef allocator< std::pair > id_value_allocator_type; - typedef allocator< id_type > id_allocator_type; - - template - table_undo_state( allocator al ) - :old_values( id_value_allocator_type( al.get_segment_manager() ) ), - removed_values( id_value_allocator_type( al.get_segment_manager() ) ), - new_ids( id_allocator_type( al.get_segment_manager() ) ){} - - typedef shared_map id_value_type_map; - typedef shared_set< id_type > id_type_set; - - id_value_type_map old_values; - id_value_type_map removed_values; - id_type_set new_ids; - id_type old_next_id = 0; - - /** - * The revision the table will be in after restoring old_values, removed_values, etc. - */ - int64_t revision = 0; - }; struct scope_state; - /** - * Common base class for all tables, it includes the type of the data stored so - * that the proper virtual interface can be looked up for dealing with the true - * table. - */ - struct table { - offset_ptr _scope; /// weak reference, table is owned by scope - uint64_t _type = 0; - uint64_t _name = 0; - }; struct shard_undo_state; @@ -207,297 +58,6 @@ namespace eosio { namespace blockchain { - /** - * A generic_table wraps a boost multi_index container and provides version control along - * with a primary key type. - */ - template - struct generic_table : public table { - typedef bip::managed_mapped_file::segment_manager segment_manager_type; - typedef MultiIndexType index_type; - typedef typename index_type::value_type value_type; - typedef bip::allocator< generic_table, segment_manager_type > allocator_type; - typedef table_undo_state< value_type > undo_state_type; - typedef typename value_type::id_type id_type; - - - template - generic_table( Allocator&& a ) - :_stack(a),_indices(a) { - table::_type = value_type::type_id; - ilog( "generic_table constructor" ); - } - ~generic_table() { - ilog( "~generic_table destructor" ); - } - - /** - * Construct a new element in the multi_index_container. - * Set the ID to the next available ID, then increment _next_id and fire off on_create(). - */ - template - const value_type& emplace( Constructor&& c ) { - auto new_id = _next_id; - - auto constructor = [&]( value_type& v ) { - v.id = new_id; - c( v ); - }; - - auto insert_result = _indices.emplace( constructor, _indices.get_allocator() ); - - if( !insert_result.second ) { - BOOST_THROW_EXCEPTION( std::logic_error("could not insert object, most likely a uniqueness constraint was violated") ); - } - - ++_next_id; - on_create( *insert_result.first ); - return *insert_result.first; - } - - void remove( const value_type& obj ) { - on_remove( obj ); - _indices.erase( _indices.iterator_to( obj ) ); - } - - template - void modify( const value_type& obj, Modifier&& m ) { - on_modify( obj ); - auto ok = _indices.modify( _indices.iterator_to( obj ), m ); - if( !ok ) BOOST_THROW_EXCEPTION( std::logic_error( "Could not modify object, most likely a uniqueness constraint was violated" ) ); - } - - - int64_t revision()const { - return _stack.size() ? _stack.back().revision : 0; - } - - void start_revision( int64_t new_revision ) { - ilog( "table starting new revision ${r}", ("r", new_revision) ); - uint64_t prior_revision = revision(); - FC_ASSERT( new_revision > prior_revision, "attempting to create a new revision with older number" ); - _stack.emplace_back( _indices.get_allocator() ); - auto& newrev = _stack.back(); - newrev.old_next_id = _next_id; - newrev.revision = new_revision; - } - - /** - * Discards all undo history prior to revision - */ - void commit( int64_t rev ) { - while( _stack.size() && _stack[0].revision <= rev ) - { - _stack.pop_front(); - } - } - - /** - * Unwinds all undo states to the last commited revision - */ - void undo_all() { - while( enabled() ) - undo(); - } - - /** - * This method works similar to git squash, it merges the change set from the two most - * recent revision numbers into one revision number (reducing the head revision number) - * - * This method does not change the state of the index, only the state of the undo buffer. - */ - void squash() { - if( !enabled() ) return; - if( _stack.size() == 1 ) { - _stack.pop_front(); - return; - } - - auto& state = _stack.back(); - auto& prev_state = _stack[_stack.size()-2]; - - // An object's relationship to a state can be: - // in new_ids : new - // in old_values (was=X) : upd(was=X) - // in removed (was=X) : del(was=X) - // not in any of above : nop - // - // When merging A=prev_state and B=state we have a 4x4 matrix of all possibilities: - // - // |--------------------- B ----------------------| - // - // +------------+------------+------------+------------+ - // | new | upd(was=Y) | del(was=Y) | nop | - // +------------+------------+------------+------------+------------+ - // / | new | N/A | new A| nop C| new A| - // | +------------+------------+------------+------------+------------+ - // | | upd(was=X) | N/A | upd(was=X)A| del(was=X)C| upd(was=X)A| - // A +------------+------------+------------+------------+------------+ - // | | del(was=X) | N/A | N/A | N/A | del(was=X)A| - // | +------------+------------+------------+------------+------------+ - // \ | nop | new B| upd(was=Y)B| del(was=Y)B| nop AB| - // +------------+------------+------------+------------+------------+ - // - // Each entry was composed by labelling what should occur in the given case. - // - // Type A means the composition of states contains the same entry as the first of the two merged states for that object. - // Type B means the composition of states contains the same entry as the second of the two merged states for that object. - // Type C means the composition of states contains an entry different from either of the merged states for that object. - // Type N/A means the composition of states violates causal timing. - // Type AB means both type A and type B simultaneously. - // - // The merge() operation is defined as modifying prev_state in-place to be the state object which represents the composition of - // state A and B. - // - // Type A (and AB) can be implemented as a no-op; prev_state already contains the correct value for the merged state. - // Type B (and AB) can be implemented by copying from state to prev_state. - // Type C needs special case-by-case logic. - // Type N/A can be ignored or assert(false) as it can only occur if prev_state and state have illegal values - // (a serious logic error which should never happen). - // - - // We can only be outside type A/AB (the nop path) if B is not nop, so it suffices to iterate through B's three containers. - - for( const auto& item : state.old_values ) - { - if( prev_state.new_ids.find( item.second.id ) != prev_state.new_ids.end() ) - { - // new+upd -> new, type A - continue; - } - if( prev_state.old_values.find( item.second.id ) != prev_state.old_values.end() ) - { - // upd(was=X) + upd(was=Y) -> upd(was=X), type A - continue; - } - // del+upd -> N/A - assert( prev_state.removed_values.find(item.second.id) == prev_state.removed_values.end() ); - // nop+upd(was=Y) -> upd(was=Y), type B - prev_state.old_values.emplace( std::move(item) ); - } - - // *+new, but we assume the N/A cases don't happen, leaving type B nop+new -> new - for( auto id : state.new_ids ) - prev_state.new_ids.insert(id); - - // *+del - for( auto& obj : state.removed_values ) - { - if( prev_state.new_ids.find(obj.second.id) != prev_state.new_ids.end() ) - { - // new + del -> nop (type C) - prev_state.new_ids.erase(obj.second.id); - continue; - } - auto it = prev_state.old_values.find(obj.second.id); - if( it != prev_state.old_values.end() ) - { - // upd(was=X) + del(was=Y) -> del(was=X) - prev_state.removed_values.emplace( std::move(*it) ); - prev_state.old_values.erase(obj.second.id); - continue; - } - // del + del -> N/A - assert( prev_state.removed_values.find( obj.second.id ) == prev_state.removed_values.end() ); - // nop + del(was=Y) -> del(was=Y) - prev_state.removed_values.emplace( std::move(obj) ); //[obj.second->id] = std::move(obj.second); - } - - _stack.pop_back(); - } - - /** - * Revert table until its state is identical to the state it was at the start of - * @param revision. - * - * Calls undo() while the current revision is greater than or - * equal to revision. - */ - void undo_until( uint64_t revision ) { - wlog( "undo table until ${r}", ("r",revision)); - while( _stack.size() && _stack.back().revision >= revision ) { - undo(); - } - } - - /** - * Restores the state to how it was prior to the current session discarding all changes - * made between the last revision and the current revision. - */ - void undo() { - if( !enabled() ) return; - - const auto& head = _stack.back(); - - for( auto& item : head.old_values ) { - auto ok = _indices.modify( _indices.find( item.second.id ), [&]( value_type& v ) { - v = std::move( item.second ); - }); - if( !ok ) BOOST_THROW_EXCEPTION( std::logic_error( "Could not modify object, most likely a uniqueness constraint was violated" ) ); - } - - for( auto id : head.new_ids ) - { - _indices.erase( _indices.find( id ) ); - } - _next_id = head.old_next_id; - - for( auto& item : head.removed_values ) { - bool ok = _indices.emplace( std::move( item.second ) ).second; - if( !ok ) BOOST_THROW_EXCEPTION( std::logic_error( "Could not restore object, most likely a uniqueness constraint was violated" ) ); - } - - _stack.pop_back(); - } - - private: - void on_create( const value_type& v ) { - if( !enabled() ) return; - auto& head = _stack.back(); - - head.new_ids.insert( v.id ); - } - void on_remove( const value_type& v ) { - if( !enabled() ) return; - - auto& head = _stack.back(); - if( head.new_ids.count(v.id) ) { - head.new_ids.erase( v.id ); - return; - } - - auto itr = head.old_values.find( v.id ); - if( itr != head.old_values.end() ) { - head.removed_values.emplace( std::move( *itr ) ); - head.old_values.erase( v.id ); - return; - } - - if( head.removed_values.count( v.id ) ) - return; - - head.removed_values.emplace( std::pair< typename value_type::id_type, const value_type& >( v.id, v ) ); - } - void on_modify( const value_type& v ) { - if( !enabled() ) return; - - auto& head = _stack.back(); - - if( head.new_ids.find( v.id ) != head.new_ids.end() ) - return; - - auto itr = head.old_values.find( v.id ); - if( itr != head.old_values.end() ) - return; - - head.old_values.emplace( std::pair< typename value_type::id_type, const value_type& >( v.id, v ) ); - } - - bool enabled()const { return _stack.size(); } - shared_deque< undo_state_type > _stack; - index_type _indices; - id_type _next_id; - }; struct scope_state { template @@ -531,20 +91,6 @@ namespace eosio { namespace blockchain { }; - struct abstract_table { - virtual void destruct( table* t, allocator a ) = 0; - virtual void undo_until( table& t, uint64_t revision ) = 0; - }; - - template - struct table_interface_impl : public abstract_table { - virtual void destruct( table* t, allocator a ) override { - a.get_segment_manager()->destroy_ptr(static_cast(t)); - } - virtual void undo_until( table& t, uint64_t revision ) override { - static_cast(t).undo_until( revision ); - } - }; class database; diff --git a/libraries/blockchain/include/eosio/blockchain/db.hpp b/libraries/blockchain/include/eosio/blockchain/db.hpp new file mode 100644 index 00000000000..719398f6c14 --- /dev/null +++ b/libraries/blockchain/include/eosio/blockchain/db.hpp @@ -0,0 +1,344 @@ +#pragma once +#include + +namespace eosio { namespace blockchain { + + typedef uint64_t scope_name; + typedef uint64_t table_name; + typedef uint64_t block_num_type; + typedef uint16_t table_id_type; + + struct shard; + + template + class table_handle; + class block_handle; + class scope_handle; + class transaction_handle; + class cycle_handle; + class shard_handle; + class database; + + using std::set; + using std::deque; + using std::vector; + + + struct undo_transaction { + undo_transaction( allocator a, shard& s ) + :new_scopes(a),new_tables(a),modified_tables(a),_shard(&s){} + + shared_vector new_scopes; + shared_set new_tables; + shared_set modified_tables; + offset_ptr next; + offset_ptr _shard; + }; + + struct shard { + shard( allocator a, const set& w, const set& r ); + + shared_set _write_scope; + shared_set _read_scope; + shared_list _transaction_history; + }; + + typedef offset_ptr shard_optr; + + struct undo_cycle { + undo_cycle( allocator a ):_write_scope_to_shard(a),_read_scope_to_shard(a),_shards(a){} + + shared_map _write_scope_to_shard; + shared_map _read_scope_to_shard; + shared_list _shards; + }; + + struct undo_block{ + undo_block( allocator a ):_cycle_undo_history(a){} + + block_num_type _block_num = 0; + shared_deque _cycle_undo_history; + }; + + struct scope { + scope( allocator a ):_tables(a){} + ~scope() { assert( _tables.size() == 0 ); } + + const table* find_table( table_name t )const; + + shared_map _tables; + }; + + typedef offset_ptr scope_optr; + + + + template + class table_handle { + public: + table_handle( generic_table& t ) + :_table( t ) + { + } + private: + generic_table& _table; + }; + + + class scope_handle { + public: + scope_handle( transaction_handle& trx, scope& s ); + + template + auto create_table( table_name t ); + + template + optional> find_table( table_name t ); + + template + table_handle get_table( table_name t ); + private: + scope& _scope; + transaction_handle& _trx; + }; + + class transaction_handle + { + public: + transaction_handle( shard_handle& sh, undo_transaction& udt ); + transaction_handle( transaction_handle&& mv ); + ~transaction_handle() { + if( !_applied ) undo(); + } + void squash() { _applied = true; } + void undo(); + void commit() { _applied = true; } + + + /** + * This only works if this transaction_handle is the head transaction handle + * and we have 'scope' scope. We cannot have two transactions in different shards + * creating new scopes, but we need scopes to be undoable as part of transactions. + * + * The scope handle is only good for the scope of the transaction + */ + scope_handle create_scope( scope_name s ); + + private: + friend class scope_handle; + + void on_create_table( table& t ); + + template + void on_modify_table( generic_table& t ); + + bool _applied = false; + database& _db; + undo_transaction& _undo_trx; + }; + + + class shard_handle { + public: + shard_handle( cycle_handle& c, shard& s ); + + transaction_handle start_transaction(); + + private: + friend class transaction_handle; + + void undo_transaction(); + void squash_transaction(); + + database& _db; + shard& _shard; + }; + + + class cycle_handle { + public: + cycle_handle( const block_handle& blk, undo_cycle& cyc ); + + /** + * Assuming this block is the current head block handle, this will attempt to + * create a new shard. It will fail if there is a conflict with the read/write scopes and + * existing shards. + */ + shard_handle start_shard( const set& write, const set& read = set() ); + private: + friend class shard_handle; + database& _db; + undo_block& _undo_block; + undo_cycle& _undo_cycle; + }; + + class block_handle { + public: + + block_handle( block_handle&& mv ) + :_db(mv._db),_undo_block(mv._undo_block) { + _undo = mv._undo; + mv._undo = false; + } + + block_handle( database& db, undo_block& udb ) + :_db(db),_undo_block(udb){} + + ~block_handle() { + if( _undo ) undo(); + } + + cycle_handle start_cycle(); + + void commit() { _undo = false; } + void undo(); + + private: + friend class cycle_handle; + bool _undo = true; + database& _db; + undo_block& _undo_block; + }; + + + /** + * @class database + * @brief a revision tracking memory mapped database + * + * Maintains a set of scopes which contain a set of generic_table. Changes to the database + * are tracked so that they can be rolled back in the event of an error. Access to the database + * is safe to perform in parallel so long as new scopes are only created in shards with "scope" scope. + */ + class database { + public: + static const block_num_type max_block_num = -1; + + + database( const path& dir, uint64_t shared_file_size = 0 ); + ~database(); + + /** the last block whose changes we can revert */ + block_num_type last_reversible_block()const; + + /** starts a new block */ + block_handle start_block( block_num_type block = 1 ); + block_handle head_block(); + + /** + * Undoes the changes made since the most recent call to start_block() + */ + void undo(); + + /** Undoes all changes made in blocks greater than blocknum */ + void undo_until( block_num_type blocknum ); + + /** marks all blocks before and including blocknum irreversible */ + void commit( block_num_type blocknum ); + + allocator get_allocator() { return _segment->get_segment_manager(); } + + const scope* find_scope( scope_name n )const; + const scope& get_scope( scope_name n )const; + + private: + friend class transaction_handle; + friend class block_handle; + friend class scope_handle; + + void undo( undo_block& udb ); + void undo( undo_transaction& udb ); + + scope* create_scope( scope_name n ); + void delete_scope( scope_name n ); + + template + T* construct() { + auto memory = _segment->get_segment_manager(); + return _segment->construct( bip::anonymous_instance )( memory ); + } + + bfs::path _data_dir; + bip::file_lock _flock; + unique_ptr _segment; + unique_ptr _meta; + + /// Variables below this point are allocated in shared memory + typedef shared_map< scope_name, scope > scopes_type; + + scopes_type* _scopes = nullptr; /// allocated in shared memory + shared_deque* _block_undo_history = nullptr; + }; + + + /** + * Create a new table, informing the transaction undo state of that this table + * might be modified. If it is the first time this table was added to the transactions + * modified tables, then push an extra undo session to the generic table + */ + template + auto scope_handle::create_table( table_name t ) { + typedef typename get_index_type::type index_type; + + FC_ASSERT( is_registered_table(), "unknown table type" ); + + const table* existing_table = _scope.find_table(t); + FC_ASSERT( !existing_table, "table with name ${n} already exists", ("n",name(t)) ); + + + wlog( "creating table ${n}", ("n",name(t)) ); + auto* new_table = _trx._db.construct< generic_table >(); + new_table->_scope = &_scope; + new_table->_name = t; + + _trx.on_create_table( *new_table ); + new_table->push(); + + return table_handle( *new_table ); + } + + template + void transaction_handle::on_modify_table( generic_table& t ) { + auto result = _undo_trx.modified_tables.insert(&t); + if( result.second ) { /// insert successful + t.push(); /// start a new undo tracking session for this table + } + } + + + /* + FC_ASSERT( existing_table->_type == ObjectType::type_id, "mismatch of expected table types" ); + auto& gt = static_cast&>(*existing_table); + */ + /* + shard::squash() { + + } + + database::commit_block() { + _block_undo_history.front().commit(); + } + undo_block::commit() { + for( auto& c : _cycle_undo_history ) + c.commit(); + } + undo_cycle::commit() { + for( auto& s : _shards ) + s.commit(); + } + + shard::commit() { + for( auto& t : _transaction_history ) + t.commit(); + } + + undo_transaction::commit() { + for( auto& t : modified_tables ) + t.commit(); + } + + table::commit() { + undo.pop_front(); + } + */ + +} } /// eosio::blockchain diff --git a/libraries/blockchain/include/eosio/blockchain/generic_table.hpp b/libraries/blockchain/include/eosio/blockchain/generic_table.hpp new file mode 100644 index 00000000000..75fb606571b --- /dev/null +++ b/libraries/blockchain/include/eosio/blockchain/generic_table.hpp @@ -0,0 +1,494 @@ +/** + * @file generic_table.hpp + * @brief defines generic_table which wraps a multi_index container with undo revision history tracking + * + * + */ +#pragma once +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace eosio { namespace blockchain { + + namespace bip = boost::interprocess; + namespace bfs = boost::filesystem; + using bip::offset_ptr; + using fc::optional; + + template + using allocator = bip::allocator; + + template + using shared_deque = bip::deque >; + + template + using shared_vector = std::vector >; + + template + using shared_set = bip::set< T, std::less, allocator >; + + template + using shared_list = bip::list< T, allocator >; + + template + using shared_map = bip::map< Key, Value, std::less, allocator< pair > >; + + typedef bip::basic_string< char, std::char_traits< char >, allocator< char > > shared_string; + + + template + using shared_multi_index_container = boost::multi_index_container >; + + + /** + * Object ID type that includes the type of the object it references + */ + template + struct oid { + oid( int64_t i = 0 ):_id(i){} + + oid& operator++() { ++_id; return *this; } + + friend bool operator < ( const oid& a, const oid& b ) { return a._id < b._id; } + friend bool operator > ( const oid& a, const oid& b ) { return a._id > b._id; } + friend bool operator == ( const oid& a, const oid& b ) { return a._id == b._id; } + friend bool operator != ( const oid& a, const oid& b ) { return a._id != b._id; } + friend std::ostream& operator<<(std::ostream& s, const oid& id) { + s << boost::core::demangle(typeid(oid).name()) << '(' << id._id << ')'; return s; + } + + int64_t _id = 0; + }; + + + template + struct object { + typedef oid id_type; + enum type_id_enum { + type_id = TypeNumber + }; + }; + + /** this class is ment to be specified to enable lookup of index type by object type using + * the SET_INDEX_TYPE macro. + **/ + template + struct get_index_type {}; + + /** + * This macro must be used at global scope and OBJECT_TYPE and INDEX_TYPE must be fully qualified + */ + #define EOSIO_SET_INDEX_TYPE( OBJECT_TYPE, INDEX_TYPE ) \ + namespace eosio { namespace blockchain { template<> struct get_index_type { typedef INDEX_TYPE type; }; } } + + #define EOSIO_DEFAULT_CONSTRUCTOR( OBJECT_TYPE ) \ + template \ + OBJECT_TYPE( Constructor&& c, Allocator&& ) { c(*this); } + + + /** + * This backups up the state of generic_table so that it can be restored after any + * edits are made. + */ + template< typename ValueType > + struct table_undo_state + { + typedef typename ValueType::id_type id_type; + typedef allocator< std::pair > id_value_allocator_type; + typedef allocator< id_type > id_allocator_type; + + template + table_undo_state( allocator al ) + :old_values( id_value_allocator_type( al.get_segment_manager() ) ), + removed_values( id_value_allocator_type( al.get_segment_manager() ) ), + new_ids( id_allocator_type( al.get_segment_manager() ) ){} + + typedef shared_map id_value_type_map; + typedef shared_set< id_type > id_type_set; + + id_value_type_map old_values; + id_value_type_map removed_values; + id_type_set new_ids; + id_type old_next_id = 0; + + /** + * The revision the table will be in after restoring old_values, removed_values, etc. + */ + int64_t revision = 0; + }; + + + struct scope; + /** + * Common base class for all tables, it includes the type of the data stored so + * that the proper virtual interface can be looked up for dealing with the true + * table. + */ + struct table { + uint64_t _type = 0; + name_type _name = name_type(""); + offset_ptr _scope; + }; + + /** + * A generic_table wraps a boost multi_index container and provides version control along + * with a primary key type. + */ + template + struct generic_table : public table { + typedef bip::managed_mapped_file::segment_manager segment_manager_type; + typedef MultiIndexType index_type; + typedef typename index_type::value_type value_type; + typedef bip::allocator< generic_table, segment_manager_type > allocator_type; + typedef table_undo_state< value_type > undo_state_type; + typedef typename value_type::id_type id_type; + static const uint64_t type_id = value_type::type_id; + + + template + generic_table( Allocator&& a ) + :_stack(a),_indices(a) { + table::_type = value_type::type_id; + ilog( "generic_table constructor" ); + } + ~generic_table() { + ilog( "~generic_table destructor '${n}'", ("n",_name) ); + } + + /** + * Construct a new element in the multi_index_container. + * Set the ID to the next available ID, then increment _next_id and fire off on_create(). + */ + template + const value_type& emplace( Constructor&& c ) { + auto new_id = _next_id; + + auto constructor = [&]( value_type& v ) { + v.id = new_id; + c( v ); + }; + + auto insert_result = _indices.emplace( constructor, _indices.get_allocator() ); + + if( !insert_result.second ) { + BOOST_THROW_EXCEPTION( std::logic_error("could not insert object, most likely a uniqueness constraint was violated") ); + } + + ++_next_id; + on_create( *insert_result.first ); + return *insert_result.first; + } + + void remove( const value_type& obj ) { + on_remove( obj ); + _indices.erase( _indices.iterator_to( obj ) ); + } + + template + void modify( const value_type& obj, Modifier&& m ) { + on_modify( obj ); + auto ok = _indices.modify( _indices.iterator_to( obj ), m ); + if( !ok ) BOOST_THROW_EXCEPTION( std::logic_error( "Could not modify object, most likely a uniqueness constraint was violated" ) ); + } + + void push() { + _stack.emplace_back( _indices.get_allocator() ); + auto& newrev = _stack.back(); + newrev.old_next_id = _next_id; + } + + /** + * Discards all undo history prior to revision + */ + void commit() { + _stack.pop_front(); + } + + /** + * This method works similar to git squash, it merges the change set from the two most + * recent revision numbers into one revision number (reducing the head revision number) + * + * This method does not change the state of the index, only the state of the undo buffer. + */ + void squash() { + if( !enabled() ) return; + if( _stack.size() == 1 ) { + _stack.pop_front(); + return; + } + + auto& state = _stack.back(); + auto& prev_state = _stack[_stack.size()-2]; + + // An object's relationship to a state can be: + // in new_ids : new + // in old_values (was=X) : upd(was=X) + // in removed (was=X) : del(was=X) + // not in any of above : nop + // + // When merging A=prev_state and B=state we have a 4x4 matrix of all possibilities: + // + // |--------------------- B ----------------------| + // + // +------------+------------+------------+------------+ + // | new | upd(was=Y) | del(was=Y) | nop | + // +------------+------------+------------+------------+------------+ + // / | new | N/A | new A| nop C| new A| + // | +------------+------------+------------+------------+------------+ + // | | upd(was=X) | N/A | upd(was=X)A| del(was=X)C| upd(was=X)A| + // A +------------+------------+------------+------------+------------+ + // | | del(was=X) | N/A | N/A | N/A | del(was=X)A| + // | +------------+------------+------------+------------+------------+ + // \ | nop | new B| upd(was=Y)B| del(was=Y)B| nop AB| + // +------------+------------+------------+------------+------------+ + // + // Each entry was composed by labelling what should occur in the given case. + // + // Type A means the composition of states contains the same entry as the first of the two merged states for that object. + // Type B means the composition of states contains the same entry as the second of the two merged states for that object. + // Type C means the composition of states contains an entry different from either of the merged states for that object. + // Type N/A means the composition of states violates causal timing. + // Type AB means both type A and type B simultaneously. + // + // The merge() operation is defined as modifying prev_state in-place to be the state object which represents the composition of + // state A and B. + // + // Type A (and AB) can be implemented as a no-op; prev_state already contains the correct value for the merged state. + // Type B (and AB) can be implemented by copying from state to prev_state. + // Type C needs special case-by-case logic. + // Type N/A can be ignored or assert(false) as it can only occur if prev_state and state have illegal values + // (a serious logic error which should never happen). + // + + // We can only be outside type A/AB (the nop path) if B is not nop, so it suffices to iterate through B's three containers. + + for( const auto& item : state.old_values ) + { + if( prev_state.new_ids.find( item.second.id ) != prev_state.new_ids.end() ) + { + // new+upd -> new, type A + continue; + } + if( prev_state.old_values.find( item.second.id ) != prev_state.old_values.end() ) + { + // upd(was=X) + upd(was=Y) -> upd(was=X), type A + continue; + } + // del+upd -> N/A + assert( prev_state.removed_values.find(item.second.id) == prev_state.removed_values.end() ); + // nop+upd(was=Y) -> upd(was=Y), type B + prev_state.old_values.emplace( std::move(item) ); + } + + // *+new, but we assume the N/A cases don't happen, leaving type B nop+new -> new + for( auto id : state.new_ids ) + prev_state.new_ids.insert(id); + + // *+del + for( auto& obj : state.removed_values ) + { + if( prev_state.new_ids.find(obj.second.id) != prev_state.new_ids.end() ) + { + // new + del -> nop (type C) + prev_state.new_ids.erase(obj.second.id); + continue; + } + auto it = prev_state.old_values.find(obj.second.id); + if( it != prev_state.old_values.end() ) + { + // upd(was=X) + del(was=Y) -> del(was=X) + prev_state.removed_values.emplace( std::move(*it) ); + prev_state.old_values.erase(obj.second.id); + continue; + } + // del + del -> N/A + assert( prev_state.removed_values.find( obj.second.id ) == prev_state.removed_values.end() ); + // nop + del(was=Y) -> del(was=Y) + prev_state.removed_values.emplace( std::move(obj) ); //[obj.second->id] = std::move(obj.second); + } + + _stack.pop_back(); + } + + /** + * Revert table until its state is identical to the state it was at the start of + * @param revision. + * + * Calls undo() while the current revision is greater than or + * equal to revision. + */ + void undo_until( uint64_t revision ) { + wlog( "undo table until ${r}", ("r",revision)); + while( _stack.size() && _stack.back().revision >= revision ) { + undo(); + } + } + + /** + * Restores the state to how it was prior to the current session discarding all changes + * made between the last revision and the current revision. + */ + void undo() { + if( !enabled() ) return; + + const auto& head = _stack.back(); + + for( auto& item : head.old_values ) { + auto ok = _indices.modify( _indices.find( item.second.id ), [&]( value_type& v ) { + v = std::move( item.second ); + }); + if( !ok ) BOOST_THROW_EXCEPTION( std::logic_error( "Could not modify object, most likely a uniqueness constraint was violated" ) ); + } + + for( auto id : head.new_ids ) + { + _indices.erase( _indices.find( id ) ); + } + _next_id = head.old_next_id; + + for( auto& item : head.removed_values ) { + bool ok = _indices.emplace( std::move( item.second ) ).second; + if( !ok ) BOOST_THROW_EXCEPTION( std::logic_error( "Could not restore object, most likely a uniqueness constraint was violated" ) ); + } + + _stack.pop_back(); + } + + private: + void on_create( const value_type& v ) { + if( !enabled() ) return; + auto& head = _stack.back(); + + head.new_ids.insert( v.id ); + } + void on_remove( const value_type& v ) { + if( !enabled() ) return; + + auto& head = _stack.back(); + if( head.new_ids.count(v.id) ) { + head.new_ids.erase( v.id ); + return; + } + + auto itr = head.old_values.find( v.id ); + if( itr != head.old_values.end() ) { + head.removed_values.emplace( std::move( *itr ) ); + head.old_values.erase( v.id ); + return; + } + + if( head.removed_values.count( v.id ) ) + return; + + head.removed_values.emplace( std::pair< typename value_type::id_type, const value_type& >( v.id, v ) ); + } + void on_modify( const value_type& v ) { + if( !enabled() ) return; + + auto& head = _stack.back(); + + if( head.new_ids.find( v.id ) != head.new_ids.end() ) + return; + + auto itr = head.old_values.find( v.id ); + if( itr != head.old_values.end() ) + return; + + head.old_values.emplace( std::pair< typename value_type::id_type, const value_type& >( v.id, v ) ); + } + + bool enabled()const { return _stack.size(); } + shared_deque< undo_state_type > _stack; + index_type _indices; + id_type _next_id; + }; + + + + /** + * Because generic_table<> exists in shared memory, it cannot implement a virtual interface, therefore, + * we define the virtual interface separately so that it can be registered with a factory that can + * lookup the abstract_table* based upon the table::_type property. + */ + struct abstract_table { + virtual void destruct( table& t, allocator a ) = 0; + virtual void undo( table& t ) = 0; + virtual void squash( table& t ) = 0; + virtual void commit( table& t ) = 0; + }; + + template + struct table_interface_impl : public abstract_table { + virtual void destruct( table& t, allocator a ) override { + a.get_segment_manager()->destroy_ptr(&static_cast(t)); + } + virtual void undo( table& t ) override { + static_cast(t).undo(); + } + virtual void squash( table& t ) override { + static_cast(t).squash(); + } + virtual void commit( table& t ) override { + static_cast(t).commit(); + } + }; + + using table_optr = offset_ptr; + + /// defines global static map + map& get_abstract_table_map(); + + + template + bool is_registered_table() { + const auto& tables = get_abstract_table_map(); + return tables.find(ObjectType::type_id) != tables.end(); + } + + /// register virtual API (construct, destruct, insert, modify, etc) + template + void register_table() { + typedef typename get_index_type::type index_type; + typedef generic_table generic_object_table_type; + FC_ASSERT( !is_registered_table(), "Table type already registered" ); + get_abstract_table_map()[ObjectType::type_id] = new table_interface_impl(); + } + abstract_table& get_abstract_table( uint16_t table_type ); + abstract_table& get_abstract_table( const table& t); + +} } /// eosio::blockchain diff --git a/libraries/blockchain/include/eosio/blockchain/name.hpp b/libraries/blockchain/include/eosio/blockchain/name.hpp new file mode 100644 index 00000000000..78d017badde --- /dev/null +++ b/libraries/blockchain/include/eosio/blockchain/name.hpp @@ -0,0 +1,97 @@ +#pragma once +#include + +namespace eosio { namespace blockchain { + using std::string; + + static constexpr char char_to_symbol( char c ) { + if( c >= 'a' && c <= 'z' ) + return (c - 'a') + 6; + if( c >= '1' && c <= '5' ) + return (c - '1') + 1; + return 0; + } + + static constexpr uint64_t string_to_name( const char* str ) { + + uint32_t len = 0; + while( str[len] ) ++len; + + uint64_t value = 0; + + for( uint32_t i = 0; i <= 12; ++i ) { + uint64_t c = 0; + if( i < len && i <= 12 ) c = char_to_symbol( str[i] ); + + if( i < 12 ) { + c &= 0x1f; + c <<= 64-5*(i+1); + } + else { + c &= 0x0f; + } + + value |= c; + } + + return value; + } + +#define N(X) eosio::blockchain::string_to_name(#X) + + struct name { + uint64_t value = 0; + bool empty()const { return 0 == value; } + bool good()const { return !empty(); } + + name( const char* str ) { set(str); } + name( const string& str ) { set( str.c_str() ); } + + void set( const char* str ); + + name( uint64_t v = 0 ):value(v){} + + explicit operator string()const; + + string to_string() const { return string(*this); } + + name& operator=( uint64_t v ) { + value = v; + return *this; + } + + name& operator=( const string& n ) { + value = name(n).value; + return *this; + } + name& operator=( const char* n ) { + value = name(n).value; + return *this; + } + + template + friend Stream& operator << ( Stream& out, const name& n ) { + return out << string(n); + } + + friend bool operator < ( const name& a, const name& b ) { return a.value < b.value; } + friend bool operator <= ( const name& a, const name& b ) { return a.value <= b.value; } + friend bool operator > ( const name& a, const name& b ) { return a.value > b.value; } + friend bool operator >=( const name& a, const name& b ) { return a.value >= b.value; } + friend bool operator == ( const name& a, const name& b ) { return a.value == b.value; } + friend bool operator != ( const name& a, const name& b ) { return a.value != b.value; } + + operator bool()const { return value; } + operator uint64_t()const { return value; } + }; + + +} } // eosio::blockchain + +namespace fc { + class variant; + void to_variant(const eosio::blockchain::name& c, fc::variant& v); + void from_variant(const fc::variant& v, eosio::blockchain::name& check); +} // fc + + diff --git a/libraries/blockchain/include/eosio/blockchain/public_key.hpp b/libraries/blockchain/include/eosio/blockchain/public_key.hpp new file mode 100644 index 00000000000..175915c5dc2 --- /dev/null +++ b/libraries/blockchain/include/eosio/blockchain/public_key.hpp @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include + +namespace eosio { namespace blockchain { + struct public_key + { + struct binary_key + { + binary_key() {} + uint32_t check = 0; + fc::ecc::public_key_data data; + }; + + fc::ecc::public_key_data key_data; + + public_key(); + public_key(const fc::ecc::public_key_data& data); + public_key(const fc::ecc::public_key& pubkey); + + explicit public_key(const std::string& base58str); + operator fc::ecc::public_key_data() const; + operator fc::ecc::public_key() const; + explicit operator std::string() const; + + friend bool operator == ( const public_key& p1, const fc::ecc::public_key& p2); + friend bool operator == ( const public_key& p1, const public_key& p2); + friend bool operator != ( const public_key& p1, const public_key& p2); + friend bool operator < ( const public_key& p1, const public_key& p2); + friend std::ostream& operator<< (std::ostream& s, const public_key& k); + + bool is_valid_v1(const std::string& base58str); + }; // public_key + +} } // namespace eosio::blockchain + +namespace fc { + void to_variant(const eosio::blockchain::public_key& var, fc::variant& vo); + void from_variant(const fc::variant& var, eosio::blockchain::public_key& vo); +} // namespace fc + +FC_REFLECT(eosio::blockchain::public_key, (key_data)) +FC_REFLECT(eosio::blockchain::public_key::binary_key, (data)(check)) diff --git a/libraries/blockchain/include/eosio/blockchain/types.hpp b/libraries/blockchain/include/eosio/blockchain/types.hpp index 630148f35dd..031b78dba2a 100644 --- a/libraries/blockchain/include/eosio/blockchain/types.hpp +++ b/libraries/blockchain/include/eosio/blockchain/types.hpp @@ -1,5 +1,6 @@ #pragma once -#include +#include +#include #include #include @@ -17,7 +18,6 @@ namespace eosio { namespace blockchain { - using std::vector; using std::map; using std::pair; @@ -36,7 +36,6 @@ namespace eosio { namespace blockchain { using fc::static_variant; using fc::path; - typedef eos::types::Name name_type; typedef fc::sha256 block_id_type; typedef fc::sha256 chain_id_type; @@ -44,6 +43,7 @@ namespace eosio { namespace blockchain { typedef fc::sha256 transaction_id_type; typedef fc::sha256 merkle_id_type; + typedef name name_type; typedef name_type account_name; typedef name_type function_name; typedef name_type permission_name; @@ -51,10 +51,9 @@ namespace eosio { namespace blockchain { typedef fc::ecc::compact_signature signature_type; - typedef eos::types::PublicKey public_key_type; + typedef eosio::blockchain::public_key public_key_type; typedef fc::ecc::private_key private_key_type; typedef map > producer_changes_type; - } } /// eosio::blockchain diff --git a/libraries/blockchain/main.cpp b/libraries/blockchain/main.cpp index f299486abca..533e1d2dfd6 100644 --- a/libraries/blockchain/main.cpp +++ b/libraries/blockchain/main.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include using namespace eosio::blockchain; @@ -41,10 +41,45 @@ int main( int argc, char** argv ) { boost::asio::io_service ios; controller control( ios ); + register_table< key_value_object >(); + wlog( "loading database" ); database db( "test.db", 1024*1024 ); - db.register_table< key_value_object >(); - idump((db.revision())); + + idump((db.last_reversible_block())); + + if( db.last_reversible_block() == database::max_block_num ) { + auto blk = db.start_block(1); + auto cyc = blk.start_cycle(); + auto shr = cyc.start_shard( {N(scope)} ); + auto trx = shr.start_transaction(); + auto sco = trx.create_scope( N(dan) ); + auto tbl = sco.create_table( N(test) ); + } + + + } catch ( const fc::exception& e ) { + edump((e.to_detail_string())); + } catch ( const std::exception& e ) { + elog(e.what()); + } + + return 0; +} + +#if 0 + /* + db.undo_all_blocks(); + auto b = db.start_block(); + auto s = b.start_shard( {1} ) + auto t = s.start_transaction(); + auto tbl = t.create_table<...>() + t.squash(); + t.undo(); + s.start_transaction() + + + wlog( "undoing all pending state" ); @@ -63,6 +98,7 @@ int main( int argc, char** argv ) { } auto shar = ses.start_shard( {1} ); + shar.start_session auto okv_table = shar.find_table(1,1000); if( !okv_table ) { @@ -233,17 +269,4 @@ int main( int argc, char** argv ) { // trx_session.squash(); // trx_session.undo() // thread_session.push(); /// prevent it from being undone - - - - - - - } catch ( const fc::exception& e ) { - edump((e.to_detail_string())); - } catch ( const std::exception& e ) { - elog(e.what()); - } - - return 0; -} +#endif