diff --git a/.gitignore b/.gitignore index 894e3e330e95432d75e7fb98f39987fe56139d19..2aa86b4ffc374e6c253d145729894987302eeb84 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ tests/chain_bench tests/chain_test tests/intense_test tests/performance_test +tests/tests/config.hpp doxygen diff --git a/contracts/CMakeLists.txt b/contracts/CMakeLists.txt index c4f3e37824cee59e54d754a16c12bd14ec152e91..614a2df2a2b50abb9d5d5b9da4d3515476b66de7 100644 --- a/contracts/CMakeLists.txt +++ b/contracts/CMakeLists.txt @@ -22,6 +22,7 @@ add_subdirectory(proxy) add_subdirectory(test_api) add_subdirectory(test_api_mem) add_subdirectory(test_api_db) +add_subdirectory(test_api_multi_index) add_subdirectory(simpledb) #add_subdirectory(storage) #add_subdirectory(social) diff --git a/contracts/eosiolib/datastream.hpp b/contracts/eosiolib/datastream.hpp index 67f708c0afa17ae42095519cd90acd8f9a7910e4..182b3225cbac11333a4fe15bedeae778e95d58bd 100644 --- a/contracts/eosiolib/datastream.hpp +++ b/contracts/eosiolib/datastream.hpp @@ -19,14 +19,14 @@ class datastream { public: datastream( T start, size_t s ) :_start(start),_pos(start),_end(start+s){} - + /** * Skips a specified number of bytes from this stream * @brief Skips a specific number of bytes from this stream * @param s The number of bytes to skip */ inline void skip( size_t s ){ _pos += s; } - + /** * Reads a specified number of bytes from the stream into a buffer * @brief Reads a specified number of bytes from this stream into a buffer @@ -52,30 +52,30 @@ class datastream { _pos += s; return true; } - + /** * Writes a byte into the stream * @brief Writes a byte into the stream * @param c byte to write */ - inline bool put(char c) { + inline bool put(char c) { eosio_assert( _pos < _end, "put" ); - *_pos = c; - ++_pos; + *_pos = c; + ++_pos; return true; } - + /** * Reads a byte from the stream * @brief Reads a byte from the stream * @param c reference to destination byte */ inline bool get( unsigned char& c ) { return get( *(char*)&c ); } - inline bool get( char& c ) + inline bool get( char& c ) { eosio_assert( _pos < _end, "get" ); c = *_pos; - ++_pos; + ++_pos; return true; } @@ -86,7 +86,7 @@ class datastream { */ T pos()const { return _pos; } inline bool valid()const { return _pos <= _end && _pos >= _start; } - + /** * Sets the position within the current stream * @brief Sets the position within the current stream @@ -100,7 +100,7 @@ class datastream { * @return p the position within the current stream */ inline size_t tellp()const { return size_t(_pos - _start); } - + /** * Returns the number of remaining bytes that can be read/skipped * @brief Returns the number of remaining bytes that can be read/skipped @@ -132,25 +132,25 @@ class datastream { }; /** - * Serialize a uint256 into a stream - * @brief Serialize a uint256 + * Serialize a key256 into a stream + * @brief Serialize a key256 * @param ds stream to write * @param d value to serialize */ template -inline datastream& operator<<(datastream& ds, const uint256 d) { - ds.write( (const char*)&d, sizeof(d) ); +inline datastream& operator<<(datastream& ds, const key256 d) { + ds.write( (const char*)d.data(), d.size() ); return ds; } /** - * Deserialize a uint256 from a stream - * @brief Deserialize a uint256 + * Deserialize a key256 from a stream + * @brief Deserialize a key256 * @param ds stream to read * @param d destination for deserialized value */ template -inline datastream& operator>>(datastream& ds, uint256& d) { - ds.read((char*)&d, sizeof(d) ); +inline datastream& operator>>(datastream& ds, key256& d) { + ds.read((char*)d.data(), d.size() ); return ds; } @@ -430,7 +430,7 @@ T unpack( const char* buffer, size_t len ) { template size_t pack_size( const T& value ) { - datastream ps; + datastream ps; ps << value; return ps.tellp(); } @@ -446,4 +446,3 @@ bytes pack( const T& value ) { } } - diff --git a/contracts/eosiolib/db.h b/contracts/eosiolib/db.h index 4d7957c308b9a952e03e6c2ee8f3280507c9b073..798202e9a0b1749142e7fc51b21e5972e4fc17f0 100644 --- a/contracts/eosiolib/db.h +++ b/contracts/eosiolib/db.h @@ -15,23 +15,23 @@ extern "C" { * EOS.IO organizes data according to the following broad structure: * * - **scope** - an account where the data is stored - * - **code** - the account name which has write permission + * - **code** - the account name which has write permission * - **table** - a name for the table that is being stored * - **record** - a row in the table * * Every transaction specifies the set of valid scopes that may be read and/or written * to. The code that is running determines what can be written to; therefore, write operations - * do not allow you to specify/configure the code. + * do not allow you to specify/configure the code. * - * @note Attempts to read and/or write outside the valid scope and/or code sections will + * @note Attempts to read and/or write outside the valid scope and/or code sections will * cause your transaction to fail. * * * @section tabletypes table Types * There are several supported table types identified by the number and - * size of the index. + * size of the index. * - * 1. @ref dbi64 + * 1. @ref dbi64 * 2. @ref dbi128i128 * * The database APIs assume that the first bytes of each record represent @@ -264,7 +264,7 @@ int32_t remove_i64( account_name scope, table_name table, void* data ); * */ int32_t store_str( account_name scope, table_name table, account_name bta, char* key, uint32_t keylen, char* value, uint32_t valuelen ); - + /** * @param scope - the account scope that will be read, must exist in the transaction scopes list * @param table - the ID/name of the table within the current scope/code context to modify @@ -286,7 +286,7 @@ int32_t store_str( account_name scope, table_name table, account_name bta, char* * */ int32_t update_str( account_name scope, table_name table, account_name bta, char* key, uint32_t keylen, char* value, uint32_t valuelen ); - + /** * @param scope - the account scope that will be read, must exist in the transaction scopes list * @param code - identifies the code that controls write-access to the data @@ -294,7 +294,7 @@ int32_t update_str( account_name scope, table_name table, account_name bta, char * @param key - location of the record key * @param keylen - length of the record key * @param value - location to copy the record value - * @param valuelen - maximum length of the record value to read + * @param valuelen - maximum length of the record value to read * * @return the number of bytes read or -1 if key was not found */ @@ -305,7 +305,7 @@ int32_t update_str( account_name scope, table_name table, account_name bta, char * @param code - identifies the code that controls write-access to the data * @param table - the ID/name of the table within the scope/code context to query * @param value - location to copy the front record value - * @param valuelen - maximum length of the record value to read + * @param valuelen - maximum length of the record value to read * @return the number of bytes read or -1 if key was not found */ int32_t front_str( account_name code, account_name scope, table_name table, char* value, uint32_t valuelen ); @@ -315,7 +315,7 @@ int32_t update_str( account_name scope, table_name table, account_name bta, char * @param code - identifies the code that controls write-access to the data * @param table - the ID/name of the table within the scope/code context to query * @param value - location to copy the back record value - * @param valuelen - maximum length of the record value to read + * @param valuelen - maximum length of the record value to read * @return the number of bytes read or -1 if key was not found */ int32_t back_str( account_name code, account_name scope, table_name table, char* value, uint32_t valuelen ); @@ -327,7 +327,7 @@ int32_t update_str( account_name scope, table_name table, account_name bta, char * @param key - location of the record key * @param keylen - length of the record key * @param value - location to copy the next record value - * @param valuelen - maximum length of the record value to read + * @param valuelen - maximum length of the record value to read * @return the number of bytes read or -1 if key was not found */ int32_t next_str( account_name code, account_name scope, table_name table, char* key, uint32_t keylen, char* value, uint32_t valuelen ); @@ -339,7 +339,7 @@ int32_t update_str( account_name scope, table_name table, account_name bta, char * @param key - location of the record key * @param keylen - length of the record key * @param value - location to copy the previous record value - * @param valuelen - maximum length of the record value to read + * @param valuelen - maximum length of the record value to read * @return the number of bytes read or -1 if key was not found */ int32_t previous_str( account_name code, account_name scope, table_name table, char* key, uint32_t keylen, char* value, uint32_t valuelen ); @@ -367,7 +367,7 @@ int32_t update_str( account_name scope, table_name table, account_name bta, char * @return the number of bytes read or -1 if key was not found */ int32_t upper_bound_str( account_name code, account_name scope, table_name table, char* key, uint32_t keylen, char* value, uint32_t valuelen ); - + /** * @param key - location of the record key * @param keylen - length of the record key @@ -375,22 +375,22 @@ int32_t update_str( account_name scope, table_name table, account_name bta, char * @return 1 if a record was removed, and 0 if no record with key was found */ int32_t remove_str( account_name scope, table_name table, char* key, uint32_t keylen ); - + ///@} dbstr /** * @defgroup dbi128i128 Dual 128 bit Index table * @brief Interface to a database table with 128 bit primary and secondary keys and arbitary binary data value. - * @ingroup databaseC + * @ingroup databaseC * * @param scope - the account where table data will be found * @param code - the code which owns the table * @param table - the name of the table where record is stored - * @param data - a pointer to memory that is at least 32 bytes long + * @param data - a pointer to memory that is at least 32 bytes long * @param len - the length of data, must be greater than or equal to 32 bytes * - * @return the total number of bytes read or -1 for "not found" or "end" where bytes - * read includes 32 bytes of the key + * @return the total number of bytes read or -1 for "not found" or "end" where bytes + * read includes 32 bytes of the key * * These methods assume a database table with records of the form: * @@ -671,16 +671,16 @@ int32_t update_i128i128( account_name scope, table_name table, account_name bta, /** * @defgroup dbi64i64i64 Triple 64 bit Index table * @brief Interface to a database table with 64 bit primary, secondary and tertiary keys and arbitrary binary data value. - * @ingroup databaseC + * @ingroup databaseC * * @param scope - the account where table data will be found * @param code - the code which owns the table * @param table - the name of the table where record is stored - * @param data - a pointer to memory that is at least 32 bytes long + * @param data - a pointer to memory that is at least 32 bytes long * @param len - the length of data, must be greater than or equal to 32 bytes * - * @return the total number of bytes read or -1 for "not found" or "end" where bytes - * read includes 24 bytes of the key + * @return the total number of bytes read or -1 for "not found" or "end" where bytes + * read includes 24 bytes of the key * * These methods assume a database table with records of the form: * @@ -1023,7 +1023,7 @@ int32_t store_i64i64i64( account_name scope, table_name table, account_name bta, * @return 1 if the record was updated, 0 if no record with key was found */ int32_t update_i64i64i64( account_name scope, table_name table, account_name bta, const void* data, uint32_t len ); -///@} dbi64i64i64 +///@} dbi64i64i64 int32_t db_store_i64(account_name scope, table_name table, account_name payer, uint64_t id, const void* data, uint32_t len); void db_update_i64(int32_t iterator, account_name payer, const void* data, uint32_t len); @@ -1032,8 +1032,9 @@ int32_t db_get_i64(int32_t iterator, const void* data, uint32_t len); int32_t db_next_i64(int32_t iterator, uint64_t* primary); int32_t db_previous_i64(int32_t iterator, uint64_t* primary); int32_t db_find_i64(account_name code, account_name scope, table_name table, uint64_t id); -int32_t db_lowerbound_i64( account_name code, account_name scope, table_name table, uint64_t id); -int32_t db_upperbound_i64( account_name code, account_name scope, table_name table, uint64_t id); +int32_t db_lowerbound_i64(account_name code, account_name scope, table_name table, uint64_t id); +int32_t db_upperbound_i64(account_name code, account_name scope, table_name table, uint64_t id); +int32_t db_end_i64(account_name code, account_name scope, table_name table); int32_t db_idx64_store(account_name scope, table_name table, account_name payer, uint64_t id, const uint64_t* secondary); void db_idx64_update(int32_t iterator, account_name payer, const uint64_t* secondary); @@ -1044,6 +1045,7 @@ int32_t db_idx64_find_primary(account_name code, account_name scope, table_name int32_t db_idx64_find_secondary(account_name code, account_name scope, table_name table, const uint64_t* secondary, uint64_t* primary); int32_t db_idx64_lowerbound(account_name code, account_name scope, table_name table, uint64_t* secondary, uint64_t* primary); int32_t db_idx64_upperbound(account_name code, account_name scope, table_name table, uint64_t* secondary, uint64_t* primary); +int32_t db_idx64_end(account_name code, account_name scope, table_name table); int32_t db_idx128_store(account_name scope, table_name table, account_name payer, uint64_t id, const uint128_t* secondary); void db_idx128_update(int32_t iterator, account_name payer, const uint128_t* secondary); @@ -1054,5 +1056,17 @@ int32_t db_idx128_find_primary(account_name code, account_name scope, table_name int32_t db_idx128_find_secondary(account_name code, account_name scope, table_name table, const uint128_t* secondary, uint64_t* primary); int32_t db_idx128_lowerbound(account_name code, account_name scope, table_name table, uint128_t* secondary, uint64_t* primary); int32_t db_idx128_upperbound(account_name code, account_name scope, table_name table, uint128_t* secondary, uint64_t* primary); +int32_t db_idx128_end(account_name code, account_name scope, table_name table); + +int32_t db_idx256_store(account_name scope, table_name table, account_name payer, uint64_t id, const void* data, uint32_t data_len ); +void db_idx256_update(int32_t iterator, account_name payer, const void* data, uint32_t data_len); +void db_idx256_remove(int32_t iterator); +int32_t db_idx256_next(int32_t iterator, uint64_t* primary); +int32_t db_idx256_previous(int32_t iterator, uint64_t* primary); +int32_t db_idx256_find_primary(account_name code, account_name scope, table_name table, void* data, uint32_t data_len, uint64_t primary); +int32_t db_idx256_find_secondary(account_name code, account_name scope, table_name table, const void* data, uint32_t data_len, uint64_t* primary); +int32_t db_idx256_lowerbound(account_name code, account_name scope, table_name table, void* data, uint32_t data_len, uint64_t* primary); +int32_t db_idx256_upperbound(account_name code, account_name scope, table_name table, void* data, uint32_t data_len, uint64_t* primary); +int32_t db_idx256_end(account_name code, account_name scope, table_name table); } diff --git a/contracts/eosiolib/fixed_key.hpp b/contracts/eosiolib/fixed_key.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9fb0774a2dac5f5937f1afb7fd2db7f7a5771d8c --- /dev/null +++ b/contracts/eosiolib/fixed_key.hpp @@ -0,0 +1,217 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once + +#include +#include +#include + +#include + +namespace eosio { + + template + class fixed_key; + + template + bool operator==(const fixed_key &c1, const fixed_key &c2); + + template + bool operator!=(const fixed_key &c1, const fixed_key &c2); + + template + bool operator>(const fixed_key &c1, const fixed_key &c2); + + template + bool operator<(const fixed_key &c1, const fixed_key &c2); + + /** + * @defgroup fixed_key fixed size key sorted lexicographically + * @ingroup types + * @{ + */ + template + class fixed_key { + private: + + template struct bool_pack; + template + using all_true = std::is_same< bool_pack, bool_pack >; + + public: + + typedef uint128_t word_t; + + static constexpr size_t num_words() { return (Size + sizeof(word_t) - 1) / sizeof(word_t); } + static constexpr size_t padded_bytes() { return num_words() * sizeof(word_t) - Size; } + + /** + * @brief Default constructor to fixed_key object + * + * @details Default constructor to fixed_key object which initializes all bytes to zero + */ + fixed_key() : _data() {} + + /** + * @brief Constructor to fixed_key object from array of num_words() words + * + * @details Constructor to fixed_key object from array of num_words() words + * @param arr data + */ + fixed_key(const word_t (&arr)[num_words()]) + { + std::copy(arr, arr + num_words(), _data.begin()); + } + + /** + * @brief Constructor to fixed_key object from std::array of num_words() words + * + * @details Constructor to fixed_key object from std::array of num_words() words + * @param arr data + */ + fixed_key(const std::array& arr) + { + std::copy(arr.begin(), arr.end(), _data.begin()); + } + + template + static + fixed_key + make_from_word_sequence(typename std::enable_if::value && + !std::is_same::value && + sizeof(FirstWord) <= sizeof(word_t) && + all_true<(std::is_same::value)...>::value, + FirstWord>::type first_word, + Rest... rest) + { + static_assert( sizeof(word_t) == (sizeof(word_t)/sizeof(FirstWord)) * sizeof(FirstWord), + "size of the backing word size is not divisble by the size of the words supplied as arguments" ); + static_assert( sizeof(FirstWord) * (1 + sizeof...(Rest)) <= Size, "too many words supplied to fixed_key constructor" ); + + fixed_key key; + + auto itr = key._data.begin(); + word_t temp_word = 0; + const size_t sub_word_shift = 8 * sizeof(FirstWord); + const size_t num_sub_words = sizeof(word_t) / sizeof(FirstWord); + auto sub_words_left = num_sub_words; + for( auto&& w : { first_word, rest... } ) { + if( sub_words_left > 1 ) { + temp_word |= static_cast(w); + temp_word <<= sub_word_shift; + --sub_words_left; + continue; + } + + eosio_assert( sub_words_left == 1, "unexpected error in fixed_key constructor" ); + temp_word |= static_cast(w); + sub_words_left = num_sub_words; + + *itr = temp_word; + temp_word = 0; + ++itr; + } + if( sub_words_left != num_sub_words ) { + if( sub_words_left > 1 ) + temp_word <<= 8 * (sub_words_left-1); + *itr = temp_word; + } + + return key; + } + + const auto& get_array()const { return _data; } + + auto data() { return _data.data(); } + auto data()const { return _data.data(); } + + auto size()const { return _data.size(); } + + std::array extract_as_byte_array()const { + std::array arr; + + const size_t num_sub_words = sizeof(word_t); + + auto arr_itr = arr.begin(); + auto data_itr = _data.begin(); + + for( size_t counter = _data.size(); counter > 0; --counter, ++data_itr ) { + size_t sub_words_left = num_sub_words; + + if( counter == 1 ) { // If last word in _data array... + sub_words_left -= padded_bytes(); + } + auto temp_word = *data_itr; + for( ; sub_words_left > 0; --sub_words_left ) { + *(arr_itr + sub_words_left - 1) = static_cast(temp_word & 0xFF); + temp_word >>= 8; + } + arr_itr += num_sub_words; + } + + return arr; + } + + // Comparison operators + friend bool operator== <>(const fixed_key &c1, const fixed_key &c2); + + friend bool operator!= <>(const fixed_key &c1, const fixed_key &c2); + + friend bool operator> <>(const fixed_key &c1, const fixed_key &c2); + + friend bool operator< <>(const fixed_key &c1, const fixed_key &c2); + + private: + + std::array _data; + }; + + /** + * @brief Compares two fixed_key variables c1 and c2 + * + * @details Lexicographically compares two fixed_key variables c1 and c2 + * @return if c1 == c2, return true, otherwise false + */ + template + bool operator==(const fixed_key &c1, const fixed_key &c2) { + return c1._data == c2._data; + } + + /** + * @brief Compares two fixed_key variables c1 and c2 + * + * @details Lexicographically compares two fixed_key variables c1 and c2 + * @return if c1 != c2, return true, otherwise false + */ + template + bool operator!=(const fixed_key &c1, const fixed_key &c2) { + return c1._data != c2._data; + } + + /** + * @brief Compares two fixed_key variables c1 and c2 + * + * @details Lexicographically compares two fixed_key variables c1 and c2 + * @return if c1 > c2, return true, otherwise false + */ + template + bool operator>(const fixed_key &c1, const fixed_key &c2) { + return c1._data > c2._data; + } + + /** + * @brief Compares two fixed_key variables c1 and c2 + * + * @details Lexicographically compares two fixed_key variables c1 and c2 + * @return if c1 < c2, return true, otherwise false + */ + template + bool operator<(const fixed_key &c1, const fixed_key &c2) { + return c1._data < c2._data; + } + /// @} fixed_key + + typedef fixed_key<32> key256; +} diff --git a/contracts/eosiolib/generic_currency.hpp b/contracts/eosiolib/generic_currency.hpp index ef9e06ee1680dc123148badd4e112383a207ed37..460d4695d2d7a7c10c688001002f9bd8b3ededaa 100644 --- a/contracts/eosiolib/generic_currency.hpp +++ b/contracts/eosiolib/generic_currency.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include #include @@ -61,6 +61,8 @@ namespace eosio { uint64_t symbol = token_type::symbol; token_type balance; + auto primary_key() const { return symbol; } + EOSLIB_SERIALIZE( account, (symbol)(balance) ) }; @@ -68,6 +70,8 @@ namespace eosio { uint64_t symbol = token_type::symbol; token_type supply; + auto primary_key() const { return symbol; } + EOSLIB_SERIALIZE( currency_stats, (symbol)(supply) ) }; @@ -75,25 +79,41 @@ namespace eosio { * Each user stores their balance in the singleton table under the * scope of their account name. */ - typedef table64 accounts; - typedef table64 stats; + typedef eosio::multi_index accounts; + typedef eosio::multi_index stats; static token_type get_balance( account_name owner ) { - return accounts::get_or_create( token_type::symbol, owner ).balance; + accounts t( code, owner ); + auto ptr = t.find( symbol ); + return ptr ? ptr->balance : token_type( asset(0, symbol) ); } - static void set_balance( account_name owner, token_type balance ) { - accounts::set( account{token_type::symbol,balance}, owner ); + static void set_balance( account_name owner, token_type balance, account_name create_bill_to, account_name update_bill_to ) { + accounts t( code, owner ); + auto f = [&](account& acc) { + acc.symbol = symbol; + acc.balance = balance; + }; + auto ptr = t.find( symbol ); + if (ptr) { + t.update( *ptr, update_bill_to, f); + } else { + t.emplace( create_bill_to, f); + } } static void on( const issue& act ) { require_auth( code ); - auto s = stats::get_or_create(token_type::symbol); - s.supply += act.quantity; - stats::set(s); + stats t( code, code ); + auto ptr = t.find( symbol ); + if (ptr) { + t.update(*ptr, 0, [&](currency_stats& s) { s.supply += act.quantity; }); + } else { + t.emplace(code, [&](currency_stats& s) { s.supply = act.quantity; }); + } - set_balance( code, get_balance( code ) + act.quantity ); + set_balance( code, get_balance( code ) + act.quantity, code, 0 ); inline_transfer( code, act.to, act.quantity ); } @@ -103,8 +123,8 @@ namespace eosio { require_auth( act.from ); require_recipient(act.to,act.from); - set_balance( act.from, get_balance( act.from ) - act.quantity ); - set_balance( act.to, get_balance( act.to ) + act.quantity ); + set_balance( act.from, get_balance( act.from ) - act.quantity, act.from, act.from ); + set_balance( act.to, get_balance( act.to ) + act.quantity, act.from, 0 ); } static void inline_transfer( account_name from, account_name to, token_type quantity, diff --git a/contracts/eosiolib/multi_index.hpp b/contracts/eosiolib/multi_index.hpp index 3b06278f0750e501301c10222013c35dd9f89ebc..9fa19e686b24480da4e7f4baa8b7236ad8ce5658 100644 --- a/contracts/eosiolib/multi_index.hpp +++ b/contracts/eosiolib/multi_index.hpp @@ -1,6 +1,8 @@ #pragma once #include #include +#include +#include #include #include @@ -12,9 +14,7 @@ #include #include #include - - - +#include namespace eosio { @@ -24,71 +24,66 @@ using boost::multi_index::const_mem_fun; template struct secondary_iterator; -template<> -struct secondary_iterator { - static int db_idx_next( int iterator, uint64_t* primary ) { return db_idx64_next( iterator, primary ); } - static int db_idx_prev( int iterator, uint64_t* primary ) { return db_idx64_previous( iterator, primary ); } - static void db_idx_remove( int iterator ) { db_idx64_remove( iterator ); } - static int db_idx_find_primary( uint64_t code, uint64_t scope, uint64_t table, uint64_t primary, uint64_t& secondary ) { - return db_idx64_find_primary( code, scope, table, &secondary, primary ); - } -}; - -template<> -struct secondary_iterator { - static int db_idx_next( int iterator, uint64_t* primary ) { return db_idx128_next( iterator, primary ); } - static int db_idx_prev( int iterator, uint64_t* primary ) { return db_idx128_previous( iterator, primary ); } - static void db_idx_remove( int iterator ) { db_idx128_remove( iterator ); } - static int db_idx_find_primary( uint64_t code, uint64_t scope, uint64_t table, - uint64_t primary, uint128_t& secondary ) { - return db_idx128_find_primary( code, scope, table, &secondary, primary ); - } -}; - -int db_idx_store( uint64_t scope, uint64_t table, uint64_t payer, uint64_t primary, const uint64_t& secondary ) { - return db_idx64_store( scope, table, payer, primary, &secondary ); -} -int db_idx_store( uint64_t scope, uint64_t table, uint64_t payer, uint64_t primary, const uint128_t& secondary ) { - return db_idx128_store( scope, table, payer, primary, &secondary ); +#define WRAP_SECONDARY_SIMPLE_TYPE(IDX, TYPE)\ +template<>\ +struct secondary_iterator {\ + static int db_idx_next( int iterator, uint64_t* primary ) { return db_##IDX##_next( iterator, primary ); }\ + static int db_idx_previous( int iterator, uint64_t* primary ) { return db_##IDX##_previous( iterator, primary ); }\ + static void db_idx_remove( int iterator ) { db_##IDX##_remove( iterator ); }\ + static int db_idx_end( uint64_t code, uint64_t scope, uint64_t table ) { return db_##IDX##_end( code, scope, table ); }\ +};\ +int db_idx_store( uint64_t scope, uint64_t table, uint64_t payer, uint64_t id, const TYPE& secondary ) {\ + return db_##IDX##_store( scope, table, payer, id, &secondary );\ +}\ +void db_idx_update( int iterator, uint64_t payer, const TYPE& secondary ) {\ + db_##IDX##_update( iterator, payer, &secondary );\ +}\ +int db_idx_find_primary( uint64_t code, uint64_t scope, uint64_t table, uint64_t primary, TYPE& secondary ) {\ + return db_##IDX##_find_primary( code, scope, table, &secondary, primary );\ +}\ +int db_idx_find_secondary( uint64_t code, uint64_t scope, uint64_t table, const TYPE& secondary, uint64_t& primary ) {\ + return db_##IDX##_find_secondary( code, scope, table, &secondary, &primary );\ +}\ +int db_idx_lowerbound( uint64_t code, uint64_t scope, uint64_t table, TYPE& secondary, uint64_t& primary ) {\ + return db_##IDX##_lowerbound( code, scope, table, &secondary, &primary );\ +}\ +int db_idx_upperbound( uint64_t code, uint64_t scope, uint64_t table, TYPE& secondary, uint64_t& primary ) {\ + return db_##IDX##_upperbound( code, scope, table, &secondary, &primary );\ } -void db_idx_update( int iterator, uint64_t payer, const uint64_t& secondary ) { - db_idx64_update( iterator, payer, &secondary ); -} -void db_idx_update( int iterator, uint64_t payer, const uint128_t& secondary ) { - db_idx128_update( iterator, payer, &secondary ); +#define WRAP_SECONDARY_ARRAY_TYPE(IDX, TYPE)\ +template<>\ +struct secondary_iterator {\ + static int db_idx_next( int iterator, uint64_t* primary ) { return db_##IDX##_next( iterator, primary ); }\ + static int db_idx_previous( int iterator, uint64_t* primary ) { return db_##IDX##_previous( iterator, primary ); }\ + static void db_idx_remove( int iterator ) { db_##IDX##_remove( iterator ); }\ + static int db_idx_end( uint64_t code, uint64_t scope, uint64_t table ) { return db_##IDX##_end( code, scope, table ); }\ +};\ +int db_idx_store( uint64_t scope, uint64_t table, uint64_t payer, uint64_t id, const TYPE& secondary ) {\ + return db_##IDX##_store( scope, table, payer, id, secondary.data(), TYPE::num_words() );\ +}\ +void db_idx_update( int iterator, uint64_t payer, const TYPE& secondary ) {\ + db_##IDX##_update( iterator, payer, secondary.data(), TYPE::num_words() );\ +}\ +int db_idx_find_primary( uint64_t code, uint64_t scope, uint64_t table, uint64_t primary, TYPE& secondary ) {\ + return db_##IDX##_find_primary( code, scope, table, secondary.data(), TYPE::num_words(), primary );\ +}\ +int db_idx_find_secondary( uint64_t code, uint64_t scope, uint64_t table, const TYPE& secondary, uint64_t& primary ) {\ + return db_##IDX##_find_secondary( code, scope, table, secondary.data(), TYPE::num_words(), &primary );\ +}\ +int db_idx_lowerbound( uint64_t code, uint64_t scope, uint64_t table, TYPE& secondary, uint64_t& primary ) {\ + return db_##IDX##_lowerbound( code, scope, table, secondary.data(), TYPE::num_words(), &primary );\ +}\ +int db_idx_upperbound( uint64_t code, uint64_t scope, uint64_t table, TYPE& secondary, uint64_t& primary ) {\ + return db_##IDX##_upperbound( code, scope, table, secondary.data(), TYPE::num_words(), &primary );\ } -int db_idx_find_primary( uint64_t code, uint64_t scope, uint64_t table, uint64_t& secondary, uint64_t primary ) { - return db_idx64_find_primary( code, scope, table, &secondary, primary ); -} -int db_idx_find_secondary( uint64_t code, uint64_t scope, uint64_t table, uint64_t& secondary, uint64_t& primary ) { - return db_idx64_find_secondary( code, scope, table, &secondary, &primary ); -} -int db_idx_lowerbound( uint64_t code, uint64_t scope, uint64_t table, uint64_t& secondary, uint64_t& primary ) { - return db_idx64_lowerbound( code, scope, table, &secondary, &primary ); -} -int db_idx_upperbound( uint64_t code, uint64_t scope, uint64_t table, uint64_t& secondary, uint64_t& primary ) { - return db_idx64_lowerbound( code, scope, table, &secondary, &primary ); -} +WRAP_SECONDARY_SIMPLE_TYPE(idx64, uint64_t) +WRAP_SECONDARY_SIMPLE_TYPE(idx128, uint128_t) +WRAP_SECONDARY_ARRAY_TYPE(idx256, key256) -int db_idx_find_primary( uint64_t code, uint64_t scope, uint64_t table, uint128_t& secondary, uint64_t primary ) { - return db_idx128_find_primary( code, scope, table, &secondary, primary ); -} -int db_idx_find_secondary( uint64_t code, uint64_t scope, uint64_t table, uint128_t& secondary, uint64_t& primary ) { - return db_idx128_find_secondary( code, scope, table, &secondary, &primary ); -} -int db_idx_lowerbound( uint64_t code, uint64_t scope, uint64_t table, uint128_t& secondary, uint64_t& primary ) { - return db_idx128_lowerbound( code, scope, table, &secondary, &primary ); -} -int db_idx_upperbound( uint64_t code, uint64_t scope, uint64_t table, uint128_t& secondary, uint64_t& primary ) { - return db_idx128_lowerbound( code, scope, table, &secondary, &primary ); -} - - - -template +template class multi_index; template @@ -99,7 +94,7 @@ struct indexed_by { }; -template +template struct index_by { typedef Extractor extractor_secondary_type; typedef typename std::decay::type secondary_type; @@ -107,12 +102,17 @@ struct index_by { index_by(){} enum constants { + table_name = TableName, index_name = IndexName, - index_number = N + index_number = N, + index_table_name = (TableName & 0xFFFFFFFFFFFFFFF0ULL) | (N & 0x000000000000000FULL) // Assuming no more than 16 secondary indices are allowed }; constexpr static int number() { return N; } - constexpr static uint64_t name() { return IndexName; } + constexpr static uint64_t name() { + // return IndexName; + return index_table_name; + } private: template @@ -121,7 +121,7 @@ struct index_by { static auto extract_secondary_key(const T& obj) { return extractor_secondary_type()(obj); } static int store( uint64_t scope, uint64_t payer, const T& obj ) { - return db_idx_store( scope, IndexName, payer, obj.primary_key(), extract_secondary_key(obj) ); + return db_idx_store( scope, name(), payer, obj.primary_key(), extract_secondary_key(obj) ); } static void update( int iterator, uint64_t payer, const secondary_type& secondary ) { @@ -129,7 +129,7 @@ struct index_by { } static int find_primary( uint64_t code, uint64_t scope, uint64_t primary, secondary_type& secondary ) { - return db_idx_find_primary( code, scope, IndexName, secondary, primary ); + return db_idx_find_primary( code, scope, name(), primary, secondary ); } static void remove( int itr ) { @@ -137,28 +137,36 @@ struct index_by { } static int find_secondary( uint64_t code, uint64_t scope, secondary_type& secondary, uint64_t& primary ) { - return db_idx_find_secondary( code, scope, IndexName, secondary, primary ); + return db_idx_find_secondary( code, scope, name(), secondary, primary ); } static int lower_bound( uint64_t code, uint64_t scope, secondary_type& secondary, uint64_t& primary ) { - return db_idx_lowerbound( code, scope, IndexName, secondary, primary ); + return db_idx_lowerbound( code, scope, name(), secondary, primary ); } + static int upper_bound( uint64_t code, uint64_t scope, secondary_type& secondary, uint64_t& primary ) { - return db_idx_upperbound( code, scope, IndexName, secondary, primary ); + return db_idx_upperbound( code, scope, name(), secondary, primary ); } }; - - - namespace hana = boost::hana; -template -class multi_index +template +class multi_index { private: - struct item : public T + + static_assert( sizeof...(Indices) <= 16, "multi_index only supports a maximum of 16 secondary indices" ); + + constexpr static bool validate_table_name(uint64_t n) { + // Limit table names to 12 characters so that the last character (4 bits) can be used to distinguish between the secondary indices. + return (n & 0x000000000000000FULL) == 0; + } + + static_assert( validate_table_name(TableName), "multi_index does not support table names with a length greater than 12"); + + struct item : public T { template item( const multi_index& idx, Constructor&& c ) @@ -168,24 +176,34 @@ class multi_index const multi_index& __idx; int __primary_itr; - int __iters[sizeof...(Indicies)+(sizeof...(Indicies)==0)]; + int __iters[sizeof...(Indices)+(sizeof...(Indices)==0)]; }; uint64_t _code; uint64_t _scope; + mutable uint64_t _next_primary_key; + + enum next_primary_key_tags : uint64_t { + no_available_primary_key = static_cast(-2), // Must be the smallest uint64_t value compared to all other tags + unset_next_primary_key = static_cast(-1) + }; + template struct intc { enum e{ value = I }; operator uint64_t()const{ return I; } }; - static constexpr auto transform_indicies( ) { - typedef decltype( hana::zip_shortest( - hana::make_tuple( intc<0>(), intc<1>(), intc<2>(), intc<3>(), intc<4>(), intc<5>() ), - hana::tuple() ) ) indicies_input_type; + static constexpr auto transform_indices( ) { + typedef decltype( hana::zip_shortest( + hana::make_tuple( intc<0>(), intc<1>(), intc<2>(), intc<3>(), intc<4>(), intc<5>(), + intc<6>(), intc<7>(), intc<8>(), intc<9>(), intc<10>(), intc<11>(), + intc<12>(), intc<13>(), intc<14>(), intc<15>() ), + hana::tuple() ) ) indices_input_type; - return hana::transform( indicies_input_type(), [&]( auto&& idx ){ + return hana::transform( indices_input_type(), [&]( auto&& idx ){ typedef typename std::decay(idx))>::type num_type; typedef typename std::decay(idx))>::type idx_type; - return index_by(); @@ -193,15 +211,15 @@ class multi_index }); } - typedef decltype( multi_index::transform_indicies() ) indicies_type; + typedef decltype( multi_index::transform_indices() ) indices_type; - indicies_type _indicies; + indices_type _indices; struct by_primary_key; struct by_primary_itr; - mutable boost::multi_index_container< item, - bmi::indexed_by< + mutable boost::multi_index_container< item, + bmi::indexed_by< bmi::ordered_unique< bmi::tag, bmi::const_mem_fun >, bmi::ordered_unique< bmi::tag, bmi::member > > @@ -225,7 +243,7 @@ class multi_index ds >> val; i.__primary_itr = itr; - boost::hana::for_each( _indicies, [&]( auto& idx ) { + boost::hana::for_each( _indices, [&]( auto& idx ) { i.__iters[ idx.number() ] = -1; }); }); @@ -239,7 +257,9 @@ class multi_index public: - multi_index( uint64_t code, uint64_t scope ):_code(code),_scope(scope){} + multi_index( uint64_t code, uint64_t scope ) + :_code(code),_scope(scope),_next_primary_key(unset_next_primary_key) + {} uint64_t get_code()const { return _code; } uint64_t get_scope()const { return _scope; } @@ -271,26 +291,27 @@ class multi_index } const_iterator& operator++() { - if( !_item ) return *this; + eosio_assert( _item != nullptr, "cannot increment end iterator" ); + const auto& idx = _idx.get(); + if( _item->__iters[Number] == -1 ) { - /// TODO: lookup iter for this item in this index secondary_key_type temp_secondary_key; - auto idxitr = secondary_iterator::db_idx_find_primary( - _idx.get_code(), - _idx.get_scope(), - _idx.name(), - _item->primary_key(), temp_secondary_key); + auto idxitr = db_idx_find_primary(idx.get_code(), + idx.get_scope(), + idx.name(), + _item->primary_key(), temp_secondary_key); + auto& mi = const_cast( *_item ); + mi.__iters[Number] = idxitr; } - uint64_t next_pk = 0; auto next_itr = secondary_iterator::db_idx_next( _item->__iters[Number], &next_pk ); - if( next_itr == -1 ) { + if( next_itr < 0 ) { _item = nullptr; return *this; } - const T& obj = *_idx._multidx.find( next_pk ); + const T& obj = *idx._multidx.find( next_pk ); auto& mi = const_cast( static_cast(obj) ); mi.__iters[Number] = next_itr; _item = &mi; @@ -299,17 +320,30 @@ class multi_index } const_iterator& operator--() { - if( !_item ) { - - } uint64_t prev_pk = 0; - auto prev_itr = secondary_iterator::db_idx_prev( _item->__iters[Number], &prev_pk ); - if( prev_itr == -1 ) { - _item = nullptr; - return *this; + int prev_itr = -1; + const auto& idx = _idx.get(); + + if( !_item ) { + auto ei = secondary_iterator::db_idx_end(idx.get_code(), idx.get_scope(), idx.name()); + eosio_assert( ei != -1, "cannot decrement end iterator when the index is empty" ); + prev_itr = secondary_iterator::db_idx_previous( ei , &prev_pk ); + eosio_assert( prev_itr != -1, "cannot decrement end iterator when the index is empty" ); + } else { + if( _item->__iters[Number] == -1 ) { + secondary_key_type temp_secondary_key; + auto idxitr = db_idx_find_primary(idx.get_code(), + idx.get_scope(), + idx.name(), + _item->primary_key(), temp_secondary_key); + auto& mi = const_cast( *_item ); + mi.__iters[Number] = idxitr; + } + prev_itr = secondary_iterator::db_idx_previous( _item->__iters[Number], &prev_pk ); + eosio_assert( prev_itr >= 0, "cannot decrement iterator at beginning of index" ); } - const T& obj = *_idx._multidx.find( prev_pk ); + const T& obj = *idx._multidx.find( prev_pk ); auto& mi = const_cast( static_cast(obj) ); mi.__iters[Number] = prev_itr; _item = &mi; @@ -318,26 +352,44 @@ class multi_index } const T& operator*()const { return *static_cast(_item); } - const T* operator->()const { return *static_cast(_item); } + const T* operator->()const { return static_cast(_item); } private: friend struct index; const_iterator( const index& idx, const typename MultiIndexType::item* i = nullptr ) - :_idx(idx), _item(i){} + :_idx(std::cref(idx)), _item(i){} - const index& _idx; + std::reference_wrapper _idx; const typename MultiIndexType::item* _item; }; const_iterator end()const { return const_iterator( *this ); } - const_iterator begin()const { return const_iterator( *this, nullptr ); } - const_iterator lower_bound( typename IndexType::secondary_type&& secondary ) { + const_iterator begin()const { + return lower_bound(typename IndexType::secondary_type()); + } + const_iterator lower_bound( typename IndexType::secondary_type&& secondary )const { return lower_bound( secondary ); } - const_iterator lower_bound( typename IndexType::secondary_type& secondary ) { + const_iterator lower_bound( const typename IndexType::secondary_type& secondary )const { + uint64_t primary = 0; + typename IndexType::secondary_type secondary_copy(secondary); + auto itr = IndexType::lower_bound( get_code(), get_scope(), secondary_copy, primary ); + if( itr < 0 ) return end(); + + const T& obj = *_multidx.find( primary ); + auto& mi = const_cast( static_cast(obj) ); + mi.__iters[Number] = itr; + + return const_iterator( *this, &mi ); + } + const_iterator upper_bound( typename IndexType::secondary_type&& secondary )const { + return upper_bound( secondary ); + } + const_iterator upper_bound( const typename IndexType::secondary_type& secondary )const { uint64_t primary = 0; - auto itr = IndexType::lower_bound( _multidx._code, _multidx._scope, secondary, primary ); - if( itr == -1 ) return end(); + typename IndexType::secondary_type secondary_copy(secondary); + auto itr = IndexType::upper_bound( get_code(), get_scope(), secondary_copy, primary ); + if( itr < 0 ) return end(); const T& obj = *_multidx.find( primary ); auto& mi = const_cast( static_cast(obj) ); @@ -354,7 +406,7 @@ class multi_index index( const MultiIndexType& midx ) //, const IndexType& idx ) :_multidx(midx){} - const MultiIndexType _multidx; + const MultiIndexType& _multidx; }; @@ -367,6 +419,7 @@ class multi_index } const T& operator*()const { return *static_cast(_item); } + const T* operator->()const { return static_cast(_item); } const_iterator operator++(int)const { return ++(const_iterator(*this)); @@ -376,31 +429,41 @@ class multi_index } const_iterator& operator++() { - //eosio_assert( _item, "null ptr" ); - uint64_t pk; - auto next_itr = db_next_i64( _item->__primary_itr, &pk ); - if( next_itr == -1 ) + eosio_assert( _item != nullptr, "cannot increment end iterator" ); + const auto& multidx = _multidx.get(); + + uint64_t next_pk; + auto next_itr = db_next_i64( _item->__primary_itr, &next_pk ); + if( next_itr < 0 ) _item = nullptr; else - _item = &_multidx.load_object_by_primary_iterator( next_itr ); + _item = &multidx.load_object_by_primary_iterator( next_itr ); return *this; } const_iterator& operator--() { - //eosio_assert( _item, "null ptr" ); - uint64_t pk; - auto next_itr = db_previous_i64( _item->__primary_itr, &pk ); - if( next_itr == -1 ) - _item = nullptr; - else - _item = &_multidx.load_object_by_primary_iterator( next_itr ); + uint64_t prev_pk; + int prev_itr = -1; + const auto& multidx = _multidx.get(); + + if( !_item ) { + auto ei = db_end_i64(multidx.get_code(), multidx.get_scope(), TableName); + eosio_assert( ei != -1, "cannot decrement end iterator when the table is empty" ); + prev_itr = db_previous_i64( ei , &prev_pk ); + eosio_assert( prev_itr != -1, "cannot decrement end iterator when the table is empty" ); + } else { + prev_itr = db_previous_i64( _item->__primary_itr, &prev_pk ); + eosio_assert( prev_itr >= 0, "cannot decrement iterator at beginning of table" ); + } + + _item = &multidx.load_object_by_primary_iterator( prev_itr ); return *this; } private: const_iterator( const multi_index& mi, const item* i = nullptr ) - :_multidx(mi),_item(i){} + :_multidx(std::cref(mi)),_item(i){} - const multi_index& _multidx; + std::reference_wrapper _multidx; const item* _item; friend class multi_index; }; @@ -408,66 +471,74 @@ class multi_index const_iterator end()const { return const_iterator( *this ); } const_iterator begin()const { return lower_bound(); } - const_iterator lower_bound( uint64_t primary = 0 )const { + const_iterator lower_bound( uint64_t primary = 0 )const { auto itr = db_lowerbound_i64( _code, _scope, TableName, primary ); + if( itr < 0 ) return end(); auto& obj = load_object_by_primary_iterator( itr ); return const_iterator( *this, &obj ); } - const_iterator upper_bound( uint64_t primary = 0 )const { + const_iterator upper_bound( uint64_t primary = 0 )const { auto itr = db_upperbound_i64( _code, _scope, TableName, primary ); + if( itr < 0 ) return end(); auto& obj = load_object_by_primary_iterator( itr ); return const_iterator( *this, &obj ); } - void new_id( uint64_t payer )const { - uint64_t val = 1; - auto itr = db_find_i64( _code, _scope, TableName+1, 0 ); - if( itr != -1 ) { - auto s = db_get_i64( itr, (char*)&val, sizeof(val) ); - ++val; - db_update_i64( itr, 0, (const char*)&val, sizeof(val) ); - } - else { - db_store_i64( _scope, TableName+1, payer, 0, (char*)&val, sizeof(val) ); - } + /** Ideally this method would only be used to determine the appropriate primary key to use within new objects added to a + * table in which the primary keys of the table are strictly intended from the beginning to be autoincrementing and + * thus will not ever be set to custom arbitrary values by the contract. + * Violating this agreement could result in the table appearing full when in reality there is plenty of space left. + */ + uint64_t available_primary_key()const { + if( _next_primary_key == unset_next_primary_key ) { + // This is the first time available_primary_key() is called for this multi_index instance. + if( begin() == end() ) { // empty table + _next_primary_key = 0; + } else { + auto itr = --end(); // last row of table sorted by primary key + auto pk = itr->primary_key(); // largest primary key currently in table + if( pk >= no_available_primary_key ) // Reserve the tags + _next_primary_key = no_available_primary_key; + else + _next_primary_key = pk + 1; + } + } + + eosio_assert( _next_primary_key < no_available_primary_key, "next primary key in table is at autoincrement limit"); + return _next_primary_key; } + template auto get_index()const { - auto idx = boost::hana::find_if( _indicies, []( auto&& in ){ - /* - auto& x = hana::at_c<1>(idxp); - return std::integral_constant::type::index_name == IndexName)>(); - */ + auto idx = boost::hana::find_if( _indices, []( auto&& in ) { return std::integral_constant::type::index_name == IndexName>(); - } ).value(); + }).value(); return index( *this ); - - /* - typedef typename std::decay(idx))>::type num_type; - - return index(idx))>::type, num_type::value >( *this, hana::at_c<1>(idx) ); - */ } template const T& emplace( uint64_t payer, Lambda&& constructor ) { auto result = _items_index.emplace( *this, [&]( auto& i ){ - T& obj = static_cast(i); - constructor( obj ); + T& obj = static_cast(i); + constructor( obj ); - char tmp[ pack_size( obj ) ]; - datastream ds( tmp, sizeof(tmp) ); - ds << obj; + char tmp[ pack_size( obj ) ]; + datastream ds( tmp, sizeof(tmp) ); + ds << obj; - auto pk = obj.primary_key(); - i.__primary_itr = db_store_i64( _scope, TableName, payer, pk, tmp, sizeof(tmp) ); + auto pk = obj.primary_key(); - boost::hana::for_each( _indicies, [&]( auto& idx ) { - i.__iters[idx.number()] = idx.store( _scope, payer, obj ); - }); - }); + i.__primary_itr = db_store_i64( _scope, TableName, payer, pk, tmp, sizeof(tmp) ); + + if( pk >= _next_primary_key ) + _next_primary_key = (pk >= no_available_primary_key) ? no_available_primary_key : (pk + 1); + + boost::hana::for_each( _indices, [&]( auto& idx ) { + i.__iters[idx.number()] = idx.store( _scope, payer, obj ); + }); + }); /// eosio_assert( result.second, "could not insert object, uniqueness constraint in cache violated" ) return static_cast(*result.first); @@ -480,11 +551,11 @@ class multi_index // eosio_assert( &objitem.__idx == this, "invalid object" ); - auto secondary_keys = boost::hana::transform( _indicies, [&]( auto&& idx ) { + auto secondary_keys = boost::hana::transform( _indices, [&]( auto&& idx ) { return idx.extract_secondary_key( obj ); }); - auto mutableobj = const_cast(obj); + auto& mutableobj = const_cast(obj); // Do not forget the auto& otherwise it would make a copy and thus not update at all. updater( mutableobj ); char tmp[ pack_size( obj ) ]; @@ -492,16 +563,20 @@ class multi_index ds << obj; auto pk = obj.primary_key(); + db_update_i64( objitem.__primary_itr, payer, tmp, sizeof(tmp) ); - boost::hana::for_each( _indicies, [&]( auto& idx ) { + if( pk >= _next_primary_key ) + _next_primary_key = (pk >= no_available_primary_key) ? no_available_primary_key : (pk + 1); + + boost::hana::for_each( _indices, [&]( auto& idx ) { typedef typename std::decay::type index_type; - auto secondary = idx.extract_secondary_key( mutableobj ); + auto secondary = idx.extract_secondary_key( obj ); if( hana::at_c(secondary_keys) != secondary ) { auto indexitr = mutableitem.__iters[idx.number()]; - if( indexitr == -1 ) + if( indexitr < 0 ) indexitr = mutableitem.__iters[idx.number()] = idx.find_primary( _code, _scope, pk, secondary ); idx.update( indexitr, payer, secondary ); @@ -514,13 +589,14 @@ class multi_index eosio_assert( result != nullptr, "unable to find key" ); return *result; } + const T* find( uint64_t primary )const { auto cacheitr = _items_index.find(primary); - if( cacheitr != _items_index.end() ) + if( cacheitr != _items_index.end() ) return &*cacheitr; int itr = db_find_i64( _code, _scope, TableName, primary ); - if( itr == -1 ) return nullptr; + if( itr < 0 ) return nullptr; const item& i = load_object_by_primary_iterator( itr ); return &static_cast(i); @@ -533,16 +609,16 @@ class multi_index db_remove_i64( objitem.__primary_itr ); - boost::hana::for_each( _indicies, [&]( auto& idx ) { + boost::hana::for_each( _indices, [&]( auto& idx ) { auto i = objitem.__iters[idx.number()]; - if( i == -1 ) { + if( i < 0 ) { typename std::decay::type::secondary_type second; i = idx.find_primary( _code, _scope, objitem.primary_key(), second ); } - if( i != -1 ) + if( i >= 0 ) idx.remove( i ); }); - + auto cacheitr = _items_index.find(obj.primary_key()); if( cacheitr != _items_index.end() ) { _items_index.erase(cacheitr); @@ -551,5 +627,4 @@ class multi_index }; -} /// eosio - +} /// eosio diff --git a/contracts/eosiolib/print.h b/contracts/eosiolib/print.h index e327814c56ecd3cf7b8fae0b7cd260cd501080d1..e9ec1693d5dcbf6d7ae1ae525bd94034434acc99 100644 --- a/contracts/eosiolib/print.h +++ b/contracts/eosiolib/print.h @@ -100,7 +100,7 @@ extern "C" { /** */ - void printhex( void* data, uint32_t datalen ); + void printhex( const void* data, uint32_t datalen ); /// @} #ifdef __cplusplus diff --git a/contracts/eosiolib/print.hpp b/contracts/eosiolib/print.hpp index 4d1c83da1deb76f6fb9db8060d1379c9dbdc0903..2104ea83351918dee0aa027b666ff6b427924473 100644 --- a/contracts/eosiolib/print.hpp +++ b/contracts/eosiolib/print.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include namespace eosio { @@ -47,6 +49,10 @@ namespace eosio { printi(uint64_t(num)); } + inline void print( long num ) { + printi(num); + } + /** * Prints unsigned integer as a 64 bit unsigned integer * @brief Prints unsigned integer @@ -71,7 +77,20 @@ namespace eosio { * @param num to be printed */ inline void print( uint128_t num ) { - printi128((uint128_t*)&num); + printi128(&num); + } + + + /** + * Prints fixed_key as a hexidecimal string + * @brief Prints fixed_key as a hexidecimal string + * @param val to be printed + */ + template + inline void print( const fixed_key& val ) { + auto arr = val.extract_as_byte_array(); + prints("0x"); + printhex(static_cast(arr.data()), arr.size()); } /** @@ -96,7 +115,7 @@ namespace eosio { inline void print_f( const char* s ) { prints(s); } - + template inline void print_f( const char* s, Arg val, Args... rest ) { while ( *s != '\0' ) { @@ -151,9 +170,9 @@ namespace eosio { * @endcode */ template - void print( Arg a, Args... args ) { - print(a); - print(args...); + void print( Arg&& a, Args&&... args ) { + print(std::forward(a)); + print(std::forward(args)...); } /** diff --git a/contracts/eosiolib/singleton.hpp b/contracts/eosiolib/singleton.hpp index 962b4a44fd54bf4c12ab638ac406fdc7b93fd242..64e73c923f0a44e653323bc6c360ca6af9e1f474 100644 --- a/contracts/eosiolib/singleton.hpp +++ b/contracts/eosiolib/singleton.hpp @@ -1,75 +1,74 @@ #pragma once -#include -#include +#include +#include namespace eosio { /** - * This wrapper uses a single table to store named objects various types. + * This wrapper uses a single table to store named objects various types. * * @tparam Code - the name of the code which has write permission * @tparam SingletonName - the name of this singlton variable - * @tparam T - the type of the singleton + * @tparam T - the type of the singleton */ template class singleton { + constexpr static uint64_t pk_value = SingletonName; + struct row { + T value; + + uint64_t primary_key() const { return pk_value; } + + EOSLIB_SERIALIZE( row, (value) ); + }; + + typedef eosio::multi_index table; + public: //static const uint64_t singleton_table_name = N(singleton); static bool exists( scope_name scope = Code ) { - uint64_t key = SingletonName; - auto read = load_i64( Code, scope, key, (char*)&key, sizeof(key) ); - return read > 0; + table t( Code, scope ); + return t.find( pk_value ); } static T get( scope_name scope = Code ) { - char temp[1024+8]; - *reinterpret_cast(temp) = SingletonName; - auto read = load_i64( Code, scope, SingletonName, temp, sizeof(temp) ); - eosio_assert( read > 0, "singleton does not exist" ); - return unpack( temp + sizeof(SingletonName), read ); + table t( Code, scope ); + auto ptr = t.find( pk_value ); + eosio_assert( bool(ptr), "singleton does not exist" ); + return ptr->value; } static T get_or_default( scope_name scope = Code, const T& def = T() ) { - char temp[1024+8]; - *reinterpret_cast(temp) = SingletonName; - auto read = load_i64( Code, scope, SingletonName, temp, sizeof(temp) ); - if ( read < 0 ) { - return def; - } - return unpack( temp + sizeof(SingletonName), size_t(read) ); + table t( Code, scope ); + auto ptr = t.find( pk_value ); + return ptr ? ptr->value : def; } static T get_or_create( scope_name scope = Code, const T& def = T() ) { - char temp[1024+8]; - *reinterpret_cast(temp) = SingletonName; - - - auto read = load_i64( Code, scope, SingletonName, temp, sizeof(temp) ); - if( read < 0 ) { - set( def, scope ); - return def; - } - return unpack( temp + sizeof(SingletonName), read ); + table t( Code, scope ); + auto ptr = t.find( pk_value ); + return ptr ? ptr->value + : t.emplace(BillToAccount, [&](row& r) { r.value = def; }); } static void set( const T& value = T(), scope_name scope = Code, account_name b = BillToAccount ) { - auto size = pack_size( value ); - char buf[size+ sizeof(SingletonName)]; - - eosio_assert( sizeof(buf) <= 1024 + 8, "singleton too big to store" ); - - datastream ds( buf, size + sizeof(SingletonName) ); - ds << SingletonName; - ds << value; - - store_i64( scope, SingletonName, b, buf, sizeof(buf) ); + table t( Code, scope ); + auto ptr = t.find( pk_value ); + if (ptr) { + t.update(*ptr, b, [&](row& r) { r.value = value; }); + } else { + t.emplace(b, [&](row& r) { r.value = value; }); + } } static void remove( scope_name scope = Code ) { - uint64_t key = SingletonName; - remove_i64( scope, SingletonName, &key ); + table t( Code, scope ); + auto ptr = t.find( pk_value ); + if (ptr) { + t.remove(*ptr); + } } }; diff --git a/contracts/eosiolib/types.h b/contracts/eosiolib/types.h index 390a88c56590c2665e414fc95dbd0f52f501b61c..732b0e79f3c92cd7dba570c6b286baafbac96237 100644 --- a/contracts/eosiolib/types.h +++ b/contracts/eosiolib/types.h @@ -23,10 +23,6 @@ extern "C" { * @{ */ -struct uint256 { - uint64_t words[4]; -}; - typedef uint64_t account_name; typedef uint64_t permission_name; typedef uint64_t token_name; diff --git a/contracts/eosiolib/types.hpp b/contracts/eosiolib/types.hpp index 44d4af330b890334e21ece5ef80af260975c66bb..5b26ac245eace65db4df1e84c6a7ee6ef66dd2b9 100644 --- a/contracts/eosiolib/types.hpp +++ b/contracts/eosiolib/types.hpp @@ -23,7 +23,7 @@ namespace eosio { /** - * @brief Converts a base32 string to a uint64_t. + * @brief Converts a base32 string to a uint64_t. * * @details Converts a base32 string to a uint64_t. This is a constexpr so that * this method can be used in template arguments as well. @@ -87,7 +87,6 @@ namespace eosio { return ds >> v.value; } }; - /// @} } // namespace eos diff --git a/contracts/identity/identity.abi b/contracts/identity/identity.abi index aca726285985d9965c4a9371d824f30addb1d6e4..8bbf55ee4ec3cd929ae0eb35b53441a09bf4c4b1 100644 --- a/contracts/identity/identity.abi +++ b/contracts/identity/identity.abi @@ -66,7 +66,6 @@ "name": "accountrow", "base": "", "fields": [ - {"name":"singleton_name", "type":"uint64"}, {"name":"identity", "type":"uint64"} ] } diff --git a/contracts/identity/identity.hpp b/contracts/identity/identity.hpp index e26eb2e15032177eae8431cdd582f0c705828337..914ae07804c786cf761d00dd8473b3f016dc2d5e 100644 --- a/contracts/identity/identity.hpp +++ b/contracts/identity/identity.hpp @@ -3,44 +3,43 @@ #include #include #include -#include +#include #include namespace identity { using eosio::action_meta; - using eosio::table_i64i64i64; - using eosio::table64; using eosio::singleton; + using eosio::key256; using std::string; using std::vector; /** * This contract maintains a graph database of certified statements about an * identity. An identity is separated from the concept of an account because - * the mapping of identity to accounts is subject to community consensus. + * the mapping of identity to accounts is subject to community consensus. * * Some use cases need a global source of trust, this trust rooted in the voter * who selects block producers. A block producer's opinion is "trusted" and so - * is the opinion of anyone the block producer marks as "trusted". - * + * is the opinion of anyone the block producer marks as "trusted". + * * When a block producer is voted out the implicit trust in every certification - * they made or those they trusted made is removed. All users are liable for - * making false certifications. + * they made or those they trusted made is removed. All users are liable for + * making false certifications. * * An account needs to claim the identity and a trusted account must certify the - * claim. + * claim. * * Data for an identity is stored: * * DeployToAccount / identity / certs / [property, trusted, certifier] => value * * Questions database is designed to answer: - * - * 1. has $identity.$unique been certified a "trusted" certifier - * 2. has $identity.$property been certified by $account + * + * 1. has $identity.$unique been certified a "trusted" certifier + * 2. has $identity.$property been certified by $account * 3. has $identity.$trusted been certified by a "trusted" certifier * 4. what account has authority to speak on behalf of identity? - * - for each trusted owner certification + * - for each trusted owner certification * check to see if the account has claimed it * * 5. what identity does account have authority to speak on behalf? @@ -49,12 +48,12 @@ namespace identity { * * This database structure enables parallel opeartions on independent identities. * - * When an account certs a property we check to see if that + * When an account certs a property we check to see if that */ template class contract { public: - + static const uint64_t code = DeployToAccount; typedef uint64_t identity_name; typedef uint64_t property_name; @@ -63,14 +62,14 @@ namespace identity { /** * This action create a new globally unique 64 bit identifier, * to minimize collisions each account is automatically assigned - * a 32 bit identity prefix based upon hash(account_name) ^ hash(tapos). + * a 32 bit identity prefix based upon hash(account_name) ^ hash(tapos). * * With this method no two accounts are likely to be assigned the same * 32 bit prefix consistantly due to the constantly changing tapos. This prevents * abuse of 'creator' selection to generate intentional conflicts with other users. * * The creator can determine the last 32 bits using an algorithm of their choice. We - * presume the creator's algorithm can avoid collisions with itself. + * presume the creator's algorithm can avoid collisions with itself. * * Even if two accounts get a collision in first 32 bits, a proper creator algorithm * should generate randomness in last 32 bits that will minimize collisions. In event @@ -84,138 +83,105 @@ namespace identity { account_name creator; uint64_t identity = 0; ///< first 32 bits determinsitically derived from creator and tapos - template - friend DataStream& operator << ( DataStream& ds, const create& c ){ - return ds << c.creator << c.identity; - } - template - friend DataStream& operator >> ( DataStream& ds, create& c ){ - return ds >> c.creator >> c.identity; - } + EOSLIB_SERIALIZE( create, (creator)(identity) ) }; struct certvalue { property_name property; ///< name of property, base32 encoded i64 string type; ///< defines type serialized in data - vector data; ///< + vector data; ///< string memo; ///< meta data documenting basis of certification - uint8_t confidence = 1; ///< used to define liability for lies, + uint8_t confidence = 1; ///< used to define liability for lies, /// 0 to delete - template - friend DataStream& operator << ( DataStream& ds, const certvalue& c ){ - return ds << c.property << c.type << c.data << c.memo << c.confidence; - } - template - friend DataStream& operator >> ( DataStream& ds, certvalue& c ){ - return ds >> c.property >> c.type >> c.data >> c.memo >> c.confidence; - } + + EOSLIB_SERIALIZE( certvalue, (property)(type)(data)(memo)(confidence) ) }; - struct certprop : public action_meta< code, N(certprop) > + struct certprop : public action_meta< code, N(certprop) > { account_name bill_storage_to; ///< account which is paying for storage - account_name certifier; + account_name certifier; identity_name identity; vector values; - template - friend DataStream& operator << ( DataStream& ds, const certprop& c ){ - return ds << c.bill_storage_to << c.certifier << c.identity << c.values; - } - template - friend DataStream& operator >> ( DataStream& ds, certprop& c ){ - return ds >> c.bill_storage_to >> c.certifier >> c.identity >> c.values; - } + EOSLIB_SERIALIZE( certprop, (bill_storage_to)(certifier)(identity)(values) ) }; - struct settrust : public action_meta< code, N(settrust) > + struct settrust : public action_meta< code, N(settrust) > { account_name trustor; ///< the account authorizing the trust account_name trusting; ///< the account receiving the trust uint8_t trust = 0; /// 0 to remove, -1 to mark untrusted, 1 to mark trusted - template - friend DataStream& operator << ( DataStream& ds, const settrust& c ){ - return ds << c.trustor << c.trusting << c.trust; - } - template - friend DataStream& operator >> ( DataStream& ds, settrust& c ){ - return ds >> c.trustor >> c.trusting >> c.trust; - } + EOSLIB_SERIALIZE( settrust, (trustor)(trusting)(trust) ) }; - /** - * Defines an object in an i64i64i64 table - */ struct certrow { + uint64_t id; property_name property; uint64_t trusted; account_name certifier; uint8_t confidence = 0; string type; vector data; - - template - friend DataStream& operator << ( DataStream& ds, const certrow& r ){ - return ds << r.property << r.trusted << r.certifier << r.confidence << r.type << r.data; - } - template - friend DataStream& operator >> ( DataStream& ds, certrow& r ){ - return ds >> r.property >> r.trusted >> r.certifier >> r.confidence >> r.type >> r.data; + uint64_t primary_key() const { return id; } + /* constexpr */ static key256 key(uint64_t property, uint64_t trusted, uint64_t certifier) { + /* + key256 key; + key.uint64s[0] = property; + key.uint64s[1] = trusted; + key.uint64s[2] = certifier; + key.uint64s[3] = 0; + */ + return key256::make_from_word_sequence(property, trusted, certifier); } + key256 get_key() const { return key(property, trusted, certifier); } + + EOSLIB_SERIALIZE( certrow , (property)(trusted)(certifier)(confidence)(type)(data)(id) ) }; struct identrow { - uint64_t identity; + uint64_t identity; account_name creator; - template - friend DataStream& operator << ( DataStream& ds, const identrow& r ){ - return ds << r.identity << r.creator; - } - template - friend DataStream& operator >> ( DataStream& ds, identrow& r ){ - return ds >> r.identity >> r.creator; - } + uint64_t primary_key() const { return identity; } + + EOSLIB_SERIALIZE( identrow , (identity)(creator) ) }; struct trustrow { account_name account; - uint8_t trusted; - template - friend DataStream& operator << ( DataStream& ds, const trustrow& r ){ - return ds << r.account << r.trusted; - } - template - friend DataStream& operator >> ( DataStream& ds, trustrow& r ){ - return ds >> r.account >> r.trusted; - } + uint64_t primary_key() const { return account; } + + EOSLIB_SERIALIZE( trustrow, (account) ); }; - typedef table_i64i64i64 certs_table; - typedef table64 idents_table; + typedef eosio::multi_index > + > certs_table; + typedef eosio::multi_index idents_table; typedef singleton accounts_table; - typedef table64 trust_table; + typedef eosio::multi_index trust_table; static identity_name get_claimed_identity( account_name acnt ) { return accounts_table::get_or_default(acnt, 0); } static account_name get_owner_for_identity( identity_name ident ) { - // for each trusted owner certification + // for each trusted owner certification // check to see if the certification is still trusted // check to see if the account has claimed it - certs_table certs( ident ); - certrow row; - bool ok = certs.primary_lower_bound(row, N(owner), 1, 0); + certs_table certs( code, ident ); + auto idx = certs.template get_index(); + auto itr = idx.lower_bound(certrow::key(N(owner), 1, 0)); account_name owner = 0; - while (ok && row.property == N(owner) && row.trusted) { - if (sizeof(account_name) == row.data.size()) { - account_name account = *reinterpret_cast(row.data.data()); + while (itr != idx.end() && itr->property == N(owner) && itr->trusted) { + if (sizeof(account_name) == itr->data.size()) { + account_name account = *reinterpret_cast(itr->data.data()); if (ident == get_claimed_identity(account)) { - if (is_trusted(row.certifier) ) { + if (is_trusted(itr->certifier) ) { // the certifier is still trusted if (!owner || owner == account) { owner = account; @@ -225,8 +191,9 @@ namespace identity { } } else if (DeployToAccount == current_receiver()){ //the certifier is no longer trusted, need to unset the flag - row.trusted = 0; - certs.store( row, 0 ); //assuming 0 means bill to the same account + certs.update(*itr, 0, [&](certrow& r) { + r.trusted = 0; + }); } else { // the certifier is no longer trusted, but the code runs in read-only mode } @@ -234,24 +201,24 @@ namespace identity { } else { // bad row - skip it } - //ok = certs.primary_upper_bound(row, row.property, row.trusted, row.certifier); - ok = certs.next_primary(row, row); + ++itr; } if (owner) { //owner found, no contradictions among certifications flaged as trusted return owner; } // trusted certification not found - // let's see if some of untrusted certifications became trusted - ok = certs.primary_lower_bound(row, N(owner), 0, 0); - while (ok && row.property == N(owner) && !row.trusted) { - if (sizeof(account_name) == row.data.size()) { - account_name account = *reinterpret_cast(row.data.data()); - if (ident == get_claimed_identity(account) && is_trusted(row.certifier)) { + // let's see if any untrusted certifications became trusted + itr = idx.lower_bound(certrow::key(N(owner), 0, 0)); + while (itr != idx.end() && itr->property == N(owner) && !itr->trusted) { + if (sizeof(account_name) == itr->data.size()) { + account_name account = *reinterpret_cast(itr->data.data()); + if (ident == get_claimed_identity(account) && is_trusted(itr->certifier)) { if (DeployToAccount == current_receiver()) { // the certifier became trusted and we have permissions to update the flag - row.trusted = 1; - certs.store( row, 0 ); //assuming 0 means bill to the same account + certs.update(*itr, 0, [&](certrow& r) { + r.trusted = 1; + }); } if (!owner || owner == account) { owner = account; @@ -263,8 +230,7 @@ namespace identity { } else { // bad row - skip it } - //ok = certs.primary_upper_bound(row, row.property, row.trusted, row.certifier); - ok = certs.next_primary(row, row); + ++itr; } return owner; } @@ -277,18 +243,19 @@ namespace identity { } static bool is_trusted_by( account_name trusted, account_name by ) { - return trust_table::exists(trusted, by); + trust_table t( code, by ); + return t.find( trusted ); } static bool is_trusted( account_name acnt ) { account_name active_producers[21]; - get_active_producers( active_producers, sizeof(active_producers) ); - for( const auto& n : active_producers ) { - if( n == acnt ) + auto count = get_active_producers( active_producers, sizeof(active_producers) ); + for( size_t i = 0; i < count; ++i ) { + if( active_producers[i] == acnt ) return true; } - for( const auto& n : active_producers ) { - if( is_trusted_by( acnt, n ) ) + for( size_t i = 0; i < count; ++i ) { + if( is_trusted_by( acnt, active_producers[i] ) ) return true; } return false; @@ -298,19 +265,27 @@ namespace identity { require_auth( t.trustor ); require_recipient( t.trusting ); - if( t.trust != 0 ) { - trust_table::set( t.trusting, t.trustor ); - } else { - trust_table::remove( t.trusting, t.trustor ); + trust_table table( code, t.trustor ); + auto ptr = table.find(t.trusting); + if (!ptr && t.trust > 0) { + table.emplace( t.trustor, [&](trustrow& row) { + row.account = t.trusting; + }); + } else if (ptr && t.trust == 0) { + table.remove(*ptr); } } static void on( const create& c ) { require_auth( c.creator ); - eosio_assert( !idents_table::exists( c.identity ), "identity already exists" ); + idents_table t( code, code); + auto ptr = t.find( c.identity ); + eosio_assert( !ptr, "identity already exists" ); eosio_assert( c.identity != 0, "identity=0 is not allowed" ); - idents_table::set( identrow{ .identity = c.identity, - .creator = c.creator } ); + t.emplace(c.creator, [&](identrow& i) { + i.identity = c.identity; + i.creator = c.creator; + }); } static void on( const certprop& cert ) { @@ -318,25 +293,43 @@ namespace identity { if( cert.bill_storage_to != cert.certifier ) require_auth( cert.bill_storage_to ); - eosio_assert( idents_table::exists( cert.identity ), "identity does not exist" ); + idents_table t( code, code ); + auto ptr = t.find( cert.identity ); + eosio_assert( ptr != nullptr, "identity does not exist" ); - /// the table exists in the scope of the identity - certs_table certs( cert.identity ); + /// the table exists in the scope of the identity + certs_table certs( code, cert.identity ); + bool trusted = is_trusted( cert.certifier ); for( const auto& value : cert.values ) { + auto idx = certs.template get_index(); if (value.confidence) { - certrow row; - row.property = value.property; - row.trusted = is_trusted( cert.certifier ); - row.certifier = cert.certifier; - row.confidence = value.confidence; eosio_assert(value.type.size() <= 32, "certrow::type should be not longer than 32 bytes"); - row.type = value.type; - row.data = value.data; + auto itr = idx.lower_bound( certrow::key(value.property, trusted, cert.certifier) ); + + if (itr != idx.end() && itr->property == value.property && itr->trusted == trusted && itr->certifier == cert.certifier) { + certs.update(*itr, 0, [&](certrow& row) { + row.confidence = value.confidence; + row.type = value.type; + row.data = value.data; + }); + } else { + auto pk = certs.available_primary_key(); + certs.emplace(code, [&](certrow& row) { + row.id = pk; + row.property = value.property; + row.trusted = trusted; + row.certifier = cert.certifier; + row.confidence = value.confidence; + row.type = value.type; + row.data = value.data; + }); + } - certs.store( row, cert.bill_storage_to ); - //remove row with different "trusted" value - certs.remove(value.property, !row.trusted, cert.certifier); + auto itr_old = idx.lower_bound( certrow::key(value.property, !trusted, cert.certifier) ); + if (itr_old != idx.end() && itr_old->property == value.property && itr_old->trusted == !trusted && itr_old->certifier == cert.certifier) { + certs.remove(*itr_old); + } //special handling for owner if (value.property == N(owner)) { @@ -347,9 +340,19 @@ namespace identity { } } } else { - //remove both tursted and untrusted because we cannot know if it was trusted back at creation time - certs.remove(value.property, 0, cert.certifier); - certs.remove(value.property, 1, cert.certifier); + bool removed = false; + auto itr = idx.lower_bound( certrow::key(value.property, trusted, cert.certifier) ); + if (itr != idx.end() && itr->property == value.property && itr->trusted == trusted && itr->certifier == cert.certifier) { + certs.remove(*itr); + } else { + removed = true; + } + itr = idx.lower_bound( certrow::key(value.property, !trusted, cert.certifier) ); + if (itr != idx.end() && itr->property == value.property && itr->trusted == !trusted && itr->certifier == cert.certifier) { + certs.remove(*itr); + } else { + removed = true; + } //special handling for owner if (value.property == N(owner)) { eosio_assert(sizeof(account_name) == value.data.size(), "data size doesn't match account_name size"); diff --git a/contracts/identity/test/identity_test.cpp b/contracts/identity/test/identity_test.cpp index f3f336cfe2555d0a79016d8981da3e099db1e637..c9904dc45ae8828cc8e54ac0ec9dd1bf5115c527 100644 --- a/contracts/identity/test/identity_test.cpp +++ b/contracts/identity/test/identity_test.cpp @@ -25,28 +25,14 @@ namespace identity_test { { uint64_t identity; - template - friend DataStream& operator << ( DataStream& ds, const get_owner_for_identity& c ){ - return ds << c.identity; - } - template - friend DataStream& operator >> ( DataStream& ds, get_owner_for_identity& c ){ - return ds >> c.identity; - } + EOSLIB_SERIALIZE( get_owner_for_identity, (identity) ); }; struct get_identity_for_account : public action_meta< code, N(getidentity) > { account_name account ; - template - friend DataStream& operator << ( DataStream& ds, const get_identity_for_account& c ){ - return ds << c.account; - } - template - friend DataStream& operator >> ( DataStream& ds, get_identity_for_account& c ){ - return ds >> c.account; - } + EOSLIB_SERIALIZE( get_identity_for_account, (account) ); }; typedef singleton result_table; diff --git a/contracts/multi_index_test/multi_index_test.abi b/contracts/multi_index_test/multi_index_test.abi index 969c44af8f62f0e0e14b5daa9b6e7ae6a9ff9bdf..7fa7ddf0b6a6433cb4c6a062164ff1b7ad85535d 100644 --- a/contracts/multi_index_test/multi_index_test.abi +++ b/contracts/multi_index_test/multi_index_test.abi @@ -1,43 +1,17 @@ { - "types": [{ - "new_type_name": "my_account_name", - "type": "name" - } - ], + "types": [], "structs": [{ - "name": "message", + "name": "trigger", "base": "", "fields": [ - {"name":"from", "type":"my_account_name"}, - {"name":"to", "type":"my_account_name"}, - {"name": "message", "type":"string" } + {"name": "what", "type": "uint32" } ] - },{ - "name": "messages_count", - "base": "", - "fields": [ - {"name": "user", "type": "my_account_name"}, - {"name": "count", "type": "uint32"} - ] - } + } ], "actions": [{ - "name": "message", - "type": "message" + "name": "trigger", + "type": "trigger" } ], - "tables": [{ - "name": "msgsent", - "type": "messages_count", - "index_type": "i64", - "key_names" : ["user"], - "key_types" : ["my_account_name"] - },{ - "name": "msgreceived", - "type": "messages_count", - "index_type": "i64", - "key_names" : ["user"], - "key_types" : ["my_account_name"] - } - ] -} \ No newline at end of file + "tables": [] +} diff --git a/contracts/multi_index_test/multi_index_test.cpp b/contracts/multi_index_test/multi_index_test.cpp index e00874105561b83f179f51eec586d8f4983ef617..5c6b334acaab3683116c880ee50f8f098ee9c2d5 100644 --- a/contracts/multi_index_test/multi_index_test.cpp +++ b/contracts/multi_index_test/multi_index_test.cpp @@ -1,7 +1,10 @@ +#include +#include #include using namespace eosio; +namespace multi_index_test { struct limit_order { uint64_t id; @@ -10,47 +13,166 @@ struct limit_order { uint64_t expiration; account_name owner; - auto primary_key()const { return id; } - uint64_t get_expiration()const { return expiration; } - uint128_t get_price()const { return price; } + auto primary_key()const { return id; } + uint64_t get_expiration()const { return expiration; } + uint128_t get_price()const { return price; } - EOSLIB_SERIALIZE( limit_order, (id)(price)(expiration)(owner) ) -}; + EOSLIB_SERIALIZE( limit_order, (id)(price)(expiration)(owner) ) + }; -extern "C" { - /// The apply method implements the dispatch of events to this contract - void apply( uint64_t code, uint64_t action ) { - eosio::multi_index >, - indexed_by > - > orders( N(exchange), N(exchange) ); + struct test_k256 { + uint64_t id; + key256 val; - auto payer = code; - const auto& order = orders.emplace( payer, [&]( auto& o ) { - o.id = 1; - o.expiration = 3; - o.owner = N(dan); - }); + auto primary_key()const { return id; } + key256 get_val()const { return val; } - orders.update( order, payer, [&]( auto& o ) { - o.expiration = 4; - }); + EOSLIB_SERIALIZE( test_k256, (id)(val) ) + }; - const auto* o = orders.find( 1 ); + class multi_index_test { + public: - orders.remove( order ); + ACTION(N(multitest), trigger) { + trigger(): what(0) {} + trigger(uint32_t w): what(w) {} - auto expidx = orders.get_index(); + uint32_t what; - for( const auto& item : orders ) { - (void)item.id; - } + EOSLIB_SERIALIZE(trigger, (what)) + }; - for( const auto& item : expidx ) { - (void)item.id; - } + static void on(const trigger& act) + { + auto payer = act.get_account(); + print("Acting on trigger action.\n"); + switch(act.what) + { + case 0: + { + print("Testing uint128_t secondary index.\n"); + eosio::multi_index >, + indexed_by< N(byprice), const_mem_fun > + > orders( N(multitest), N(multitest) ); + + const auto& order1 = orders.emplace( payer, [&]( auto& o ) { + o.id = 1; + o.expiration = 300; + o.owner = N(dan); + }); + + const auto& order2 = orders.emplace( payer, [&]( auto& o ) { + o.id = 2; + o.expiration = 200; + o.owner = N(alice); + }); + + print("Items sorted by primary key:\n"); + for( const auto& item : orders ) { + print(" ID=", item.id, ", expiration=", item.expiration, ", owner=", name(item.owner), "\n"); + } + + auto expidx = orders.get_index(); + + print("Items sorted by expiration:\n"); + for( const auto& item : expidx ) { + print(" ID=", item.id, ", expiration=", item.expiration, ", owner=", name(item.owner), "\n"); + } + + print("Updating expiration of order with ID=2 to 400.\n"); + orders.update( order2, payer, [&]( auto& o ) { + o.expiration = 400; + }); + + print("Items sorted by expiration:\n"); + for( const auto& item : expidx ) { + print(" ID=", item.id, ", expiration=", item.expiration, ", owner=", name(item.owner), "\n"); + } + + auto lower = expidx.lower_bound(100); + + print("First order with an expiration of at least 100 has ID=", lower->id, " and expiration=", lower->get_expiration(), "\n"); + + } + break; + case 1: // Test key265 secondary index + { + print("Testing key256 secondary index.\n"); + eosio::multi_index > + > testtable( N(multitest), N(exchange) ); // Code must be same as the receiver? Scope doesn't have to be. + + const auto& entry1 = testtable.emplace( payer, [&]( auto& o ) { + o.id = 1; + o.val = key256::make_from_word_sequence(0ULL, 0ULL, 0ULL, 42ULL); + }); + + const auto& entry2 = testtable.emplace( payer, [&]( auto& o ) { + o.id = 2; + o.val = key256::make_from_word_sequence(1ULL, 2ULL, 3ULL, 4ULL); + }); - auto lower = expidx.lower_bound(4); + const auto& entry3 = testtable.emplace( payer, [&]( auto& o ) { + o.id = 3; + o.val = key256::make_from_word_sequence(0ULL, 0ULL, 0ULL, 42ULL); + }); + const auto* e = testtable.find( 2 ); + + print("Items sorted by primary key:\n"); + for( const auto& item : testtable ) { + print(" ID=", item.primary_key(), ", val=", item.val, "\n"); + } + + auto validx = testtable.get_index(); + + auto lower1 = validx.lower_bound(key256::make_from_word_sequence(0ULL, 0ULL, 0ULL, 40ULL)); + print("First entry with a val of at least 40 has ID=", lower1->id, ".\n"); + + auto lower2 = validx.lower_bound(key256::make_from_word_sequence(0ULL, 0ULL, 0ULL, 50ULL)); + print("First entry with a val of at least 50 has ID=", lower2->id, ".\n"); + + if( &*lower2 == e ) { + print("Previously found entry is the same as the one found earlier with a primary key value of 2.\n"); + } + + print("Items sorted by val (secondary key):\n"); + for( const auto& item : validx ) { + print(" ID=", item.primary_key(), ", val="); + cout << item.val << "\n"; + } + + auto upper = validx.upper_bound(key256::make_from_word_sequence(0ULL, 0ULL, 0ULL, 42ULL)); + + print("First entry with a val greater than 42 has ID=", upper->id, ".\n"); + + print("Removed entry with ID=", lower1->id, ".\n"); + testtable.remove( *lower1 ); + + print("Items sorted by primary key:\n"); + for( const auto& item : testtable ) { + print(" ID=", item.primary_key(), ", val="); + cout << item.val << "\n"; + } + + } + break; + default: + eosio_assert(0, "Given what code is not supported."); + break; + } + } + }; + +} /// multi_index_test + +namespace multi_index_test { + extern "C" { + /// The apply method implements the dispatch of events to this contract + void apply( uint64_t code, uint64_t action ) { + eosio_assert(eosio::dispatch(code, action), + "Could not dispatch"); + } } } diff --git a/contracts/proxy/proxy.cpp b/contracts/proxy/proxy.cpp index 9b5e434bee3861a86545626944d5a4516cfa355e..fa4b00024210add7d3190ec4fe5954c3b4ef913c 100644 --- a/contracts/proxy/proxy.cpp +++ b/contracts/proxy/proxy.cpp @@ -11,16 +11,25 @@ namespace proxy { using namespace eosio; namespace configs { + bool get(config &out, const account_name &self) { - auto read = load_i64(self, self, N(config), (char*)&out, sizeof(config)); - if (read < 0) { + auto it = db_find_i64(self, self, N(config), config::key); + if (it != -1) { + auto size = db_get_i64(it, (char*)&out, sizeof(config)); + eosio_assert(size == sizeof(config), "Wrong record size"); + return true; + } else { return false; } - return true; } void store(const config &in, const account_name &self) { - store_i64(self, N(config), self, (const char *)&in, sizeof(config)); + auto it = db_find_i64(self, self, N(config), config::key); + if (it != -1) { + db_update_i64(it, self, (const char *)&in, sizeof(config)); + } else { + db_store_i64(self, N(config), self, config::key, (const char *)&in, sizeof(config)); + } } }; diff --git a/contracts/proxy/proxy.hpp b/contracts/proxy/proxy.hpp index 95a7e498ff6245835555c8bada0c31dadc4d6f08..f85b9ea2c57dd645589ec4b265eaa97f50902714 100644 --- a/contracts/proxy/proxy.hpp +++ b/contracts/proxy/proxy.hpp @@ -15,7 +15,7 @@ namespace proxy { //@abi table struct config { config(){} - const uint64_t key = N(config); + constexpr static uint64_t key = N(config); account_name owner = 0; uint32_t delay = 0; uint32_t next_id = 0; diff --git a/contracts/simpledb/simpledb.cpp b/contracts/simpledb/simpledb.cpp index 568e043e481d6d6e88889cea3b35220c9a985472..0441a10e04ad29836fc47c69c8b9ae384c0d0b12 100644 --- a/contracts/simpledb/simpledb.cpp +++ b/contracts/simpledb/simpledb.cpp @@ -12,7 +12,7 @@ using namespace eosio; namespace simpledb { - + template struct dispatchable { constexpr static uint64_t action_name = Val; @@ -20,22 +20,22 @@ namespace simpledb { //@abi table struct record1 { - + uint64_t key; - - uint256 u256; + + //uint256 u256; uint128_t u128; uint64_t u64; uint32_t u32; - uint16_t u16; + uint16_t u16; uint8_t u8; int64_t i64; int32_t i32; int16_t i16; int8_t i8; - - EOSLIB_SERIALIZE( record1, (key)(u256)(u128)(u64)(u32)(u16)(u8)(i64)(i32)(i16)(i8) ); + + EOSLIB_SERIALIZE( record1, (key)(u128)(u64)(u32)(u16)(u8)(i64)(i32)(i16)(i8) ); }; //@abi action insert1 @@ -223,4 +223,4 @@ extern "C" { } } -} \ No newline at end of file +} diff --git a/contracts/test_api/test_api.hpp b/contracts/test_api/test_api.hpp index edefdaee1467ca05ecc1d0b3c7c138eb36a044a8..638304ee6c07a8a5743c2f40e3f456922647f553 100644 --- a/contracts/test_api/test_api.hpp +++ b/contracts/test_api/test_api.hpp @@ -24,7 +24,7 @@ static constexpr u64 WASM_TEST_ACTION(const char* cls, const char* method) CLASS::METHOD(); \ return; \ } - + #pragma pack(push, 1) struct dummy_action { char a; //1 @@ -123,7 +123,7 @@ struct test_db { static void key_i64i64i64_under_limit(); static void key_i64i64i64_available_space_exceed_limit(); static void key_i64i64i64_another_under_limit(); - + static void primary_i64_general(); static void primary_i64_lowerbound(); static void primary_i64_upperbound(); @@ -133,6 +133,16 @@ struct test_db { static void idx64_upperbound(); }; +struct test_multi_index { + static void idx64_general(); + static void idx64_store_only(); + static void idx64_check_without_storing(); + static void idx128_autoincrement_test(); + static void idx128_autoincrement_test_part1(); + static void idx128_autoincrement_test_part2(); + static void idx256_general(); +}; + struct test_crypto { static void test_recover_key(); static void test_recover_key_assert_true(); @@ -233,4 +243,3 @@ struct test_checktime { static void checktime_pass(); static void checktime_failure(); }; - diff --git a/contracts/test_api/test_types.cpp b/contracts/test_api/test_types.cpp index 28a8ac32f8418afe1ec40cdd1d4ec7e694eaaed2..56d57f10814424bcc25b4e889cf5d95e7a50b50d 100644 --- a/contracts/test_api/test_types.cpp +++ b/contracts/test_api/test_types.cpp @@ -7,7 +7,7 @@ #include "test_api.hpp" void test_types::types_size() { - + eosio_assert( sizeof(int64_t) == 8, "int64_t size != 8"); eosio_assert( sizeof(uint64_t) == 8, "uint64_t size != 8"); eosio_assert( sizeof(uint32_t) == 4, "uint32_t size != 4"); @@ -20,11 +20,11 @@ void test_types::types_size() { eosio_assert( sizeof(token_name) == 8, "token_name size != 8"); eosio_assert( sizeof(table_name) == 8, "table_name size != 8"); eosio_assert( sizeof(time) == 4, "time size != 4"); - eosio_assert( sizeof(uint256) == 32, "uint256 != 32" ); + eosio_assert( sizeof(key256) == 32, "key256 size != 32" ); } void test_types::char_to_symbol() { - + eosio_assert( eosio::char_to_symbol('1') == 1, "eosio::char_to_symbol('1') != 1"); eosio_assert( eosio::char_to_symbol('2') == 2, "eosio::char_to_symbol('2') != 2"); eosio_assert( eosio::char_to_symbol('3') == 3, "eosio::char_to_symbol('3') != 3"); @@ -56,7 +56,7 @@ void test_types::char_to_symbol() { eosio_assert( eosio::char_to_symbol('x') == 29, "eosio::char_to_symbol('x') != 29"); eosio_assert( eosio::char_to_symbol('y') == 30, "eosio::char_to_symbol('y') != 30"); eosio_assert( eosio::char_to_symbol('z') == 31, "eosio::char_to_symbol('z') != 31"); - + for(unsigned char i = 0; i<255; i++) { if((i >= 'a' && i <= 'z') || (i >= '1' || i <= '5')) continue; eosio_assert( eosio::char_to_symbol(i) == 0, "eosio::char_to_symbol() != 0"); diff --git a/contracts/test_api_db/test_db.cpp b/contracts/test_api_db/test_db.cpp index 29fce5f243ada5102ef8cf6969fc78b62bf19f64..4234500218b007a5ea6f75ec97aa58e9922f1654 100644 --- a/contracts/test_api_db/test_db.cpp +++ b/contracts/test_api_db/test_db.cpp @@ -89,7 +89,7 @@ void test_db::key_str_table() { const char* atr[] = { "atr", "atr", "atr", "atr" }; const char* ztr[] = { "ztr", "ztr", "ztr", "ztr" }; - + eosio::var_table StringTableAtr; eosio::var_table StringTableZtr; eosio::var_table StringTableStr; @@ -100,13 +100,13 @@ void test_db::key_str_table() { for( int ii = 0; ii < 4; ++ii ) { res = StringTableAtr.store( (char*)keys[ii], STRLEN(keys[ii]), (char*)atr[ii], STRLEN(atr[ii]) ); eosio_assert( res != 0, "atr" ); - + res = StringTableZtr.store( (char*)keys[ii], STRLEN(keys[ii]), (char*)ztr[ii], STRLEN(ztr[ii]) ); eosio_assert(res != 0, "ztr" ); } char tmp[64]; - + res = StringTableStr.store ((char *)keys[0], STRLEN(keys[0]), (char *)vals[0], STRLEN(vals[0])); eosio_assert(res != 0, "store alice" ); @@ -307,7 +307,7 @@ void test_db::key_i64_general() { res = previous_i64( current_receiver(), current_receiver(), N(test_table), &tmp, sizeof(test_model) ); eosio_assert(res == sizeof(test_model) && tmp.name == N(carol) && tmp.age == 30 && tmp.phone == 545342453, "carol previous"); - + res = previous_i64( current_receiver(), current_receiver(), N(test_table), &tmp, sizeof(test_model) ); eosio_assert(res == sizeof(test_model) && tmp.name == N(bob) && tmp.age == 15 && tmp.phone == 11932435, "bob previous"); @@ -340,13 +340,13 @@ void test_db::key_i64_general() { alice.age = 21; alice.phone = 1234; - + res = store_i64(current_receiver(), N(test_table), &alice, sizeof(test_model)); eosio_assert(res == 0, "store alice 2" ); my_memset(&alice, 0, sizeof(test_model)); alice.name = N(alice); - + res = load_i64(current_receiver(), current_receiver(), N(test_table), &alice, sizeof(test_model)); eosio_assert(res == sizeof(test_model) && alice.age == 21 && alice.phone == 1234, "alice error 2"); @@ -426,7 +426,7 @@ void test_db::key_i64_general() { res = load_i64(current_receiver(), current_receiver(), N(test_table), &tmp2, sizeof(test_model_v3)); eosio_assert(res == sizeof(test_model_v2) && - tmp2.age == 21 && + tmp2.age == 21 && tmp2.phone == 1234 && tmp2.new_field == 66655444, "load4update"); @@ -437,7 +437,7 @@ void test_db::key_i64_general() { res = load_i64(current_receiver(), current_receiver(), N(test_table), &tmp2, sizeof(test_model_v3)); eosio_assert(res == sizeof(test_model_v3) && - tmp2.age == 21 && + tmp2.age == 21 && tmp2.phone == 1234 && tmp2.new_field == 66655444 && tmp2.another_field == 221122, @@ -449,7 +449,7 @@ void test_db::key_i64_general() { res = load_i64(current_receiver(), current_receiver(), N(test_table), &tmp2, sizeof(test_model_v3)); eosio_assert(res == sizeof(test_model_v3) && - tmp2.age == 11 && + tmp2.age == 11 && tmp2.phone == 1234 && tmp2.new_field == 66655444 && tmp2.another_field == 221122, @@ -466,22 +466,22 @@ void test_db::key_i64_general() { } void test_db::key_i64_remove_all() { - + uint32_t res = 0; uint64_t key; key = N(alice); res = remove_i64(current_receiver(), N(test_table), &key); eosio_assert(res == 1, "remove alice"); - + key = N(bob); res = remove_i64(current_receiver(), N(test_table), &key); eosio_assert(res == 1, "remove bob"); - + key = N(carol); res = remove_i64(current_receiver(), N(test_table), &key); eosio_assert(res == 1, "remove carol"); - + key = N(dave); res = remove_i64(current_receiver(), N(test_table), &key); eosio_assert(res == 1, "remove dave"); @@ -492,19 +492,19 @@ void test_db::key_i64_remove_all() { res = back_i64( current_receiver(), current_receiver(), N(test_table), &tmp, sizeof(test_model) ); eosio_assert(res == 0, "back_i64_i64 remove"); - + key = N(alice); res = remove_i64(current_receiver(), N(test_table), &key); eosio_assert(res == 0, "remove alice 1"); - + key = N(bob); res = remove_i64(current_receiver(), N(test_table), &key); eosio_assert(res == 0, "remove bob 1"); - + key = N(carol); res = remove_i64(current_receiver(), N(test_table), &key); eosio_assert(res == 0, "remove carol 1"); - + key = N(dave); res = remove_i64(current_receiver(), N(test_table), &key); eosio_assert(res == 0, "remove dave 1"); @@ -546,7 +546,7 @@ void test_db::key_i64_not_found() { } void test_db::key_i64_front_back() { - + uint32_t res = 0; test_model dave { N(dave), 46, 6535354}; @@ -598,7 +598,7 @@ void test_db::key_i64_front_back() { key = N(dave); remove_i64(current_receiver(), N(b), &key); - + res = front_i64( current_receiver(), current_receiver(), N(b), &tmp, sizeof(test_model) ); eosio_assert(res == 0, "key_i64_front 9"); res = back_i64( current_receiver(), current_receiver(), N(b), &tmp, sizeof(test_model) ); @@ -628,7 +628,7 @@ uint32_t store_set_in_table(uint64_t table_name) { uint32_t res = 0; - + TestModel128x2 alice0{0, 500, N(alice0), table_name}; TestModel128x2 alice1{1, 400, N(alice1), table_name}; TestModel128x2 alice2{2, 300, N(alice2), table_name}; @@ -714,7 +714,7 @@ void store_set_in_table(TestModel3xi64* records, int len, uint64_t table_name) { //TODO fix things #if 0 void test_db::key_i64i64i64_general() { - + uint32_t res = 0; TestModel3xi64 records[] = { @@ -784,7 +784,7 @@ void test_db::key_i64i64i64_general() { V={4}; LOAD_OK(primary, i64i64i64, N(table2), 7, "i64x3 LOAD primary 4"); V={5}; LOAD_OK(primary, i64i64i64, N(table2), 9, "i64x3 LOAD primary 5"); V={6}; LOAD_ER(primary, i64i64i64, N(table2), "i64x3 LOAD primary fail 6"); - + V={11,0}; LOAD_OK(secondary, i64i64i64, N(table2), 7, "i64x3 LOAD secondary 0"); V={11,1}; LOAD_OK(secondary, i64i64i64, N(table2), 0, "i64x3 LOAD secondary 1"); V={11,2}; LOAD_OK(secondary, i64i64i64, N(table2),10, "i64x3 LOAD secondary 2"); @@ -886,7 +886,7 @@ void test_db::key_i64i64i64_general() { v2.new_field = 555; res = update_i64i64i64(current_receiver(), N(table2), &v2, sizeof(TestModel3xi64_V2)); - eosio_assert(res == 1, "store v2"); + eosio_assert(res == 1, "store v2"); res = LOAD(primary, i64i64i64, N(table2), v2); eosio_assert(res == sizeof(TestModel3xi64_V2), "load v2 updated"); @@ -899,7 +899,7 @@ void test_db::key_i64i64i64_general() { return 0; } -#endif +#endif void test_db::key_i128i128_general() { uint32_t res = 0; @@ -928,7 +928,7 @@ return; my_memset(&tmp, 0, sizeof(TestModel128x2)); tmp.price = 4; - + res = load_secondary_i128i128( current_receiver(), current_receiver(), N(table5), &tmp, sizeof(TestModel128x2) ); eosio_assert( res == sizeof(TestModel128x2) && tmp.number == 13 && @@ -944,7 +944,7 @@ return; tmp.extra == N(alice0) && tmp.table_name == N(table5), "front primary load"); - + res = previous_primary_i128i128( current_receiver(), current_receiver(), N(table5), &tmp, sizeof(TestModel128x2) ); eosio_assert(res == -1, "previous primary fail"); @@ -963,7 +963,7 @@ return; tmp.extra == N(bob0) && tmp.table_name == N(table5), "front secondary ok"); - + res = previous_secondary_i128i128( current_receiver(), current_receiver(), N(table5), &tmp, sizeof(TestModel128x2) ); eosio_assert(res == -1, "previous secondary fail"); @@ -982,7 +982,7 @@ return; tmp.extra == N(dave3) && tmp.table_name == N(table5), "back primary ok"); - + res = next_primary_i128i128( current_receiver(), current_receiver(), N(table5), &tmp, sizeof(TestModel128x2) ); eosio_assert(res == -1, "next primary fail"); @@ -1001,12 +1001,12 @@ return; tmp.extra == N(carol0) && tmp.table_name == N(table5), "back secondary ok"); - + res = next_secondary_i128i128( current_receiver(), current_receiver(), N(table5), &tmp, sizeof(TestModel128x2) ); eosio_assert(res == -1, "next secondary fail"); res = previous_secondary_i128i128( current_receiver(), current_receiver(), N(table5), &tmp, sizeof(TestModel128x2) ); - + eosio_assert( res == sizeof(TestModel128x2) && tmp.number == 21 && tmp.price == 800 && @@ -1052,7 +1052,7 @@ return; tmp2.extra == N(carol0) && tmp2.table_name == N(table5), "ub secondary ok"); - + tmp2.new_field = 123456; res = update_i128i128(current_receiver(), N(table5), &tmp2, sizeof(TestModel128x2_V2)); eosio_assert( res == 1, "update_i128i128 ok"); @@ -1526,7 +1526,7 @@ void test_db::primary_i64_general() // nothing after charlie uint64_t prim = 0; int end_itr = db_next_i64(charlie_itr, &prim); - eosio_assert(end_itr == -1, "primary_i64_general - db_next_i64"); + eosio_assert(end_itr < 0, "primary_i64_general - db_next_i64"); // prim didn't change eosio_assert(prim == 0, "primary_i64_general - db_next_i64"); } @@ -1548,23 +1548,22 @@ void test_db::primary_i64_general() eosio_assert(itr_prev == itr_prev_expected && prim == N(alice), "primary_i64_general - db_previous_i64"); itr_prev = db_previous_i64(itr_prev, &prim); - itr_prev_expected = -1; - eosio_assert(itr_prev == itr_prev_expected && prim == N(alice), "primary_i64_general - db_previous_i64"); + eosio_assert(itr_prev < 0 && prim == N(alice), "primary_i64_general - db_previous_i64"); } // remove { int itr = db_find_i64(current_receiver(), current_receiver(), table1, N(alice)); - eosio_assert(itr != -1, "primary_i64_general - db_find_i64"); + eosio_assert(itr >= 0, "primary_i64_general - db_find_i64"); db_remove_i64(itr); itr = db_find_i64(current_receiver(), current_receiver(), table1, N(alice)); - eosio_assert(itr == -1, "primary_i64_general - db_find_i64"); + eosio_assert(itr < 0, "primary_i64_general - db_find_i64"); } // get { int itr = db_find_i64(current_receiver(), current_receiver(), table1, N(bob)); - eosio_assert(itr != -1, ""); + eosio_assert(itr >= 0, ""); int buffer_len = 5; char value[50]; auto len = db_get_i64(itr, value, buffer_len); @@ -1572,7 +1571,7 @@ void test_db::primary_i64_general() std::string s(value); eosio_assert(len == strlen("bob's info"), "primary_i64_general - db_get_i64"); eosio_assert(s == "bob's", "primary_i64_general - db_get_i64"); - + buffer_len = 20; db_get_i64(itr, value, buffer_len); value[buffer_len] = '\0'; @@ -1583,9 +1582,9 @@ void test_db::primary_i64_general() // update { int itr = db_find_i64(current_receiver(), current_receiver(), table1, N(bob)); - eosio_assert(itr != -1, ""); + eosio_assert(itr >= 0, ""); const char* new_value = "bob's new info"; - int new_value_len = strlen(new_value); + int new_value_len = strlen(new_value); db_update_i64(itr, current_receiver(), new_value, new_value_len); char ret_value[50]; auto len = db_get_i64(itr, ret_value, new_value_len); @@ -1625,7 +1624,7 @@ void test_db::primary_i64_lowerbound() } { int lb = db_lowerbound_i64(current_receiver(), current_receiver(), table, N(kevin)); - eosio_assert(lb == -1, err.c_str()); + eosio_assert(lb < 0, err.c_str()); } } @@ -1647,11 +1646,11 @@ void test_db::primary_i64_upperbound() } { int ub = db_upperbound_i64(current_receiver(), current_receiver(), table, N(joe)); - eosio_assert(ub == -1, err.c_str()); + eosio_assert(ub < 0, err.c_str()); } { int ub = db_upperbound_i64(current_receiver(), current_receiver(), table, N(kevin)); - eosio_assert(ub == -1, err.c_str()); + eosio_assert(ub < 0, err.c_str()); } } @@ -1683,62 +1682,62 @@ void test_db::idx64_general() { secondary_type sec = 0; int itr = db_idx64_find_primary(current_receiver(), current_receiver(), table, &sec, 999); - eosio_assert(itr == -1 && sec == 0, "idx64_general - db_idx64_find_primary"); + eosio_assert(itr < 0 && sec == 0, "idx64_general - db_idx64_find_primary"); itr = db_idx64_find_primary(current_receiver(), current_receiver(), table, &sec, 110); - eosio_assert(itr != -1 && sec == N(joe), "idx64_general - db_idx64_find_primary"); + eosio_assert(itr >= 0 && sec == N(joe), "idx64_general - db_idx64_find_primary"); uint64_t prim_next = 0; int itr_next = db_idx64_next(itr, &prim_next); - eosio_assert(itr_next == -1 && prim_next == 0, "idx64_general - db_idx64_find_primary"); + eosio_assert(itr_next < 0 && prim_next == 0, "idx64_general - db_idx64_find_primary"); } // iterate forward starting with charlie { secondary_type sec = 0; int itr = db_idx64_find_primary(current_receiver(), current_receiver(), table, &sec, 234); - eosio_assert(itr != -1 && sec == N(charlie), "idx64_general - db_idx64_find_primary"); + eosio_assert(itr >= 0 && sec == N(charlie), "idx64_general - db_idx64_find_primary"); uint64_t prim_next = 0; int itr_next = db_idx64_next(itr, &prim_next); - eosio_assert(itr_next != -1 && prim_next == 976, "idx64_general - db_idx64_next"); + eosio_assert(itr_next >= 0 && prim_next == 976, "idx64_general - db_idx64_next"); secondary_type sec_next = 0; int itr_next_expected = db_idx64_find_primary(current_receiver(), current_receiver(), table, &sec_next, prim_next); eosio_assert(itr_next == itr_next_expected && sec_next == N(emily), "idx64_general - db_idx64_next"); itr_next = db_idx64_next(itr_next, &prim_next); - eosio_assert(itr_next != -1 && prim_next == 110, "idx64_general - db_idx64_next"); + eosio_assert(itr_next >= 0 && prim_next == 110, "idx64_general - db_idx64_next"); itr_next_expected = db_idx64_find_primary(current_receiver(), current_receiver(), table, &sec_next, prim_next); eosio_assert(itr_next == itr_next_expected && sec_next == N(joe), "idx64_general - db_idx64_next"); itr_next = db_idx64_next(itr_next, &prim_next); - eosio_assert(itr_next == -1 && prim_next == 110, "idx64_general - db_idx64_next"); + eosio_assert(itr_next < 0 && prim_next == 110, "idx64_general - db_idx64_next"); } // iterate backward staring with second bob { secondary_type sec = 0; int itr = db_idx64_find_primary(current_receiver(), current_receiver(), table, &sec, 781); - eosio_assert(itr != -1 && sec == N(bob), "idx64_general - db_idx64_find_primary"); + eosio_assert(itr >= 0 && sec == N(bob), "idx64_general - db_idx64_find_primary"); uint64_t prim_prev = 0; int itr_prev = db_idx64_previous(itr, &prim_prev); - eosio_assert(itr_prev != -1 && prim_prev == 540, "idx64_general - db_idx64_previous"); + eosio_assert(itr_prev >= 0 && prim_prev == 540, "idx64_general - db_idx64_previous"); secondary_type sec_prev = 0; int itr_prev_expected = db_idx64_find_primary(current_receiver(), current_receiver(), table, &sec_prev, prim_prev); eosio_assert(itr_prev == itr_prev_expected && sec_prev == N(bob), "idx64_general - db_idx64_previous"); itr_prev = db_idx64_previous(itr_prev, &prim_prev); - eosio_assert(itr_prev != -1 && prim_prev == 650, "idx64_general - db_idx64_previous"); + eosio_assert(itr_prev >= 0 && prim_prev == 650, "idx64_general - db_idx64_previous"); itr_prev_expected = db_idx64_find_primary(current_receiver(), current_receiver(), table, &sec_prev, prim_prev); eosio_assert(itr_prev == itr_prev_expected && sec_prev == N(allyson), "idx64_general - db_idx64_previous"); itr_prev = db_idx64_previous(itr_prev, &prim_prev); - eosio_assert(itr_prev != -1 && prim_prev == 265, "idx64_general - db_idx64_previous"); + eosio_assert(itr_prev >= 0 && prim_prev == 265, "idx64_general - db_idx64_previous"); itr_prev_expected = db_idx64_find_primary(current_receiver(), current_receiver(), table, &sec_prev, prim_prev); eosio_assert(itr_prev == itr_prev_expected && sec_prev == N(alice), "idx64_general - db_idx64_previous"); itr_prev = db_idx64_previous(itr_prev, &prim_prev); - eosio_assert(itr_prev == -1 && prim_prev == 265, "idx64_general - db_idx64_previous"); + eosio_assert(itr_prev < 0 && prim_prev == 265, "idx64_general - db_idx64_previous"); } // find_secondary @@ -1746,15 +1745,15 @@ void test_db::idx64_general() uint64_t prim = 0; auto sec = N(bob); int itr = db_idx64_find_secondary(current_receiver(), current_receiver(), table, &sec, &prim); - eosio_assert(itr != -1 && prim == 540, "idx64_general - db_idx64_find_secondary"); + eosio_assert(itr >= 0 && prim == 540, "idx64_general - db_idx64_find_secondary"); sec = N(emily); itr = db_idx64_find_secondary(current_receiver(), current_receiver(), table, &sec, &prim); - eosio_assert(itr != -1 && prim == 976, "idx64_general - db_idx64_find_secondary"); + eosio_assert(itr >= 0 && prim == 976, "idx64_general - db_idx64_find_secondary"); sec = N(frank); itr = db_idx64_find_secondary(current_receiver(), current_receiver(), table, &sec, &prim); - eosio_assert(itr == -1 && prim == 976, "idx64_general - db_idx64_find_secondary"); + eosio_assert(itr < 0 && prim == 976, "idx64_general - db_idx64_find_secondary"); } // update and remove @@ -1769,7 +1768,7 @@ void test_db::idx64_general() eosio_assert(sec_itr == itr && sec == new_name, "idx64_general - db_idx64_update"); db_idx64_remove(itr); int itrf = db_idx64_find_primary(current_receiver(), current_receiver(), table, &sec, ssn); - eosio_assert(itrf == -1, "idx64_general - db_idx64_remove"); + eosio_assert(itrf < 0, "idx64_general - db_idx64_remove"); } } @@ -1807,7 +1806,7 @@ void test_db::idx64_lowerbound() uint64_t lb_prim = 0; int lb = db_idx64_lowerbound(current_receiver(), current_receiver(), table, &lb_sec, &lb_prim); eosio_assert(lb_prim == 0 && lb_sec == N(kevin), err.c_str()); - eosio_assert(lb == -1, ""); + eosio_assert(lb < 0, ""); } } @@ -1838,14 +1837,13 @@ void test_db::idx64_upperbound() const uint64_t ssn = 110; int ub = db_idx64_upperbound(current_receiver(), current_receiver(), table, &ub_sec, &ub_prim); eosio_assert(ub_prim == 0 && ub_sec == N(joe), err.c_str()); - eosio_assert(ub == -1, err.c_str()); + eosio_assert(ub < 0, err.c_str()); } { secondary_type ub_sec = N(kevin); uint64_t ub_prim = 0; int ub = db_idx64_upperbound(current_receiver(), current_receiver(), table, &ub_sec, &ub_prim); eosio_assert(ub_prim == 0 && ub_sec == N(kevin), err.c_str()); - eosio_assert(ub == -1, err.c_str()); + eosio_assert(ub < 0, err.c_str()); } } - diff --git a/contracts/test_api_multi_index/CMakeLists.txt b/contracts/test_api_multi_index/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..b6906b0fb34c1ceb2982e75a7dc05d561951ce72 --- /dev/null +++ b/contracts/test_api_multi_index/CMakeLists.txt @@ -0,0 +1,5 @@ +add_wast_executable(TARGET test_api_multi_index + INCLUDE_FOLDERS "${STANDARD_INCLUDE_FOLDERS}" + LIBRARIES libc++ libc eosiolib + DESTINATION_FOLDER ${CMAKE_CURRENT_BINARY_DIR} +) diff --git a/contracts/test_api_multi_index/test_api_multi_index.cpp b/contracts/test_api_multi_index/test_api_multi_index.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4d2a37a8e1b2433cc9e676bed4bcdcd9851726e0 --- /dev/null +++ b/contracts/test_api_multi_index/test_api_multi_index.cpp @@ -0,0 +1,30 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#include +#include "../test_api/test_api.hpp" + +#include "test_multi_index.cpp" + +extern "C" { + + void init() { + + } + + void apply( unsigned long long code, unsigned long long action ) { + + WASM_TEST_HANDLER(test_multi_index, idx64_general); + WASM_TEST_HANDLER(test_multi_index, idx64_store_only); + WASM_TEST_HANDLER(test_multi_index, idx64_check_without_storing); + WASM_TEST_HANDLER(test_multi_index, idx128_autoincrement_test); + WASM_TEST_HANDLER(test_multi_index, idx128_autoincrement_test_part1); + WASM_TEST_HANDLER(test_multi_index, idx128_autoincrement_test_part2); + WASM_TEST_HANDLER(test_multi_index, idx256_general); + + //unhandled test call + eosio_assert(false, "Unknown Test"); + } + +} diff --git a/contracts/test_api_multi_index/test_multi_index.cpp b/contracts/test_api_multi_index/test_multi_index.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b044288107b673e470297e2a1942a741ea7adb70 --- /dev/null +++ b/contracts/test_api_multi_index/test_multi_index.cpp @@ -0,0 +1,403 @@ +#include +#include "../test_api/test_api.hpp" +#include + +namespace _test_multi_index { + + using eosio::key256; + + struct record_idx64 { + uint64_t id; + uint64_t sec; + + auto primary_key()const { return id; } + uint64_t get_secondary()const { return sec; } + + EOSLIB_SERIALIZE( record_idx64, (id)(sec) ) + }; + + struct record_idx128 { + uint64_t id; + uint128_t sec; + + auto primary_key()const { return id; } + uint128_t get_secondary()const { return sec; } + + EOSLIB_SERIALIZE( record_idx128, (id)(sec) ) + }; + + struct record_idx256 { + uint64_t id; + key256 sec; + + auto primary_key()const { return id; } + const key256& get_secondary()const { return sec; } + + EOSLIB_SERIALIZE( record_idx256, (id)(sec) ) + }; + + template + void idx64_store_only() + { + using namespace eosio; + + typedef record_idx64 record; + + record records[] = {{265, N(alice)}, + {781, N(bob)}, + {234, N(charlie)}, + {650, N(allyson)}, + {540, N(bob)}, + {976, N(emily)}, + {110, N(joe)} + }; + size_t num_records = sizeof(records)/sizeof(records[0]); + + // Construct and fill table using multi_index + multi_index > + > table( current_receiver(), current_receiver() ); + + auto payer = current_receiver(); + + for (size_t i = 0; i < num_records; ++i) { + table.emplace( payer, [&]( auto& r ) { + r.id = records[i].id; + r.sec = records[i].sec; + }); + } + } + + template + void idx64_check_without_storing() + { + using namespace eosio; + + typedef record_idx64 record; + + // Load table using multi_index + multi_index > + > table( current_receiver(), current_receiver() ); + + auto payer = current_receiver(); + + auto secondary_index = table.template get_index(); + + // find by primary key + { + auto ptr = table.find(999); + eosio_assert(ptr == nullptr, "idx64_general - table.find() of non-existing primary key"); + + ptr = table.find(976); + eosio_assert(ptr != nullptr && ptr->sec == N(emily), "idx64_general - table.find() of existing primary key"); + + // Workaround: would prefer to instead receive iterator (rather than pointer) from find(). + auto itr = table.lower_bound(976); + eosio_assert(itr != table.end() && itr->id == 976 && itr->sec == N(emily), "idx64_general - iterator to existing object in primary index"); + + ++itr; + eosio_assert(itr == table.end(), "idx64_general - increment primary iterator to end"); + } + + // iterate forward starting with charlie + { + auto itr = secondary_index.lower_bound(N(charlie)); + eosio_assert(itr != secondary_index.end() && itr->sec == N(charlie), "idx64_general - secondary_index.lower_bound()"); + + ++itr; + eosio_assert(itr != secondary_index.end() && itr->id == 976 && itr->sec == N(emily), "idx64_general - increment secondary iterator"); + + ++itr; + eosio_assert(itr != secondary_index.end() && itr->id == 110 && itr->sec == N(joe), "idx64_general - increment secondary iterator again"); + + ++itr; + eosio_assert(itr == secondary_index.end(), "idx64_general - increment secondary iterator to end"); + } + + // iterate backward staring with second bob + { + auto ptr = table.find(781); + eosio_assert(ptr != nullptr && ptr->sec == N(bob), "idx64_general - table.find() of existing primary key"); + + // Workaround: need to add find_primary wrapper support in secondary indices of multi_index + auto itr = secondary_index.upper_bound(ptr->sec); --itr; + eosio_assert(itr->id == 781 && itr->sec == N(bob), "idx64_general - iterator to existing object in secondary index"); + + --itr; + eosio_assert(itr != secondary_index.end() && itr->id == 540 && itr->sec == N(bob), "idx64_general - decrement secondary iterator"); + + --itr; + eosio_assert(itr != secondary_index.end() && itr->id == 650 && itr->sec == N(allyson), "idx64_general - decrement secondary iterator again"); + + --itr; + eosio_assert(itr == secondary_index.begin() && itr->id == 265 && itr->sec == N(alice), "idx64_general - decrement secondary iterator to beginning"); + } + + // update and remove + { + const uint64_t ssn = 421; + const auto& new_person = table.emplace( payer, [&]( auto& r ) { + r.id = ssn; + r.sec = N(bob); + }); + + table.update(new_person, payer, [&]( auto& r ) { + r.sec = N(billy); + }); + + auto ptr = table.find(ssn); + eosio_assert(ptr != nullptr && ptr->sec == N(billy), "idx64_general - table.update()"); + + table.remove(*ptr); + auto ptr2 = table.find(ssn); + eosio_assert( ptr2 == nullptr, "idx64_general - table.remove()"); + } + } + +} /// _test_multi_index + +void test_multi_index::idx64_store_only() +{ + _test_multi_index::idx64_store_only(); +} + +void test_multi_index::idx64_check_without_storing() +{ + _test_multi_index::idx64_check_without_storing(); +} + +void test_multi_index::idx64_general() +{ + _test_multi_index::idx64_store_only(); + _test_multi_index::idx64_check_without_storing(); +} + +void test_multi_index::idx128_autoincrement_test() +{ + using namespace eosio; + using namespace _test_multi_index; + + typedef record_idx128 record; + + const uint64_t table_name = N(indextable3); + auto payer = current_receiver(); + + multi_index > + > table( current_receiver(), current_receiver() ); + + for( int i = 0; i < 5; ++i ) { + table.emplace( payer, [&]( auto& r ) { + r.id = table.available_primary_key(); + r.sec = 1000 - static_cast(r.id); + }); + } + + uint64_t expected_key = 4; + for( const auto& r : table.get_index() ) + { + eosio_assert( r.primary_key() == expected_key, "idx128_autoincrement_test - unexpected primary key" ); + --expected_key; + } + + auto ptr = table.find(3); + eosio_assert( ptr != nullptr, "idx128_autoincrement_test - could not find object with primary key of 3" ); + + table.update(*ptr, payer, [&]( auto& r ) { + r.id = 100; + }); + + eosio_assert( table.available_primary_key() == 101, "idx128_autoincrement_test - next_primary_key was not correct after record update" ); +} + +void test_multi_index::idx128_autoincrement_test_part1() +{ + using namespace eosio; + using namespace _test_multi_index; + + typedef record_idx128 record; + + const uint64_t table_name = N(indextable4); + auto payer = current_receiver(); + + multi_index > + > table( current_receiver(), current_receiver() ); + + for( int i = 0; i < 3; ++i ) { + table.emplace( payer, [&]( auto& r ) { + r.id = table.available_primary_key(); + r.sec = 1000 - static_cast(r.id); + }); + } + + table.remove(table.get(0)); + + uint64_t expected_key = 2; + for( const auto& r : table.get_index() ) + { + eosio_assert( r.primary_key() == expected_key, "idx128_autoincrement_test_part1 - unexpected primary key" ); + --expected_key; + } + +} + +void test_multi_index::idx128_autoincrement_test_part2() +{ + using namespace eosio; + using namespace _test_multi_index; + + typedef record_idx128 record; + + const uint64_t table_name = N(indextable4); + auto payer = current_receiver(); + + { + multi_index > + > table( current_receiver(), current_receiver() ); + + eosio_assert( table.available_primary_key() == 3, "idx128_autoincrement_test_part2 - did not recover expected next primary key"); + } + + multi_index > + > table( current_receiver(), current_receiver() ); + + table.emplace( payer, [&]( auto& r) { + r.id = 0; + r.sec = 1000; + }); + // Done this way to make sure that table._next_primary_key is not incorrectly set to 1. + + for( int i = 3; i < 5; ++i ) { + table.emplace( payer, [&]( auto& r ) { + auto itr = table.available_primary_key(); + print(itr, "\n"); + r.id = itr; + r.sec = 1000 - static_cast(r.id); + }); + } + + uint64_t expected_key = 4; + for( const auto& r : table.get_index() ) + { + eosio_assert( r.primary_key() == expected_key, "idx128_autoincrement_test_part2 - unexpected primary key" ); + --expected_key; + } + + auto ptr = table.find(3); + eosio_assert( ptr != nullptr, "idx128_autoincrement_test_part2 - could not find object with primary key of 3" ); + + table.update(*ptr, payer, [&]( auto& r ) { + r.id = 100; + }); + + eosio_assert( table.available_primary_key() == 101, "idx128_autoincrement_test_part2 - next_primary_key was not correct after record update" ); +} + +void test_multi_index::idx256_general() +{ + using namespace eosio; + using namespace _test_multi_index; + + typedef record_idx256 record; + + const uint64_t table_name = N(indextable5); + auto payer = current_receiver(); + + print("Testing key256 secondary index.\n"); + multi_index > + > table( current_receiver(), current_receiver() ); + + auto fourtytwo = key256::make_from_word_sequence(0ULL, 0ULL, 0ULL, 42ULL); + auto onetwothreefour = key256::make_from_word_sequence(1ULL, 2ULL, 3ULL, 4ULL); + + const auto& entry1 = table.emplace( payer, [&]( auto& o ) { + o.id = 1; + o.sec = fourtytwo; + }); + + const auto& entry2 = table.emplace( payer, [&]( auto& o ) { + o.id = 2; + o.sec = onetwothreefour; + }); + + const auto& entry3 = table.emplace( payer, [&]( auto& o ) { + o.id = 3; + o.sec = fourtytwo; + }); + + const auto* e = table.find( 2 ); + + print("Items sorted by primary key:\n"); + for( const auto& item : table ) { + print(" ID=", item.primary_key(), ", secondary=", item.sec, "\n"); + } + + { + auto itr = table.begin(); + eosio_assert( itr->primary_key() == 1 && itr->get_secondary() == fourtytwo, "idx256_general - primary key sort" ); + ++itr; + eosio_assert( itr->primary_key() == 2 && itr->get_secondary() == onetwothreefour, "idx256_general - primary key sort" ); + ++itr; + eosio_assert( itr->primary_key() == 3 && itr->get_secondary() == fourtytwo, "idx256_general - primary key sort" ); + ++itr; + eosio_assert( itr == table.end(), "idx256_general - primary key sort" ); + } + + auto secidx = table.get_index(); + + auto lower1 = secidx.lower_bound(key256::make_from_word_sequence(0ULL, 0ULL, 0ULL, 40ULL)); + print("First entry with a secondary key of at least 40 has ID=", lower1->id, ".\n"); + eosio_assert( lower1->id == 1, "idx256_general - lower_bound" ); + + auto lower2 = secidx.lower_bound(key256::make_from_word_sequence(0ULL, 0ULL, 0ULL, 50ULL)); + print("First entry with a secondary key of at least 50 has ID=", lower2->id, ".\n"); + eosio_assert( lower2->id == 2, "idx256_general - lower_bound" ); + + if( &*lower2 == e ) { + print("Previously found entry is the same as the one found earlier with a primary key value of 2.\n"); + } + + print("Items sorted by secondary key (key256):\n"); + for( const auto& item : secidx ) { + print(" ID=", item.primary_key(), ", secondary="); + cout << item.sec << "\n"; + } + + { + auto itr = secidx.begin(); + eosio_assert( itr->primary_key() == 1, "idx256_general - secondary key sort" ); + ++itr; + eosio_assert( itr->primary_key() == 3, "idx256_general - secondary key sort" ); + ++itr; + eosio_assert( itr->primary_key() == 2, "idx256_general - secondary key sort" ); + ++itr; + eosio_assert( itr == secidx.end(), "idx256_general - secondary key sort" ); + } + + auto upper = secidx.upper_bound(key256::make_from_word_sequence(0ULL, 0ULL, 0ULL, 42ULL)); + + print("First entry with a secondary key greater than 42 has ID=", upper->id, ".\n"); + eosio_assert( upper->id == 2, "idx256_general - upper_bound" ); + + print("Removed entry with ID=", lower1->id, ".\n"); + table.remove( *lower1 ); + + print("Items sorted by primary key:\n"); + for( const auto& item : table ) { + print(" ID=", item.primary_key(), ", secondary=", item.sec, "\n"); + } + + { + auto itr = table.begin(); + eosio_assert( itr->primary_key() == 2 && itr->get_secondary() == onetwothreefour, "idx256_general - primary key sort after remove" ); + ++itr; + eosio_assert( itr->primary_key() == 3 && itr->get_secondary() == fourtytwo, "idx256_general - primary key sort after remove" ); + ++itr; + eosio_assert( itr == table.end(), "idx256_general - primary key sort after remove" ); + } +} diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index 3d31e2363e66096fc7d31ddffaafb079f86d1e69..9a9707ed06c4db9cb9883efc74b9319e10601eb0 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -118,13 +118,13 @@ void apply_context::require_authorization( const account_name& account )const { wdump((act)); EOS_ASSERT( false, tx_missing_auth, "missing authority of ${account}", ("account",account)); } -void apply_context::require_authorization(const account_name& account, +void apply_context::require_authorization(const account_name& account, const permission_name& permission)const { for( const auto& auth : act.authorization ) if( auth.actor == account ) { if( auth.permission == permission ) return; } - EOS_ASSERT( false, tx_missing_auth, "missing authority of ${account}/${permission}", + EOS_ASSERT( false, tx_missing_auth, "missing authority of ${account}/${permission}", ("account",account)("permission",permission) ); } @@ -162,7 +162,7 @@ void apply_context::require_read_lock(const account_name& account, const scope_n bool apply_context::has_recipient( account_name code )const { for( auto a : _notified ) - if( a == code ) + if( a == code ) return true; return false; } @@ -304,17 +304,17 @@ void apply_context::update_db_usage( const account_name& payer, int64_t delta ) } -int apply_context::get_action( uint32_t type, uint32_t index, char* buffer, size_t buffer_size )const +int apply_context::get_action( uint32_t type, uint32_t index, char* buffer, size_t buffer_size )const { const transaction& trx = trx_meta.trx(); const action* act = nullptr; if( type == 0 ) { - if( index >= trx.context_free_actions.size() ) + if( index >= trx.context_free_actions.size() ) return -1; act = &trx.context_free_actions[index]; } else if( type == 1 ) { - if( index >= trx.actions.size() ) + if( index >= trx.actions.size() ) return -1; act = &trx.actions[index]; } @@ -336,7 +336,7 @@ int apply_context::get_context_free_data( uint32_t index, char* buffer, size_t b if( buffer_size < s ) memcpy( buffer, trx_meta.context_free_data.data(), buffer_size ); - else + else memcpy( buffer, trx_meta.context_free_data.data(), s ); return s; @@ -379,7 +379,7 @@ void apply_context::db_update_i64( int iterator, account_name payer, const char* if( payer == account_name() ) payer = obj.payer; if( account_name(obj.payer) == payer ) { - update_db_usage( obj.payer, buffer_size + 200 - old_size ); + update_db_usage( obj.payer, buffer_size - old_size ); } else { update_db_usage( obj.payer, -(old_size+200) ); update_db_usage( payer, (buffer_size+200) ); @@ -404,94 +404,122 @@ void apply_context::db_remove_i64( int iterator ) { }); mutable_db.remove( obj ); - keyval_cache.remove( iterator, obj ); + keyval_cache.remove( iterator ); } int apply_context::db_get_i64( int iterator, char* buffer, size_t buffer_size ) { const key_value_object& obj = keyval_cache.get( iterator ); memcpy( buffer, obj.value.data(), std::min(obj.value.size(), buffer_size) ); - + return obj.value.size(); } int apply_context::db_next_i64( int iterator, uint64_t& primary ) { - const auto& obj = keyval_cache.get( iterator ); + if( iterator < -1 ) return -1; // cannot increment past end iterator of table + + const auto& obj = keyval_cache.get( iterator ); // Check for iterator != -1 happens in this call const auto& idx = db.get_index(); auto itr = idx.iterator_to( obj ); ++itr; - if( itr == idx.end() ) return -1; - if( itr->t_id != obj.t_id ) return -1; + if( itr == idx.end() || itr->t_id != obj.t_id ) return keyval_cache.get_end_iterator_by_table_id(obj.t_id); primary = itr->primary_key; return keyval_cache.add( *itr ); } int apply_context::db_previous_i64( int iterator, uint64_t& primary ) { - const auto& obj = keyval_cache.get(iterator); const auto& idx = db.get_index(); - + + if( iterator < -1 ) // is end iterator + { + auto tab = keyval_cache.find_table_by_end_iterator(iterator); + FC_ASSERT( tab, "not a valid end iterator" ); + + auto itr = idx.upper_bound(tab->id); + if( itr == idx.begin() ) return -1; // Empty table + + --itr; + + if( itr->t_id != tab->id ) return -1; // Empty table + + primary = itr->primary_key; + return keyval_cache.add(*itr); + } + + const auto& obj = keyval_cache.get(iterator); + auto itr = idx.iterator_to(obj); - if (itr == idx.end() || itr == idx.begin()) return -1; + if( itr == idx.begin() ) return -1; // cannot decrement past beginning iterator of table --itr; - if (itr->t_id != obj.t_id) return -1; - + if( itr->t_id != obj.t_id ) return -1; // cannot decrement past beginning iterator of table + primary = itr->primary_key; return keyval_cache.add(*itr); } int apply_context::db_find_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { - require_read_lock( code, scope ); + require_read_lock( code, scope ); // redundant? const auto* tab = find_table( code, scope, table ); if( !tab ) return -1; validate_table_key(*tab, contracts::table_key_type::type_i64); + auto table_end_itr = keyval_cache.cache_table( *tab ); const key_value_object* obj = db.find( boost::make_tuple( tab->id, id ) ); - if( !obj ) return -1; + if( !obj ) return table_end_itr; - keyval_cache.cache_table( *tab ); return keyval_cache.add( *obj ); } int apply_context::db_lowerbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { - require_read_lock( code, scope ); + require_read_lock( code, scope ); // redundant? const auto* tab = find_table( code, scope, table ); if( !tab ) return -1; validate_table_key(*tab, contracts::table_key_type::type_i64); + auto table_end_itr = keyval_cache.cache_table( *tab ); const auto& idx = db.get_index(); auto itr = idx.lower_bound( boost::make_tuple( tab->id, id ) ); - if( itr == idx.end() ) return -1; - if( itr->t_id != tab->id ) return -1; + if( itr == idx.end() ) return table_end_itr; + if( itr->t_id != tab->id ) return table_end_itr; - keyval_cache.cache_table( *tab ); return keyval_cache.add( *itr ); } int apply_context::db_upperbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { - require_read_lock( code, scope ); + require_read_lock( code, scope ); // redundant? const auto* tab = find_table( code, scope, table ); if( !tab ) return -1; validate_table_key(*tab, contracts::table_key_type::type_i64); + auto table_end_itr = keyval_cache.cache_table( *tab ); const auto& idx = db.get_index(); auto itr = idx.upper_bound( boost::make_tuple( tab->id, id ) ); - if( itr == idx.end() ) return -1; - if( itr->t_id != tab->id ) return -1; + if( itr == idx.end() ) return table_end_itr; + if( itr->t_id != tab->id ) return table_end_itr; - keyval_cache.cache_table( *tab ); return keyval_cache.add( *itr ); } +int apply_context::db_end_i64( uint64_t code, uint64_t scope, uint64_t table ) { + require_read_lock( code, scope ); // redundant? + + const auto* tab = find_table( code, scope, table ); + if( !tab ) return -1; + validate_table_key(*tab, contracts::table_key_type::type_i64); + + return keyval_cache.cache_table( *tab ); +} + template<> contracts::table_key_type apply_context::get_key_type() { return contracts::table_key_type::type_i64; diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp index d60814d34691cf29fc2537ac6d60ea27f6f6ea99..e96df8e1e6adc22e6bf9db0bd1178192f388d5b6 100644 --- a/libraries/chain/chain_controller.cpp +++ b/libraries/chain/chain_controller.cpp @@ -1048,6 +1048,7 @@ void chain_controller::_initialize_indexes() { _db.add_index(); _db.add_index(); _db.add_index(); + _db.add_index(); _db.add_index(); diff --git a/libraries/chain/include/eosio/chain/apply_context.hpp b/libraries/chain/include/eosio/chain/apply_context.hpp index 469647ce2a159c69d452170d52a284847cc546e3..37327103eb26040f89e7bca9a6389a84f0d9a776 100644 --- a/libraries/chain/include/eosio/chain/apply_context.hpp +++ b/libraries/chain/include/eosio/chain/apply_context.hpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace chainbase { class database; } @@ -26,35 +27,63 @@ class apply_context { typedef contracts::table_id_object table_id_object; iterator_cache(){ + _end_iterator_to_table.reserve(8); _iterator_to_object.reserve(32); } - void cache_table( const table_id_object& tobj ) { - _table_cache[tobj.id] = &tobj; + /// Returns end iterator of the table. + int cache_table( const table_id_object& tobj ) { + auto itr = _table_cache.find(tobj.id); + if( itr != _table_cache.end() ) + return itr->second.second; + + auto ei = index_to_end_iterator(_end_iterator_to_table.size()); + _end_iterator_to_table.push_back( &tobj ); + _table_cache.emplace( tobj.id, make_pair(&tobj, ei) ); + return ei; + } + + const table_id_object& get_table( table_id_object::id_type i )const { + auto itr = _table_cache.find(i); + FC_ASSERT( itr != _table_cache.end(), "an invariant was broken, table should be in cache" ); + return *itr->second.first; } - const table_id_object& get_table( table_id_object::id_type i ) { + int get_end_iterator_by_table_id( table_id_object::id_type i )const { auto itr = _table_cache.find(i); FC_ASSERT( itr != _table_cache.end(), "an invariant was broken, table should be in cache" ); - return *_table_cache[i]; + return itr->second.second; + } + + const table_id_object* find_table_by_end_iterator( int ei )const { + FC_ASSERT( ei < -1, "not an end iterator" ); + auto indx = end_iterator_to_index(ei); + if( indx >= _end_iterator_to_table.size() ) return nullptr; + return _end_iterator_to_table[indx]; } const T& get( int iterator ) { - FC_ASSERT( iterator >= 0, "invalid iterator" ); + FC_ASSERT( iterator != -1, "invalid iterator" ); + FC_ASSERT( iterator >= 0, "reference of end iterator" ); FC_ASSERT( iterator < _iterator_to_object.size(), "iterator out of range" ); auto result = _iterator_to_object[iterator]; FC_ASSERT( result, "reference of deleted object" ); return *result; } - void remove( int iterator, const T& obj ) { + void remove( int iterator ) { + FC_ASSERT( iterator != -1, "invalid iterator" ); + FC_ASSERT( iterator >= 0, "cannot call remove on end iterators" ); + FC_ASSERT( iterator < _iterator_to_object.size(), "iterator out of range" ); + auto obj_ptr = _iterator_to_object[iterator]; + if( !obj_ptr ) return; _iterator_to_object[iterator] = nullptr; - _object_to_iterator.erase( &obj ); + _object_to_iterator.erase( obj_ptr ); } int add( const T& obj ) { auto itr = _object_to_iterator.find( &obj ); - if( itr != _object_to_iterator.end() ) + if( itr != _object_to_iterator.end() ) return itr->second; _iterator_to_object.push_back( &obj ); @@ -64,21 +93,94 @@ class apply_context { } private: - map _table_cache; + map> _table_cache; + vector _end_iterator_to_table; vector _iterator_to_object; map _object_to_iterator; + + /// Precondition: std::numeric_limits::min() < ei < -1 + /// Iterator of -1 is reserved for invalid iterators (i.e. when the appropriate table has not yet been created). + inline size_t end_iterator_to_index( int ei )const { return (-ei - 2); } + /// Precondition: indx < _end_iterator_to_table.size() <= std::numeric_limits::max() + inline int index_to_end_iterator( size_t indx )const { return -(indx + 2); } + }; + + template + struct array_size; + + template + struct array_size< std::array > { + static constexpr size_t size = N; + }; + + template + class secondary_key_helper; + + template + class secondary_key_helper::type>::value>::type > + { + public: + typedef SecondaryKey secondary_key_type; + + static void set(secondary_key_type& sk_in_table, const secondary_key_type& sk_from_wasm) { + sk_in_table = sk_from_wasm; + } + + static void get(secondary_key_type& sk_from_wasm, const secondary_key_type& sk_in_table ) { + sk_from_wasm = sk_in_table; + } + + static auto create_tuple(const contracts::table_id_object& tab, const secondary_key_type& secondary) { + return boost::make_tuple( tab.id, secondary ); + } + }; + + template + class secondary_key_helper::type>::value && + std::is_pointer::type>::value>::type > + { + public: + typedef SecondaryKey secondary_key_type; + typedef SecondaryKeyProxy secondary_key_proxy_type; + typedef SecondaryKeyProxyConst secondary_key_proxy_const_type; + + static constexpr size_t N = array_size::size; + + static void set(secondary_key_type& sk_in_table, secondary_key_proxy_const_type sk_from_wasm) { + std::copy(sk_from_wasm, sk_from_wasm + N, sk_in_table.begin()); + } + + static void get(secondary_key_proxy_type sk_from_wasm, const secondary_key_type& sk_in_table) { + std::copy(sk_in_table.begin(), sk_in_table.end(), sk_from_wasm); + } + + static auto create_tuple(const contracts::table_id_object& tab, secondary_key_proxy_const_type sk_from_wasm) { + secondary_key_type secondary; + std::copy(sk_from_wasm, sk_from_wasm + N, secondary.begin()); + return boost::make_tuple( tab.id, secondary ); + } }; public: - template + template::type, + typename SecondaryKeyProxyConst = typename std::add_lvalue_reference< + typename std::add_const::type>::type > class generic_index { public: typedef typename ObjectType::secondary_key_type secondary_key_type; + typedef SecondaryKeyProxy secondary_key_proxy_type; + typedef SecondaryKeyProxyConst secondary_key_proxy_const_type; + + using secondary_key_helper_t = secondary_key_helper; + generic_index( apply_context& c ):context(c){} int store( uint64_t scope, uint64_t table, const account_name& payer, - uint64_t id, const secondary_key_type& value ) + uint64_t id, secondary_key_proxy_const_type value ) { FC_ASSERT( payer != account_name(), "must specify a valid account to pay for new record" ); @@ -88,8 +190,8 @@ class apply_context { const auto& obj = context.mutable_db.create( [&]( auto& o ){ o.t_id = tab.id; - o.primary_key = id; - o.secondary_key = value; + o.primary_key = id; + secondary_key_helper_t::set(o.secondary_key, value); o.payer = payer; }); @@ -97,7 +199,7 @@ class apply_context { ++t.count; }); - context.update_db_usage( payer, sizeof(secondary_key_type)+200 ); + context.update_db_usage( payer, contracts::get_key_memory_usage()+base_row_fee ); itr_cache.cache_table( tab ); return itr_cache.add( obj ); @@ -105,7 +207,7 @@ class apply_context { void remove( int iterator ) { const auto& obj = itr_cache.get( iterator ); - context.update_db_usage( obj.payer, -( sizeof(secondary_key_type)+200 ) ); + context.update_db_usage( obj.payer, -( contracts::get_key_memory_usage()+base_row_fee ) ); const auto& table_obj = itr_cache.get_table( obj.t_id ); context.require_write_lock( table_obj.scope ); @@ -115,173 +217,222 @@ class apply_context { }); context.mutable_db.remove( obj ); - itr_cache.remove( iterator, obj ); + itr_cache.remove( iterator ); } - void update( int iterator, account_name payer, const secondary_key_type& secondary ) { + void update( int iterator, account_name payer, secondary_key_proxy_const_type secondary ) { const auto& obj = itr_cache.get( iterator ); + context.require_write_lock( itr_cache.get_table( obj.t_id ).scope ); + if( payer == account_name() ) payer = obj.payer; if( obj.payer != payer ) { - context.update_db_usage( obj.payer, -(sizeof(secondary_key_type)+200) ); - context.update_db_usage( payer, +(sizeof(secondary_key_type)+200) ); + context.update_db_usage( obj.payer, -(contracts::get_key_memory_usage()+base_row_fee) ); + context.update_db_usage( payer, +(contracts::get_key_memory_usage()+base_row_fee) ); } context.mutable_db.modify( obj, [&]( auto& o ) { - o.secondary_key = secondary; + secondary_key_helper_t::set(o.secondary_key, secondary); o.payer = payer; }); } - int find_secondary( uint64_t code, uint64_t scope, uint64_t table, const secondary_key_type& secondary, uint64_t& primary ) { - auto tab = context.find_table( context.receiver, scope, table ); + int find_secondary( uint64_t code, uint64_t scope, uint64_t table, secondary_key_proxy_const_type secondary, uint64_t& primary ) { + auto tab = context.find_table( code, scope, table ); if( !tab ) return -1; - const auto* obj = context.db.find( boost::make_tuple( tab->id, secondary ) ); - if( !obj ) return -1; + auto table_end_itr = itr_cache.cache_table( *tab ); + + const auto* obj = context.db.find( secondary_key_helper_t::create_tuple( *tab, secondary ) ); + if( !obj ) return table_end_itr; primary = obj->primary_key; - itr_cache.cache_table( *tab ); return itr_cache.add( *obj ); } - int lowerbound_secondary( uint64_t code, uint64_t scope, uint64_t table, secondary_key_type& secondary, uint64_t& primary ) { - auto tab = context.find_table( context.receiver, scope, table ); + int lowerbound_secondary( uint64_t code, uint64_t scope, uint64_t table, secondary_key_proxy_type secondary, uint64_t& primary ) { + auto tab = context.find_table( code, scope, table ); if( !tab ) return -1; + auto table_end_itr = itr_cache.cache_table( *tab ); + const auto& idx = context.db.get_index< typename chainbase::get_index_type::type, contracts::by_secondary >(); - auto itr = idx.lower_bound( boost::make_tuple( tab->id, secondary ) ); - if( itr == idx.end() ) return -1; - if( itr->t_id != tab->id ) return -1; + auto itr = idx.lower_bound( secondary_key_helper_t::create_tuple( *tab, secondary ) ); + if( itr == idx.end() ) return table_end_itr; + if( itr->t_id != tab->id ) return table_end_itr; primary = itr->primary_key; - secondary = itr->secondary_key; + secondary_key_helper_t::get(secondary, itr->secondary_key); - itr_cache.cache_table( *tab ); return itr_cache.add( *itr ); } - int upperbound_secondary( uint64_t code, uint64_t scope, uint64_t table, secondary_key_type& secondary, uint64_t& primary ) { - auto tab = context.find_table( context.receiver, scope, table ); + int upperbound_secondary( uint64_t code, uint64_t scope, uint64_t table, secondary_key_proxy_type secondary, uint64_t& primary ) { + auto tab = context.find_table( code, scope, table ); if( !tab ) return -1; + auto table_end_itr = itr_cache.cache_table( *tab ); + const auto& idx = context.db.get_index< typename chainbase::get_index_type::type, contracts::by_secondary >(); - auto itr = idx.upper_bound( boost::make_tuple( tab->id, secondary ) ); - if( itr == idx.end() ) return -1; - if( itr->t_id != tab->id ) return -1; + auto itr = idx.upper_bound( secondary_key_helper_t::create_tuple( *tab, secondary ) ); + if( itr == idx.end() ) return table_end_itr; + if( itr->t_id != tab->id ) return table_end_itr; primary = itr->primary_key; - secondary = itr->secondary_key; + secondary_key_helper_t::get(secondary, itr->secondary_key); - itr_cache.cache_table( *tab ); return itr_cache.add( *itr ); } + int end_secondary( uint64_t code, uint64_t scope, uint64_t table ) { + auto tab = context.find_table( code, scope, table ); + if( !tab ) return -1; + + return itr_cache.cache_table( *tab ); + } + int next_secondary( int iterator, uint64_t& primary ) { - const auto& obj = itr_cache.get(iterator); + if( iterator < -1 ) return -1; // cannot increment past end iterator of index + + const auto& obj = itr_cache.get(iterator); // Check for iterator != -1 happens in this call const auto& idx = context.db.get_index::type, contracts::by_secondary>(); auto itr = idx.iterator_to(obj); - if (itr == idx.end()) return -1; - ++itr; - - if (itr == idx.end() || itr->t_id != obj.t_id) return -1; + + if (itr == idx.end() || itr->t_id != obj.t_id) return itr_cache.get_end_iterator_by_table_id(obj.t_id); primary = itr->primary_key; return itr_cache.add(*itr); } - + int previous_secondary( int iterator, uint64_t& primary ) { - const auto& obj = itr_cache.get(iterator); const auto& idx = context.db.get_index::type, contracts::by_secondary>(); + if( iterator < -1 ) // is end iterator + { + auto tab = itr_cache.find_table_by_end_iterator(iterator); + FC_ASSERT( tab, "not a valid end iterator" ); + + auto itr = idx.upper_bound(tab->id); + if( itr == idx.begin() ) return -1; // Empty index + + --itr; + + if( itr->t_id != tab->id ) return -1; // Empty index + + primary = itr->primary_key; + return itr_cache.add(*itr); + } + + const auto& obj = itr_cache.get(iterator); + auto itr = idx.iterator_to(obj); - if (itr == idx.end() || itr == idx.begin()) return -1; + if( itr == idx.begin() ) return -1; // cannot decrement past beginning iterator of index --itr; - if (itr->t_id != obj.t_id) return -1; + if( itr->t_id != obj.t_id ) return -1; // cannot decrement past beginning iterator of index primary = itr->primary_key; return itr_cache.add(*itr); } - - int find_primary( uint64_t code, uint64_t scope, uint64_t table, secondary_key_type& secondary, uint64_t primary ) { - auto tab = context.find_table( context.receiver, scope, table ); + int find_primary( uint64_t code, uint64_t scope, uint64_t table, secondary_key_proxy_type secondary, uint64_t primary ) { + auto tab = context.find_table( code, scope, table ); if( !tab ) return -1; + auto table_end_itr = itr_cache.cache_table( *tab ); + const auto* obj = context.db.find( boost::make_tuple( tab->id, primary ) ); - if( !obj ) return -1; - secondary = obj->secondary_key; + if( !obj ) return table_end_itr; + secondary_key_helper_t::get(secondary, obj->secondary_key); - itr_cache.cache_table( *tab ); return itr_cache.add( *obj ); } int lowerbound_primary( uint64_t code, uint64_t scope, uint64_t table, uint64_t primary ) { - auto tab = context.find_table(context.receiver, scope, table); + auto tab = context.find_table( code, scope, table ); if (!tab) return -1; - + + auto table_end_itr = itr_cache.cache_table( *tab ); + const auto& idx = context.db.get_index::type, contracts::by_primary>(); auto itr = idx.lower_bound(boost::make_tuple(tab->id, primary)); - if (itr == idx.end()) return -1; - if (itr->t_id != tab->id) return -1; + if (itr == idx.end()) return table_end_itr; + if (itr->t_id != tab->id) return table_end_itr; - itr_cache.cache_table(*tab); return itr_cache.add(*itr); } int upperbound_primary( uint64_t code, uint64_t scope, uint64_t table, uint64_t primary ) { - auto tab = context.find_table(context.receiver, scope, table); + auto tab = context.find_table( code, scope, table ); if ( !tab ) return -1; + auto table_end_itr = itr_cache.cache_table( *tab ); + const auto& idx = context.db.get_index::type, contracts::by_primary>(); auto itr = idx.upper_bound(boost::make_tuple(tab->id, primary)); - if (itr == idx.end()) return -1; - if (itr->t_id != tab->id) return -1; + if (itr == idx.end()) return table_end_itr; + if (itr->t_id != tab->id) return table_end_itr; itr_cache.cache_table(*tab); return itr_cache.add(*itr); } int next_primary( int iterator, uint64_t& primary ) { - const auto& obj = itr_cache.get(iterator); + if( iterator < -1 ) return -1; // cannot increment past end iterator of table + + const auto& obj = itr_cache.get(iterator); // Check for iterator != -1 happens in this call const auto& idx = context.db.get_index::type, contracts::by_primary>(); auto itr = idx.iterator_to(obj); - if (itr == idx.end()) return -1; - ++itr; - if (itr == idx.end() || itr->t_id != obj.t_id) return -1; + if (itr == idx.end() || itr->t_id != obj.t_id) return itr_cache.get_end_iterator_by_table_id(obj.t_id); primary = itr->primary_key; return itr_cache.add(*itr); } int previous_primary( int iterator, uint64_t& primary ) { - const auto& obj = itr_cache.get(iterator); const auto& idx = context.db.get_index::type, contracts::by_primary>(); + if( iterator < -1 ) // is end iterator + { + auto tab = itr_cache.find_table_by_end_iterator(iterator); + FC_ASSERT( tab, "not a valid end iterator" ); + + auto itr = idx.upper_bound(tab->id); + if( itr == idx.begin() ) return -1; // Empty table + + --itr; + + if( itr->t_id != tab->id ) return -1; // Empty table + + primary = itr->primary_key; + return itr_cache.add(*itr); + } + + const auto& obj = itr_cache.get(iterator); + auto itr = idx.iterator_to(obj); - if (itr == idx.end() || itr == idx.begin()) return -1; + if( itr == idx.begin() ) return -1; // cannot decrement past beginning iterator of table --itr; - if (itr->t_id != obj.t_id) return -1; + if( itr->t_id != obj.t_id ) return -1; // cannot decrement past beginning iterator of index primary = itr->primary_key; return itr_cache.add(*itr); } - void get( int iterator, uint64_t& primary, secondary_key_type& secondary ) { + void get( int iterator, uint64_t& primary, secondary_key_proxy_type secondary ) { const auto& obj = itr_cache.get( iterator ); primary = obj.primary_key; - secondary = obj.secondary_key; + secondary_key_helper_t::get(secondary, obj.secondary_key); } private: @@ -294,15 +445,16 @@ class apply_context { apply_context(chain_controller& con, chainbase::database& db, const action& a, const transaction_metadata& trx_meta, uint32_t depth=0) - :controller(con), - db(db), - act(a), + :controller(con), + db(db), + act(a), mutable_controller(con), - mutable_db(db), + mutable_db(db), used_authorizations(act.authorization.size(), false), - trx_meta(trx_meta), - idx64(*this), + trx_meta(trx_meta), + idx64(*this), idx128(*this), + idx256(*this), recurse_depth(depth) {} @@ -326,27 +478,27 @@ class apply_context { int32_t remove_record( const table_id_object& t_id, const typename ObjectType::key_type* keys ); template - int32_t load_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ); + int32_t load_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ); template - int32_t front_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ); + int32_t front_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ); template - int32_t back_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, - char* value, size_t valuelen ); + int32_t back_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, + char* value, size_t valuelen ); template - int32_t next_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ); + int32_t next_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ); template - int32_t previous_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ); + int32_t previous_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ); template - int32_t lower_bound_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ); + int32_t lower_bound_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ); template - int32_t upper_bound_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ); + int32_t upper_bound_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ); /** * @brief Require @ref account to have approved of this message @@ -435,16 +587,21 @@ class apply_context { void db_update_i64( int iterator, account_name payer, const char* buffer, size_t buffer_size ); void db_remove_i64( int iterator ); int db_get_i64( int iterator, char* buffer, size_t buffer_size ); - int db_next_i64( int iterator, uint64_t& primary ); + int db_next_i64( int iterator, uint64_t& primary ); int db_previous_i64( int iterator, uint64_t& primary ); int db_find_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ); int db_lowerbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ); int db_upperbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ); + int db_end_i64( uint64_t code, uint64_t scope, uint64_t table ); generic_index idx64; generic_index idx128; + generic_index idx256; + uint32_t recurse_depth; // how deep inline actions can recurse + static constexpr uint32_t base_row_fee = 200; + private: iterator_cache keyval_cache; @@ -661,7 +818,7 @@ using apply_handler = std::function; int32_t apply_context::update_record( const table_id_object& t_id, const account_name& bta, const typename ObjectType::key_type* keys, const char* value, size_t valuelen ) { require_write_lock( t_id.scope ); validate_or_add_table_key(t_id, get_key_type()); - + auto tuple = impl::exact_tuple::get(t_id, keys); const auto* obj = db.find(tuple); @@ -718,7 +875,7 @@ using apply_handler = std::function; } } - template + template int32_t apply_context::front_record( const table_id_object& t_id, typename IndexType::value_type::key_type* keys, char* value, size_t valuelen ) { require_read_lock( t_id.code, t_id.scope ); validate_table_key(t_id, get_key_type()); @@ -778,7 +935,7 @@ using apply_handler = std::function; validate_table_key(t_id, get_key_type()); const auto& pidx = db.get_index(); - + auto tuple = impl::exact_tuple::get(t_id, keys); auto pitr = pidx.find(tuple); @@ -822,7 +979,7 @@ using apply_handler = std::function; validate_table_key(t_id, get_key_type()); const auto& pidx = db.get_index(); - + auto tuple = impl::exact_tuple::get(t_id, keys); auto pitr = pidx.find(tuple); @@ -833,7 +990,7 @@ using apply_handler = std::function; auto itr = fidx.indicies().template project(pitr); const auto& idx = db.get_index(); - + if( itr == idx.end() || itr == idx.begin() || itr->t_id != t_id.id || diff --git a/libraries/chain/include/eosio/chain/contracts/contract_table_objects.hpp b/libraries/chain/include/eosio/chain/contracts/contract_table_objects.hpp index 1c51b1a058385d4a50fa092b3b1a1a56bade2ea6..3c9dba05acba8bcdb8c8719dc19ba0fd4ad24176 100644 --- a/libraries/chain/include/eosio/chain/contracts/contract_table_objects.hpp +++ b/libraries/chain/include/eosio/chain/contracts/contract_table_objects.hpp @@ -9,6 +9,9 @@ #include +#include +#include + namespace eosio { namespace chain { namespace contracts { enum table_key_type { @@ -58,7 +61,7 @@ namespace eosio { namespace chain { namespace contracts { struct by_scope_secondary; struct by_scope_tertiary; - + struct key_value_object : public chainbase::object { OBJECT_CTOR(key_value_object, (value)) @@ -126,12 +129,39 @@ namespace eosio { namespace chain { namespace contracts { > index_index; }; +/* + template + struct _is_array : std::false_type {}; + + template + struct _is_array::type> : std::true_type {}; + + template + inline + typename std::enable_if<_is_array::value, size_t>::type + get_key_memory_usage() { + return SecondaryKey::arr_size; + } +*/ + + template + inline + //typename std::enable_if::value, size_t>::type + size_t + get_key_memory_usage() { + return sizeof(SecondaryKey); + } + typedef secondary_index::index_object index64_object; typedef secondary_index::index_index index64_index; typedef secondary_index::index_object index128_object; typedef secondary_index::index_index index128_index; + typedef std::array key256_t; + typedef secondary_index::index_object index256_object; + typedef secondary_index::index_index index256_index; + /* struct index64_object : public chainbase::object { OBJECT_CTOR(index64_object) @@ -353,6 +383,7 @@ CHAINBASE_SET_INDEX_TYPE(eosio::chain::contracts::key64x64x64_value_object, eosi CHAINBASE_SET_INDEX_TYPE(eosio::chain::contracts::index64_object, eosio::chain::contracts::index64_index) CHAINBASE_SET_INDEX_TYPE(eosio::chain::contracts::index128_object, eosio::chain::contracts::index128_index) +CHAINBASE_SET_INDEX_TYPE(eosio::chain::contracts::index256_object, eosio::chain::contracts::index256_index) FC_REFLECT(eosio::chain::contracts::table_id_object, (id)(code)(scope)(table)(key_type) ) FC_REFLECT(eosio::chain::contracts::key_value_object, (id)(t_id)(primary_key)(value)(payer) ) diff --git a/libraries/chain/include/eosio/chain/contracts/types.hpp b/libraries/chain/include/eosio/chain/contracts/types.hpp index ba224a04c1a6020f1c30e0874741cc9549948f52..a454c78f904abef662e2099274cb517aca7bc8e7 100644 --- a/libraries/chain/include/eosio/chain/contracts/types.hpp +++ b/libraries/chain/include/eosio/chain/contracts/types.hpp @@ -28,7 +28,6 @@ using field_name = fixed_string16; using table_name = name; using action_name = eosio::chain::action_name; - struct type_def { type_def() = default; type_def(const type_name& new_type_name, const type_name& type) @@ -44,7 +43,7 @@ struct field_def { field_def(const field_name& name, const type_name& type) :name(name), type(type) {} - + field_name name; type_name type; @@ -291,4 +290,3 @@ FC_REFLECT( eosio::chain::contracts::unlinkauth , (account FC_REFLECT( eosio::chain::contracts::postrecovery , (account)(data)(memo) ) FC_REFLECT( eosio::chain::contracts::passrecovery , (account) ) FC_REFLECT( eosio::chain::contracts::vetorecovery , (account) ) - diff --git a/libraries/chain/include/eosio/chain/fixed_key.hpp b/libraries/chain/include/eosio/chain/fixed_key.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ee3c714dd971f7d7179bff1832bca46c001018d0 --- /dev/null +++ b/libraries/chain/include/eosio/chain/fixed_key.hpp @@ -0,0 +1,265 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once + +#include +#include +#include + +#include + +namespace eosio { + + template + class fixed_key; + + template + bool operator==(const fixed_key &c1, const fixed_key &c2); + + template + bool operator!=(const fixed_key &c1, const fixed_key &c2); + + template + bool operator>(const fixed_key &c1, const fixed_key &c2); + + template + bool operator<(const fixed_key &c1, const fixed_key &c2); + + /** + * @defgroup fixed_key fixed size key sorted lexicographically + * @ingroup types + * @{ + */ + template + class fixed_key { + private: + + template struct bool_pack; + template + using all_true = std::is_same< bool_pack, bool_pack >; + + public: + + typedef uint128_t word_t; + + static constexpr size_t num_words() { return (Size + sizeof(word_t) - 1) / sizeof(word_t); } + static constexpr size_t padded_bytes() { return num_words() * sizeof(word_t) - Size; } + + /** + * @brief Default constructor to fixed_key object + * + * @details Default constructor to fixed_key object which initializes all bytes to zero + */ + fixed_key() : _data() {} + + /** + * @brief Constructor to fixed_key object from array of num_words() words + * + * @details Constructor to fixed_key object from array of num_words() words + * @param arr data + */ + fixed_key(const word_t (&arr)[num_words()]) + { + std::copy(arr, arr + num_words(), _data.begin()); + } + + /** + * @brief Constructor to fixed_key object from std::array of num_words() words + * + * @details Constructor to fixed_key object from std::array of num_words() words + * @param arr data + */ + fixed_key(const std::array& arr) + { + std::copy(arr.begin(), arr.end(), _data.begin()); + } + +#if 0 +// Cannot infer constructor template arguments in C++ versions prior to C++17 + /** + * @brief Constructor to fixed_key object from sequence of words supplied as arguments + * + * @details Constructor to fixed_key object from sequence of words (of small sizes allowed) supplied as arguments + */ + template + fixed_key(typename std::enable_if::value && + !std::is_same::value && + sizeof(FirstWord) <= sizeof(word_t) && + all_true<(std::is_same::value)...>::value, + FirstWord>::type first_word, + Rest... rest) + { + static_assert( sizeof(word_t) == (sizeof(word_t)/sizeof(FirstWord)) * sizeof(FirstWord), + "size of the backing word size is not divisble by the size of the words supplied as arguments" ); + static_assert( sizeof(FirstWord) * (1 + sizeof...(Rest)) <= Size, "too many words supplied to fixed_key constructor" ); + + auto itr = _data.begin(); + word_t temp_word = 0; + const size_t sub_word_shift = 8 * sizeof(FirstWord); + const size_t num_sub_words = sizeof(word_t) / sizeof(FirstWord); + auto sub_words_left = num_sub_words; + for( auto&& w : { first_word, rest... } ) { + if( sub_words_left > 1 ) { + temp_word |= static_cast(w); + temp_word <<= sub_word_shift; + --sub_words_left; + continue; + } + + FC_ASSERT( sub_words_left == 1, "unexpected error in fixed_key constructor" ); + temp_word |= static_cast(w); + sub_words_left = num_sub_words; + + *itr = temp_word; + temp_word = 0; + ++itr; + } + if( sub_words_left != num_sub_words ) { + if( sub_words_left > 1 ) + temp_word <<= 8 * (sub_words_left-1); + *itr = temp_word; + } + } +#endif + + template + static + fixed_key + make_from_word_sequence(typename std::enable_if::value && + !std::is_same::value && + sizeof(FirstWord) <= sizeof(word_t) && + all_true<(std::is_same::value)...>::value, + FirstWord>::type first_word, + Rest... rest) + { + static_assert( sizeof(word_t) == (sizeof(word_t)/sizeof(FirstWord)) * sizeof(FirstWord), + "size of the backing word size is not divisble by the size of the words supplied as arguments" ); + static_assert( sizeof(FirstWord) * (1 + sizeof...(Rest)) <= Size, "too many words supplied to fixed_key constructor" ); + + fixed_key key; + + auto itr = key._data.begin(); + word_t temp_word = 0; + const size_t sub_word_shift = 8 * sizeof(FirstWord); + const size_t num_sub_words = sizeof(word_t) / sizeof(FirstWord); + auto sub_words_left = num_sub_words; + for( auto&& w : { first_word, rest... } ) { + if( sub_words_left > 1 ) { + temp_word |= static_cast(w); + temp_word <<= sub_word_shift; + --sub_words_left; + continue; + } + + FC_ASSERT( sub_words_left == 1, "unexpected error in fixed_key constructor" ); + temp_word |= static_cast(w); + sub_words_left = num_sub_words; + + *itr = temp_word; + temp_word = 0; + ++itr; + } + if( sub_words_left != num_sub_words ) { + if( sub_words_left > 1 ) + temp_word <<= 8 * (sub_words_left-1); + *itr = temp_word; + } + + return key; + } + + const auto& get_array()const { return _data; } + + auto data() { return _data.data(); } + auto data()const { return _data.data(); } + + auto size()const { return _data.size(); } + + std::array extract_as_byte_array()const { + std::array arr; + + const size_t num_sub_words = sizeof(word_t); + + auto arr_itr = arr.begin(); + auto data_itr = _data.begin(); + + for( size_t counter = _data.size(); counter > 0; --counter, ++data_itr ) { + size_t sub_words_left = num_sub_words; + + if( counter == 1 ) { // If last word in _data array... + sub_words_left -= padded_bytes(); + } + auto temp_word = *data_itr; + for( ; sub_words_left > 0; --sub_words_left ) { + *(arr_itr + sub_words_left - 1) = static_cast(temp_word & 0xFF); + temp_word >>= 8; + } + arr_itr += num_sub_words; + } + + return arr; + } + + // Comparison operators + friend bool operator== <>(const fixed_key &c1, const fixed_key &c2); + + friend bool operator!= <>(const fixed_key &c1, const fixed_key &c2); + + friend bool operator> <>(const fixed_key &c1, const fixed_key &c2); + + friend bool operator< <>(const fixed_key &c1, const fixed_key &c2); + + private: + + std::array _data; + }; + + /** + * @brief Compares two fixed_key variables c1 and c2 + * + * @details Lexicographically compares two fixed_key variables c1 and c2 + * @return if c1 == c2, return true, otherwise false + */ + template + bool operator==(const fixed_key &c1, const fixed_key &c2) { + return c1._data == c2._data; + } + + /** + * @brief Compares two fixed_key variables c1 and c2 + * + * @details Lexicographically compares two fixed_key variables c1 and c2 + * @return if c1 != c2, return true, otherwise false + */ + template + bool operator!=(const fixed_key &c1, const fixed_key &c2) { + return c1._data != c2._data; + } + + /** + * @brief Compares two fixed_key variables c1 and c2 + * + * @details Lexicographically compares two fixed_key variables c1 and c2 + * @return if c1 > c2, return true, otherwise false + */ + template + bool operator>(const fixed_key &c1, const fixed_key &c2) { + return c1._data > c2._data; + } + + /** + * @brief Compares two fixed_key variables c1 and c2 + * + * @details Lexicographically compares two fixed_key variables c1 and c2 + * @return if c1 < c2, return true, otherwise false + */ + template + bool operator<(const fixed_key &c1, const fixed_key &c2) { + return c1._data < c2._data; + } + /// @} fixed_key + + typedef fixed_key<32> key256; +} diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index bfe6c8757e9e732989bff52a65feb43bf82384fc..a3c1bad2f1ea18f75f62bbce4b60c5a872ab2f17 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -122,6 +122,7 @@ namespace eosio { namespace chain { key64x64_value_object_type, index64_object_type, index128_object_type, + index256_object_type, action_permission_object_type, global_property_object_type, dynamic_global_property_object_type, @@ -164,7 +165,7 @@ namespace eosio { namespace chain { using uint128_t = __uint128_t; using bytes = vector; - + } } // eosio::chain @@ -180,6 +181,7 @@ FC_REFLECT_ENUM(eosio::chain::object_type, (key64x64_value_object_type) (index64_object_type) (index128_object_type) + (index256_object_type) (action_permission_object_type) (global_property_object_type) (dynamic_global_property_object_type) diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index fc8c1b932cd67eb544b0d1c062f17640217ca0b7..d732fcc9bbdcd04bc8fde4de5067f3a13affe67d 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -370,7 +370,7 @@ class context_aware_api { class context_free_api : public context_aware_api { public: context_free_api( wasm_interface& wasm ) - :context_aware_api(wasm, true) { + :context_aware_api(wasm, true) { /* the context_free_data is not available during normal application because it is prunable */ FC_ASSERT( context.context_free, "this API may only be called from context_free apply" ); } @@ -392,7 +392,7 @@ class privileged_api : public context_aware_api { * block that includes this call is irreversible. It should * fail if the feature is already pending. * - * Feature name should be base32 encoded name. + * Feature name should be base32 encoded name. */ void activate_feature( int64_t feature_name ) { FC_ASSERT( !"Unsupported Hardfork Detected" ); @@ -408,7 +408,7 @@ class privileged_api : public context_aware_api { return false; } - void set_resource_limits( account_name account, + void set_resource_limits( account_name account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight, int64_t cpu_usec_per_period ) { auto& buo = context.db.get( account ); @@ -432,15 +432,15 @@ class privileged_api : public context_aware_api { } - void get_resource_limits( account_name account, + void get_resource_limits( account_name account, uint64_t& ram_bytes, uint64_t& net_weight, uint64_t cpu_weight ) { } - + void set_active_producers( array_ptr packed_producer_schedule, size_t datalen) { datastream ds( packed_producer_schedule, datalen ); producer_schedule_type psch; fc::raw::unpack(ds, psch); - context.mutable_db.modify( context.controller.get_global_properties(), + context.mutable_db.modify( context.controller.get_global_properties(), [&]( auto& gprops ) { gprops.new_active_producers = psch; }); @@ -486,7 +486,7 @@ class producer_api : public context_aware_api { auto active_producers = context.get_active_producers(); size_t len = active_producers.size(); size_t cpy_len = std::min(datalen, len); - memcpy(producers, active_producers.data(), cpy_len * sizeof(chain::account_name) ); + memcpy(producers, active_producers.data(), cpy_len * sizeof(chain::account_name)); return len; } }; @@ -497,9 +497,9 @@ class crypto_api : public context_aware_api { /** * This method can be optimized out during replay as it has - * no possible side effects other than "passing". + * no possible side effects other than "passing". */ - void assert_recover_key( fc::sha256& digest, + void assert_recover_key( fc::sha256& digest, array_ptr sig, size_t siglen, array_ptr pub, size_t publen ) { fc::crypto::signature s; @@ -514,7 +514,7 @@ class crypto_api : public context_aware_api { FC_ASSERT( check == p, "Error expected key different than recovered key" ); } - int recover_key( fc::sha256& digest, + int recover_key( fc::sha256& digest, array_ptr sig, size_t siglen, array_ptr pub, size_t publen ) { fc::crypto::signature s; @@ -585,14 +585,16 @@ class system_api : public context_aware_api { } void eosio_assert(bool condition, null_terminated_ptr str) { - std::string message( str ); - if( !condition ) edump((message)); - FC_ASSERT( condition, "assertion failed: ${s}", ("s",message)); + if( !condition ) { + std::string message( str ); + edump((message)); + FC_ASSERT( condition, "assertion failed: ${s}", ("s",message)); + } } - + fc::time_point_sec now() { return context.controller.head_block_time(); - } + } }; class action_api : public context_aware_api { @@ -661,6 +663,89 @@ class console_api : public context_aware_api { } }; +#define DB_API_METHOD_WRAPPERS_SIMPLE_SECONDARY(IDX, TYPE)\ + int db_##IDX##_store( uint64_t scope, uint64_t table, uint64_t payer, uint64_t id, const TYPE& secondary ) {\ + return context.IDX.store( scope, table, payer, id, secondary );\ + }\ + void db_##IDX##_update( int iterator, uint64_t payer, const TYPE& secondary ) {\ + return context.IDX.update( iterator, payer, secondary );\ + }\ + void db_##IDX##_remove( int iterator ) {\ + return context.IDX.remove( iterator );\ + }\ + int db_##IDX##_find_secondary( uint64_t code, uint64_t scope, uint64_t table, const TYPE& secondary, uint64_t& primary ) {\ + return context.IDX.find_secondary(code, scope, table, secondary, primary);\ + }\ + int db_##IDX##_find_primary( uint64_t code, uint64_t scope, uint64_t table, TYPE& secondary, uint64_t primary ) {\ + return context.IDX.find_primary(code, scope, table, secondary, primary);\ + }\ + int db_##IDX##_lowerbound( uint64_t code, uint64_t scope, uint64_t table, TYPE& secondary, uint64_t& primary ) {\ + return context.IDX.lowerbound_secondary(code, scope, table, secondary, primary);\ + }\ + int db_##IDX##_upperbound( uint64_t code, uint64_t scope, uint64_t table, TYPE& secondary, uint64_t& primary ) {\ + return context.IDX.upperbound_secondary(code, scope, table, secondary, primary);\ + }\ + int db_##IDX##_end( uint64_t code, uint64_t scope, uint64_t table ) {\ + return context.IDX.end_secondary(code, scope, table);\ + }\ + int db_##IDX##_next( int iterator, uint64_t& primary ) {\ + return context.IDX.next_secondary(iterator, primary);\ + }\ + int db_##IDX##_previous( int iterator, uint64_t& primary ) {\ + return context.IDX.previous_secondary(iterator, primary);\ + } + +#define DB_API_METHOD_WRAPPERS_ARRAY_SECONDARY(IDX, ARR_SIZE, ARR_ELEMENT_TYPE)\ + int db_##IDX##_store( uint64_t scope, uint64_t table, uint64_t payer, uint64_t id, array_ptr data, size_t data_len) {\ + FC_ASSERT( data_len == ARR_SIZE,\ + "invalid size of secondary key array for " #IDX ": given ${given} bytes but expected ${expected} bytes",\ + ("given",data_len)("expected",ARR_SIZE) );\ + return context.IDX.store(scope, table, payer, id, data.value);\ + }\ + void db_##IDX##_update( int iterator, uint64_t payer, array_ptr data, size_t data_len ) {\ + FC_ASSERT( data_len == ARR_SIZE,\ + "invalid size of secondary key array for " #IDX ": given ${given} bytes but expected ${expected} bytes",\ + ("given",data_len)("expected",ARR_SIZE) );\ + return context.IDX.update(iterator, payer, data.value);\ + }\ + void db_##IDX##_remove( int iterator ) {\ + return context.IDX.remove(iterator);\ + }\ + int db_##IDX##_find_secondary( uint64_t code, uint64_t scope, uint64_t table, array_ptr data, size_t data_len, uint64_t& primary ) {\ + FC_ASSERT( data_len == ARR_SIZE,\ + "invalid size of secondary key array for " #IDX ": given ${given} bytes but expected ${expected} bytes",\ + ("given",data_len)("expected",ARR_SIZE) );\ + return context.IDX.find_secondary(code, scope, table, data, primary);\ + }\ + int db_##IDX##_find_primary( uint64_t code, uint64_t scope, uint64_t table, array_ptr data, size_t data_len, uint64_t primary ) {\ + FC_ASSERT( data_len == ARR_SIZE,\ + "invalid size of secondary key array for " #IDX ": given ${given} bytes but expected ${expected} bytes",\ + ("given",data_len)("expected",ARR_SIZE) );\ + return context.IDX.find_primary(code, scope, table, data.value, primary);\ + }\ + int db_##IDX##_lowerbound( uint64_t code, uint64_t scope, uint64_t table, array_ptr data, size_t data_len, uint64_t& primary ) {\ + FC_ASSERT( data_len == ARR_SIZE,\ + "invalid size of secondary key array for " #IDX ": given ${given} bytes but expected ${expected} bytes",\ + ("given",data_len)("expected",ARR_SIZE) );\ + return context.IDX.lowerbound_secondary(code, scope, table, data.value, primary);\ + }\ + int db_##IDX##_upperbound( uint64_t code, uint64_t scope, uint64_t table, array_ptr data, size_t data_len, uint64_t& primary ) {\ + FC_ASSERT( data_len == ARR_SIZE,\ + "invalid size of secondary key array for " #IDX ": given ${given} bytes but expected ${expected} bytes",\ + ("given",data_len)("expected",ARR_SIZE) );\ + return context.IDX.upperbound_secondary(code, scope, table, data.value, primary);\ + }\ + int db_##IDX##_end( uint64_t code, uint64_t scope, uint64_t table ) {\ + return context.IDX.end_secondary(code, scope, table);\ + }\ + int db_##IDX##_next( int iterator, uint64_t& primary ) {\ + return context.IDX.next_secondary(iterator, primary);\ + }\ + int db_##IDX##_previous( int iterator, uint64_t& primary ) {\ + return context.IDX.previous_secondary(iterator, primary);\ + } + + class database_api : public context_aware_api { public: using context_aware_api::context_aware_api; @@ -683,84 +768,22 @@ class database_api : public context_aware_api { int db_previous_i64( int itr, uint64_t& primary ) { return context.db_previous_i64(itr, primary); } - int db_find_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { - return context.db_find_i64( code, scope, table, id ); - } - int db_lowerbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { - return context.db_lowerbound_i64( code, scope, table, id ); - } - int db_upperbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { - return context.db_upperbound_i64( code, scope, table, id ); - } - - int db_idx64_store( uint64_t scope, uint64_t table, uint64_t payer, uint64_t id, const uint64_t& secondary ) { - return context.idx64.store( scope, table, payer, id, secondary ); - } - void db_idx64_update( int iterator, uint64_t payer, const uint64_t& secondary ) { - return context.idx64.update( iterator, payer, secondary ); - } - void db_idx64_remove( int iterator ) { - return context.idx64.remove( iterator ); + int db_find_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { + return context.db_find_i64( code, scope, table, id ); } - int db_idx64_find_secondary( uint64_t code, uint64_t scope, uint64_t table, const uint64_t& secondary, uint64_t& primary ) { - return context.idx64.find_secondary(code, scope, table, secondary, primary); + int db_lowerbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { + return context.db_lowerbound_i64( code, scope, table, id ); } - int db_idx64_find_primary( uint64_t code, uint64_t scope, uint64_t table, uint64_t& secondary, uint64_t primary ) { - return context.idx64.find_primary(code, scope, table, secondary, primary); + int db_upperbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) { + return context.db_upperbound_i64( code, scope, table, id ); } - int db_idx64_lowerbound( uint64_t code, uint64_t scope, uint64_t table, uint64_t& secondary, uint64_t& primary ) { - return context.idx64.lowerbound_secondary(code, scope, table, secondary, primary); - } - int db_idx64_upperbound( uint64_t code, uint64_t scope, uint64_t table, uint64_t& secondary, uint64_t& primary ) { - return context.idx64.upperbound_secondary(code, scope, table, secondary, primary); - } - int db_idx64_next( int iterator, uint64_t& primary ) { - return context.idx64.next_secondary(iterator, primary); - } - int db_idx64_previous( int iterator, uint64_t& primary ) { - return context.idx64.previous_secondary(iterator, primary); + int db_end_i64( uint64_t code, uint64_t scope, uint64_t table ) { + return context.db_end_i64( code, scope, table ); } - int db_idx128_store( uint64_t scope, uint64_t table, uint64_t payer, uint64_t id, const uint128_t& secondary ) { - return context.idx128.store( scope, table, payer, id, secondary ); - } - void db_idx128_update( int iterator, uint64_t payer, const uint128_t& secondary ) { - return context.idx128.update( iterator, payer, secondary ); - } - void db_idx128_remove( int iterator ) { - return context.idx128.remove( iterator ); - } - int db_idx128_find_primary( uint64_t code, uint64_t scope, uint64_t table, uint128_t& secondary, uint64_t primary ) { - return context.idx128.find_primary( code, scope, table, secondary, primary ); - } - int db_idx128_find_secondary( uint64_t code, uint64_t scope, uint64_t table, const uint128_t& secondary, uint64_t& primary ) { - return context.idx128.find_secondary(code, scope, table, secondary, primary); - } - int db_idx128_lowerbound( uint64_t code, uint64_t scope, uint64_t table, uint128_t& secondary, uint64_t& primary ) { - return context.idx128.lowerbound_secondary(code, scope, table, secondary, primary); - } - int db_idx128_upperbound( uint64_t code, uint64_t scope, uint64_t table, uint128_t& secondary, uint64_t& primary ) { - return context.idx128.upperbound_secondary(code, scope, table, secondary, primary); - } - int db_idx128_next( int iterator, uint64_t& primary ) { - return context.idx128.next_secondary(iterator, primary); - } - int db_idx128_previous( int iterator, uint64_t& primary ) { - return context.idx128.previous_secondary(iterator, primary); - } - - /* - int db_idx128_next( int iterator, uint64_t& primary ) { - } - int db_idx128_prev( int iterator, uint64_t& primary ) { - } - int db_idx128_find_secondary( uint64_t code, uint64_t scope, uint64_t table, uint128_t& secondary, uint64_t& primary ) { - } - int db_idx128_lowerbound( uint64_t code, uint64_t scope, uint64_t table, uint128_t& secondary, uint64_t& primary ) { - } - int db_idx128_upperbound( uint64_t code, uint64_t scope, uint64_t table, uint128_t& secondary, uint64_t& primary ) { - } - */ + DB_API_METHOD_WRAPPERS_SIMPLE_SECONDARY(idx64, uint64_t) + DB_API_METHOD_WRAPPERS_SIMPLE_SECONDARY(idx128, uint128_t) + DB_API_METHOD_WRAPPERS_ARRAY_SECONDARY(idx256, 2, uint128_t) }; @@ -795,7 +818,7 @@ class db_api : public context_aware_api { int update(const scope_name& scope, const name& table, const account_name& bta, array_ptr data, size_t data_len) { return call(&apply_context::update_record, scope, table, bta, data, data_len); } - + int remove(const scope_name& scope, const name& table, const KeyArrayType &keys) { const auto& t_id = context.find_or_create_table(context.receiver, scope, table); return context.remove_record(t_id, keys); @@ -809,16 +832,16 @@ class db_api : public context_aware_api { using KeyArrayType = KeyType[KeyCount]; using ContextMethodType = int(apply_context::*)(const table_id_object&, const KeyType*, const char*, size_t); -/* TODO something weird is going on here, will maybe fix before DB changes or this might get +/* TODO something weird is going on here, will maybe fix before DB changes or this might get * totally changed anyway private: int call(ContextMethodType method, const scope_name& scope, const name& table, account_name bta, null_terminated_ptr key, size_t key_len, array_ptr data, size_t data_len) { const auto& t_id = context.find_or_create_table(context.receiver, scope, table); - const KeyType keys((const char*)key.value, key_len); + const KeyType keys((const char*)key.value, key_len); - const char* record_data = ((const char*)data); - size_t record_len = data_len; + const char* record_data = ((const char*)data); + size_t record_len = data_len; return (context.*(method))(t_id, bta, &keys, record_data, record_len); } */ @@ -828,23 +851,23 @@ class db_api : public context_aware_api { int store_str(const scope_name& scope, const name& table, const account_name& bta, null_terminated_ptr key, uint32_t key_len, array_ptr data, size_t data_len) { const auto& t_id = context.find_or_create_table(context.receiver, scope, table); - const KeyType keys(key.value, key_len); - const char* record_data = ((const char*)data); - size_t record_len = data_len; + const KeyType keys(key.value, key_len); + const char* record_data = ((const char*)data); + size_t record_len = data_len; return context.store_record(t_id, bta, &keys, record_data, record_len); //return call(&apply_context::store_record, scope, table, bta, key, key_len, data, data_len); } - int update_str(const scope_name& scope, const name& table, const account_name& bta, + int update_str(const scope_name& scope, const name& table, const account_name& bta, null_terminated_ptr key, uint32_t key_len, array_ptr data, size_t data_len) { const auto& t_id = context.find_or_create_table(context.receiver, scope, table); - const KeyType keys((const char*)key, key_len); - const char* record_data = ((const char*)data); - size_t record_len = data_len; + const KeyType keys((const char*)key, key_len); + const char* record_data = ((const char*)data); + size_t record_len = data_len; return context.update_record(t_id, bta, &keys, record_data, record_len); //return call(&apply_context::update_record, scope, table, bta, key, key_len, data, data_len); } - + int remove_str(const scope_name& scope, const name& table, array_ptr &key, uint32_t key_len) { const auto& t_id = context.find_or_create_table(scope, context.receiver, table); const KeyArrayType k = {std::string(key, key_len)}; @@ -928,7 +951,7 @@ class db_index_api : public context_aware_ using ContextMethodType = int(apply_context::*)(const table_id_object&, KeyType*, char*, size_t); - int call(ContextMethodType method, const scope_name& scope, const account_name& code, const name& table, + int call(ContextMethodType method, const scope_name& scope, const account_name& code, const name& table, array_ptr &key, uint32_t key_len, array_ptr data, size_t data_len) { auto maybe_t_id = context.find_table(scope, context.receiver, table); if (maybe_t_id == nullptr) { @@ -982,7 +1005,7 @@ class memory_api : public context_aware_api { public: memory_api( wasm_interface& wasm ) :context_aware_api(wasm,true){} - + char* memcpy( array_ptr dest, array_ptr src, size_t length) { return (char *)::memcpy(dest, src, length); } @@ -1104,37 +1127,37 @@ class compiler_builtins : public context_aware_api { i >>= shift; ret = (unsigned __int128)i; } - + void __divti3(__int128& ret, uint64_t la, uint64_t ha, uint64_t lb, uint64_t hb) { __int128 lhs = ha; __int128 rhs = hb; - + lhs <<= 64; lhs |= la; rhs <<= 64; rhs |= lb; - FC_ASSERT(rhs != 0, "divide by zero"); + FC_ASSERT(rhs != 0, "divide by zero"); - lhs /= rhs; + lhs /= rhs; ret = lhs; - } + } void __udivti3(unsigned __int128& ret, uint64_t la, uint64_t ha, uint64_t lb, uint64_t hb) { unsigned __int128 lhs = ha; unsigned __int128 rhs = hb; - + lhs <<= 64; lhs |= la; rhs <<= 64; rhs |= lb; - FC_ASSERT(rhs != 0, "divide by zero"); + FC_ASSERT(rhs != 0, "divide by zero"); - lhs /= rhs; + lhs /= rhs; ret = lhs; } @@ -1148,9 +1171,9 @@ class compiler_builtins : public context_aware_api { rhs <<= 64; rhs |= lb; - lhs *= rhs; + lhs *= rhs; ret = lhs; - } + } void __modti3(__int128& ret, uint64_t la, uint64_t ha, uint64_t lb, uint64_t hb) { __int128 lhs = ha; @@ -1158,10 +1181,10 @@ class compiler_builtins : public context_aware_api { lhs <<= 64; lhs |= la; - + rhs <<= 64; rhs |= lb; - + FC_ASSERT(rhs != 0, "divide by zero"); lhs %= rhs; @@ -1174,10 +1197,10 @@ class compiler_builtins : public context_aware_api { lhs <<= 64; lhs |= la; - + rhs <<= 64; rhs |= lb; - + FC_ASSERT(rhs != 0, "divide by zero"); lhs %= rhs; @@ -1190,13 +1213,13 @@ class compiler_builtins : public context_aware_api { class math_api : public context_aware_api { public: using context_aware_api::context_aware_api; - + void diveq_i128(unsigned __int128* self, const unsigned __int128* other) { fc::uint128_t s(*self); const fc::uint128_t o(*other); FC_ASSERT( o != 0, "divide by zero" ); - + s = s/o; *self = (unsigned __int128)s; } @@ -1304,6 +1327,30 @@ REGISTER_INTRINSICS(producer_api, (get_active_producers, int(int, int) ) ); +#define DB_SECONDARY_INDEX_METHODS_SIMPLE(IDX) \ + (db_##IDX##_store, int(int64_t,int64_t,int64_t,int64_t,int))\ + (db_##IDX##_remove, void(int))\ + (db_##IDX##_update, void(int,int64_t,int))\ + (db_##IDX##_find_primary, int(int64_t,int64_t,int64_t,int,int64_t))\ + (db_##IDX##_find_secondary, int(int64_t,int64_t,int64_t,int,int))\ + (db_##IDX##_lowerbound, int(int64_t,int64_t,int64_t,int,int))\ + (db_##IDX##_upperbound, int(int64_t,int64_t,int64_t,int,int))\ + (db_##IDX##_end, int(int64_t,int64_t,int64_t))\ + (db_##IDX##_next, int(int, int))\ + (db_##IDX##_previous, int(int, int)) + +#define DB_SECONDARY_INDEX_METHODS_ARRAY(IDX) \ + (db_##IDX##_store, int(int64_t,int64_t,int64_t,int64_t,int,int))\ + (db_##IDX##_remove, void(int))\ + (db_##IDX##_update, void(int,int64_t,int,int))\ + (db_##IDX##_find_primary, int(int64_t,int64_t,int64_t,int,int,int64_t))\ + (db_##IDX##_find_secondary, int(int64_t,int64_t,int64_t,int,int,int))\ + (db_##IDX##_lowerbound, int(int64_t,int64_t,int64_t,int,int,int))\ + (db_##IDX##_upperbound, int(int64_t,int64_t,int64_t,int,int,int))\ + (db_##IDX##_end, int(int64_t,int64_t,int64_t))\ + (db_##IDX##_next, int(int, int))\ + (db_##IDX##_previous, int(int, int)) + REGISTER_INTRINSICS( database_api, (db_store_i64, int(int64_t,int64_t,int64_t,int64_t,int,int)) (db_update_i64, void(int,int64_t,int,int)) @@ -1314,26 +1361,11 @@ REGISTER_INTRINSICS( database_api, (db_find_i64, int(int64_t,int64_t,int64_t,int64_t)) (db_lowerbound_i64, int(int64_t,int64_t,int64_t,int64_t)) (db_upperbound_i64, int(int64_t,int64_t,int64_t,int64_t)) - - (db_idx64_store, int(int64_t,int64_t,int64_t,int64_t,int)) - (db_idx64_remove, void(int)) - (db_idx64_update, void(int,int64_t,int)) - (db_idx64_find_primary, int(int64_t,int64_t,int64_t,int,int64_t)) - (db_idx64_find_secondary, int(int64_t,int64_t,int64_t,int,int)) - (db_idx64_lowerbound, int(int64_t,int64_t,int64_t,int,int)) - (db_idx64_upperbound, int(int64_t,int64_t,int64_t,int,int)) - (db_idx64_next, int(int, int)) - (db_idx64_previous, int(int, int)) - - (db_idx128_store, int(int64_t,int64_t,int64_t,int64_t,int)) - (db_idx128_remove, void(int)) - (db_idx128_update, void(int,int64_t,int)) - (db_idx128_find_primary, int(int64_t,int64_t,int64_t,int,int64_t)) - (db_idx128_find_secondary, int(int64_t,int64_t,int64_t,int,int)) - (db_idx128_lowerbound, int(int64_t,int64_t,int64_t,int,int)) - (db_idx128_upperbound, int(int64_t,int64_t,int64_t,int,int)) - (db_idx128_next, int(int, int)) - (db_idx128_previous, int(int, int)) + (db_end_i64, int(int64_t,int64_t,int64_t)) + + DB_SECONDARY_INDEX_METHODS_SIMPLE(idx64) + DB_SECONDARY_INDEX_METHODS_SIMPLE(idx128) + DB_SECONDARY_INDEX_METHODS_ARRAY(idx256) ); REGISTER_INTRINSICS(crypto_api, diff --git a/libraries/fc/include/fc/crypto/sha256.hpp b/libraries/fc/include/fc/crypto/sha256.hpp index 58bba9e3afa2d8e7b553a50d962c758440cba75a..bef0fea6807aba8747dcce3c2ce5da70810c5c14 100644 --- a/libraries/fc/include/fc/crypto/sha256.hpp +++ b/libraries/fc/include/fc/crypto/sha256.hpp @@ -107,6 +107,7 @@ class sha256 uint64_t hash64(const char* buf, size_t len); } // fc + namespace std { template<> @@ -117,6 +118,7 @@ namespace std return *((size_t*)&s); } }; + } namespace boost diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index 28d3989d0e5854b94dbe135e2c9bca98eabbab15..4f8bed06af7304146588792f50ad5a94332ea5a4 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -318,7 +318,8 @@ namespace eosio { namespace testing { if (tbl) { const auto *obj = db.find(boost::make_tuple(tbl->id, asset_symbol.value())); if (obj) { - fc::datastream ds(obj->value.data(), obj->value.size()); + //balance is the second field after symbol, so skip the symbol + fc::datastream ds(obj->value.data()+sizeof(symbol), obj->value.size()-sizeof(symbol)); fc::raw::unpack(ds, result); } } diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index 174020babce338831064129fd52a05ff8de7608a..bf2edb2803cacb3b0d1c120de63eca903df5e6cd 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -192,6 +192,11 @@ public: memcpy( data.data()+sizeof(uint64_t), obj.value.data(), obj.value.size() ); } + static void copy_inline_row(const chain::contracts::key_value_object& obj, vector& data) { + data.resize( obj.value.size() ); + memcpy( data.data(), obj.value.data(), obj.value.size() ); + } + static void copy_row(const chain::contracts::keystr_value_object& obj, vector& data) { data.resize( obj.primary_key.size() + obj.value.size() + 8 ); fc::datastream ds(data.data(), data.size()); @@ -215,6 +220,11 @@ public: memcpy( data.data()+3*sizeof(uint64_t), obj.value.data(), obj.value.size() ); } + static void copy_inline_row(const chain::contracts::key64x64x64_value_object& obj, vector& data) { + data.resize( obj.value.size() ); + memcpy( data.data(), obj.value.data(), obj.value.size() ); + } + template void walk_table(const name& code, const name& scope, const name& table, Function f) const { diff --git a/tests/api_tests/api_tests.cpp b/tests/api_tests/api_tests.cpp index b1937c961ded16f1a98976dcaf385658fa8d999f..92b8e6d30f311eec76da8f6845a84e0636a8fd7e 100644 --- a/tests/api_tests/api_tests.cpp +++ b/tests/api_tests/api_tests.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include FC_REFLECT( dummy_action, (a)(b)(c) ); @@ -208,20 +209,20 @@ uint32_t last_fnc_err = 0; *************************************************************************************/ BOOST_FIXTURE_TEST_CASE(action_tests, tester) { try { produce_blocks(2); - create_account( N(testapi) ); - create_account( N(acc1) ); - create_account( N(acc2) ); - create_account( N(acc3) ); - create_account( N(acc4) ); + create_account( N(testapi) ); + create_account( N(acc1) ); + create_account( N(acc2) ); + create_account( N(acc3) ); + create_account( N(acc4) ); produce_blocks(1000); set_code( N(testapi), test_api_wast ); produce_blocks(1); - + // test assert_true CALL_TEST_FUNCTION( *this, "test_action", "assert_true", {}); //test assert_false - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "assert_false", {}), fc::assert_exception, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "assert_false", {}), fc::assert_exception, [](const fc::assert_exception& e) { return expect_assert_message(e, "test_action::assert_false"); } @@ -234,10 +235,10 @@ BOOST_FIXTURE_TEST_CASE(action_tests, tester) { try { // test read_action_to_0 std::vector raw_bytes((1<<16)); CALL_TEST_FUNCTION( *this, "test_action", "read_action_to_0", raw_bytes ); - + // test read_action_to_0 raw_bytes.resize((1<<16)+1); - BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "read_action_to_0", raw_bytes), eosio::chain::wasm_execution_error, + BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_action", "read_action_to_0", raw_bytes), eosio::chain::wasm_execution_error, [](const eosio::chain::wasm_execution_error& e) { return expect_assert_message(e, "access violation"); } @@ -271,7 +272,7 @@ BOOST_FIXTURE_TEST_CASE(action_tests, tester) { try { auto res = test.push_transaction(trx); BOOST_CHECK_EQUAL(res.status, transaction_receipt::executed); }; - BOOST_CHECK_EXCEPTION(test_require_notice(*this, raw_bytes, scope), tx_missing_sigs, + BOOST_CHECK_EXCEPTION(test_require_notice(*this, raw_bytes, scope), tx_missing_sigs, [](const tx_missing_sigs& e) { return expect_assert_message(e, "transaction declares authority"); } @@ -323,7 +324,7 @@ BOOST_FIXTURE_TEST_CASE(action_tests, tester) { try { auto res = push_transaction(trx); BOOST_CHECK_EQUAL(res.status, transaction_receipt::executed); } - + uint32_t now = control->head_block_time().sec_since_epoch(); CALL_TEST_FUNCTION( *this, "test_action", "now", fc::raw::pack(now)); @@ -335,7 +336,7 @@ BOOST_FIXTURE_TEST_CASE(action_tests, tester) { try { } ); - // test test_current_receiver + // test test_current_receiver CALL_TEST_FUNCTION( *this, "test_action", "test_current_receiver", fc::raw::pack(N(testapi))); // test send_action_sender @@ -352,7 +353,7 @@ BOOST_FIXTURE_TEST_CASE(action_tests, tester) { try { return expect_assert_message(e, "abort() called"); } ); - + } FC_LOG_AND_RETHROW() } @@ -378,7 +379,7 @@ BOOST_AUTO_TEST_CASE(checktime_fail_tests) { t.create_account( N(testapi) ); t.set_code( N(testapi), test_api_wast ); - + auto call_test = [](tester& test, auto ac) { signed_transaction trx; @@ -460,12 +461,12 @@ BOOST_FIXTURE_TEST_CASE(compiler_builtins_tests, tester) { try { *************************************************************************************/ BOOST_FIXTURE_TEST_CASE(transaction_tests, tester) { try { produce_blocks(2); - create_account( N(testapi) ); + create_account( N(testapi) ); produce_blocks(100); set_code( N(testapi), test_api_wast ); produce_blocks(1); - - // test send_action + + // test send_action CALL_TEST_FUNCTION(*this, "test_transaction", "send_action", {}); // test send_action_empty @@ -483,7 +484,7 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, tester) { try { } ); control->push_deferred_transactions( true ); - + // test send_transaction CALL_TEST_FUNCTION(*this, "test_transaction", "send_transaction", {}); control->push_deferred_transactions( true ); @@ -495,11 +496,11 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, tester) { try { } ); control->push_deferred_transactions( true ); - - // test test_transaction_size + + // test test_transaction_size CALL_TEST_FUNCTION(*this, "test_transaction", "test_transaction_size", fc::raw::pack(56) ); control->push_deferred_transactions( true ); - + // test test_read_transaction // this is a bit rough, but I couldn't figure out a better way to compare the hashes CAPTURE( cerr, CALL_TEST_FUNCTION( *this, "test_transaction", "test_read_transaction", {} ) ); @@ -507,11 +508,11 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, tester) { try { string sha_expect = "bdeb5b58dda272e4b23ee7d2a5f0ff034820c156364893b758892e06fa39e7fe"; BOOST_CHECK_EQUAL(capture[3] == sha_expect, true); // test test_tapos_block_num - CALL_TEST_FUNCTION(*this, "test_transaction", "test_tapos_block_num", fc::raw::pack(control->head_block_num()) ); + CALL_TEST_FUNCTION(*this, "test_transaction", "test_tapos_block_num", fc::raw::pack(control->head_block_num()) ); // test test_tapos_block_prefix - CALL_TEST_FUNCTION(*this, "test_transaction", "test_tapos_block_prefix", fc::raw::pack(control->head_block_id()._hash[1]) ); - + CALL_TEST_FUNCTION(*this, "test_transaction", "test_tapos_block_prefix", fc::raw::pack(control->head_block_id()._hash[1]) ); + // test send_action_recurse BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(*this, "test_transaction", "send_action_recurse", {}), eosio::chain::transaction_exception, [](const eosio::chain::transaction_exception& e) { @@ -560,15 +561,15 @@ BOOST_FIXTURE_TEST_CASE(chain_tests, tester) { try { create_account( N(initu) ); create_account( N(initv) ); - create_account( N(testapi) ); - + create_account( N(testapi) ); + // set active producers { signed_transaction trx; auto pl = vector{{config::system_account_name, config::active_name}}; action act(pl, test_chain_action()); - vector prod_keys = { + vector prod_keys = { { N(inita), get_public_key( N(inita), "active" ) }, { N(initb), get_public_key( N(initb), "active" ) }, { N(initc), get_public_key( N(initc), "active" ) }, @@ -598,16 +599,16 @@ BOOST_FIXTURE_TEST_CASE(chain_tests, tester) { try { trx.actions.push_back(act); set_tapos(trx); - + auto sigs = trx.sign(get_private_key(config::system_account_name, "active"), chain_id_type()); trx.get_signature_keys(chain_id_type() ); auto res = push_transaction(trx); BOOST_CHECK_EQUAL(res.status, transaction_receipt::executed); - } + } set_code( N(testapi), test_api_wast ); produce_blocks(100); - auto& gpo = control->get_global_properties(); + auto& gpo = control->get_global_properties(); std::vector prods(gpo.active_producers.producers.size()); for ( int i=0; i < gpo.active_producers.producers.size(); i++ ) { prods[i] = gpo.active_producers.producers[i].producer_name; @@ -657,12 +658,31 @@ BOOST_FIXTURE_TEST_CASE(db_tests, tester) { try { */ } FC_LOG_AND_RETHROW() } +/************************************************************************************* + * multi_index_tests test case + *************************************************************************************/ +BOOST_FIXTURE_TEST_CASE(multi_index_tests, tester) { try { + produce_blocks(1); + create_account( N(testapi) ); + produce_blocks(1); + set_code( N(testapi), test_api_multi_index_wast ); + produce_blocks(1); + + CALL_TEST_FUNCTION( *this, "test_multi_index", "idx64_general", {}); + CALL_TEST_FUNCTION( *this, "test_multi_index", "idx64_store_only", {}); + CALL_TEST_FUNCTION( *this, "test_multi_index", "idx64_check_without_storing", {}); + CALL_TEST_FUNCTION( *this, "test_multi_index", "idx128_autoincrement_test", {}); + CALL_TEST_FUNCTION( *this, "test_multi_index", "idx128_autoincrement_test_part1", {}); + CALL_TEST_FUNCTION( *this, "test_multi_index", "idx128_autoincrement_test_part2", {}); + CALL_TEST_FUNCTION( *this, "test_multi_index", "idx256_general", {}); +} FC_LOG_AND_RETHROW() } + /************************************************************************************* * fixedpoint_tests test case *************************************************************************************/ BOOST_FIXTURE_TEST_CASE(fixedpoint_tests, tester) { try { produce_blocks(2); - create_account( N(testapi) ); + create_account( N(testapi) ); produce_blocks(1000); set_code( N(testapi), test_api_wast ); produce_blocks(1000); @@ -686,7 +706,7 @@ BOOST_FIXTURE_TEST_CASE(fixedpoint_tests, tester) { try { *************************************************************************************/ BOOST_FIXTURE_TEST_CASE(real_tests, tester) { try { produce_blocks(1000); - create_account(N(testapi) ); + create_account(N(testapi) ); produce_blocks(1000); set_code(N(testapi), test_api_wast); produce_blocks(1000); @@ -711,7 +731,7 @@ BOOST_FIXTURE_TEST_CASE(real_tests, tester) { try { *************************************************************************************/ BOOST_FIXTURE_TEST_CASE(crypto_tests, tester) { try { produce_blocks(1000); - create_account(N(testapi) ); + create_account(N(testapi) ); produce_blocks(1000); set_code(N(testapi), test_api_wast); produce_blocks(1000); @@ -829,7 +849,7 @@ BOOST_FIXTURE_TEST_CASE(memory_tests, tester) { try { *************************************************************************************/ BOOST_FIXTURE_TEST_CASE(extended_memory_test_initial_memory, tester) { try { produce_blocks(1000); - create_account(N(testapi) ); + create_account(N(testapi) ); produce_blocks(1000); set_code(N(testapi), test_api_mem_wast); produce_blocks(1000); @@ -838,7 +858,7 @@ BOOST_FIXTURE_TEST_CASE(extended_memory_test_initial_memory, tester) { try { BOOST_FIXTURE_TEST_CASE(extended_memory_test_page_memory, tester) { try { produce_blocks(1000); - create_account(N(testapi) ); + create_account(N(testapi) ); produce_blocks(1000); set_code(N(testapi), test_api_mem_wast); produce_blocks(1000); @@ -857,7 +877,7 @@ BOOST_FIXTURE_TEST_CASE(extended_memory_test_page_memory_exceeded, tester) { try BOOST_FIXTURE_TEST_CASE(extended_memory_test_page_memory_negative_bytes, tester) { try { produce_blocks(1000); - create_account(N(testapi) ); + create_account(N(testapi) ); produce_blocks(1000); set_code(N(testapi), test_api_mem_wast); produce_blocks(1000); @@ -871,10 +891,10 @@ BOOST_FIXTURE_TEST_CASE(extended_memory_test_page_memory_negative_bytes, tester) *************************************************************************************/ BOOST_FIXTURE_TEST_CASE(print_tests, tester) { try { produce_blocks(2); - create_account(N(testapi) ); + create_account(N(testapi) ); produce_blocks(1000); - set_code(N(testapi), test_api_wast); + set_code(N(testapi), test_api_wast); produce_blocks(1000); string captured = ""; @@ -888,8 +908,8 @@ BOOST_FIXTURE_TEST_CASE(print_tests, tester) { try { // test printi CAPTURE_AND_PRE_TEST_PRINT("test_printi"); - BOOST_CHECK_EQUAL( captured.substr(0,1), U64Str(0) ); - BOOST_CHECK_EQUAL( captured.substr(1,6), U64Str(556644) ); + BOOST_CHECK_EQUAL( captured.substr(0,1), U64Str(0) ); + BOOST_CHECK_EQUAL( captured.substr(1,6), U64Str(556644) ); BOOST_CHECK_EQUAL( captured.substr(7, capture[3].size()), U64Str(-1) ); // "18446744073709551615" // test printn @@ -908,7 +928,7 @@ BOOST_FIXTURE_TEST_CASE(print_tests, tester) { try { BOOST_CHECK_EQUAL( captured.substr(0, 39), U128Str(-1) ); BOOST_CHECK_EQUAL( captured.substr(39, 1), U128Str(0) ); BOOST_CHECK_EQUAL( captured.substr(40, 11), U128Str(87654323456) ); - + } FC_LOG_AND_RETHROW() } @@ -917,7 +937,7 @@ BOOST_FIXTURE_TEST_CASE(print_tests, tester) { try { *************************************************************************************/ BOOST_FIXTURE_TEST_CASE(math_tests, tester) { try { produce_blocks(1000); - create_account( N(testapi) ); + create_account( N(testapi) ); produce_blocks(1000); produce_blocks(1000); @@ -950,7 +970,7 @@ BOOST_FIXTURE_TEST_CASE(math_tests, tester) { try { return expect_assert_message(e, "divide by zero"); } ); - + CALL_TEST_FUNCTION( *this, "test_math", "test_double_api", {}); union { @@ -970,7 +990,7 @@ BOOST_FIXTURE_TEST_CASE(math_tests, tester) { try { return expect_assert_message(e, "divide by zero"); } ); - + } FC_LOG_AND_RETHROW() } @@ -979,7 +999,7 @@ BOOST_FIXTURE_TEST_CASE(math_tests, tester) { try { *************************************************************************************/ BOOST_FIXTURE_TEST_CASE(types_tests, tester) { try { produce_blocks(1000); - create_account( N(testapi) ); + create_account( N(testapi) ); produce_blocks(1000); set_code( N(testapi), test_api_wast ); @@ -997,8 +1017,8 @@ BOOST_FIXTURE_TEST_CASE(types_tests, tester) { try { *************************************************************************************/ BOOST_FIXTURE_TEST_CASE(privileged_tests, tester) { try { produce_blocks(2); - create_account( N(testapi) ); - create_account( N(acc1) ); + create_account( N(testapi) ); + create_account( N(acc1) ); produce_blocks(100); set_code( N(testapi), test_api_wast ); produce_blocks(1); @@ -1008,7 +1028,7 @@ BOOST_FIXTURE_TEST_CASE(privileged_tests, tester) { try { auto pl = vector{{config::system_account_name, config::active_name}}; action act(pl, test_chain_action()); - vector prod_keys = { + vector prod_keys = { { N(inita), get_public_key( N(inita), "active" ) }, { N(initb), get_public_key( N(initb), "active" ) }, { N(initc), get_public_key( N(initc), "active" ) }, @@ -1038,14 +1058,14 @@ BOOST_FIXTURE_TEST_CASE(privileged_tests, tester) { try { trx.actions.push_back(act); set_tapos(trx); - + auto sigs = trx.sign(get_private_key(config::system_account_name, "active"), chain_id_type()); trx.get_signature_keys(chain_id_type() ); auto res = push_transaction(trx); BOOST_CHECK_EQUAL(res.status, transaction_receipt::executed); } - CALL_TEST_FUNCTION( *this, "test_privileged", "test_is_privileged", {} ); + CALL_TEST_FUNCTION( *this, "test_privileged", "test_is_privileged", {} ); BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( *this, "test_privileged", "test_is_privileged", {} ), fc::assert_exception, [](const fc::assert_exception& e) { return expect_assert_message(e, "context.privileged: testapi does not have permission to call this API"); diff --git a/tests/tests/abi_tests.cpp b/tests/tests/abi_tests.cpp index f3cfed2a9f95c415395918707804fe0272c9cad9..648e952df179e61e44afd783fbc827eee8e2b7c9 100644 --- a/tests/tests/abi_tests.cpp +++ b/tests/tests/abi_tests.cpp @@ -310,7 +310,7 @@ const char* my_abi = R"=====( BOOST_AUTO_TEST_CASE(uint_types) { try { - + const char* currency_abi = R"=====( { "types": [], @@ -365,7 +365,7 @@ struct abi_gen_helper { static bool is_abi_generation_exception(const eosio::abi_generation_exception& e) { return true; }; bool generate_abi(const char* source, const char* abi, bool opt_sfs=false) { - + std::string include_param = std::string("-I") + eosiolib_path; std::string stdcpp_include_param = std::string("-I") + eosiolib_path + "/libc++/upstream/include"; std::string stdc_include_param = std::string("-I") + eosiolib_path + "/musl/upstream/include"; @@ -447,7 +447,7 @@ BOOST_FIXTURE_TEST_CASE(abigen_all_types, abi_gen_helper) uint32_t field11; uint64_t field12; uint128_t field13; - uint256 field14; + //uint256 field14; int8_t field15; int16_t field16; int32_t field17; @@ -522,9 +522,6 @@ BOOST_FIXTURE_TEST_CASE(abigen_all_types, abi_gen_helper) },{ "name": "field13", "type": "uint128" - },{ - "name": "field14", - "type": "uint256" },{ "name": "field15", "type": "int8" diff --git a/tests/wasm_tests/identity_tests.cpp b/tests/wasm_tests/identity_tests.cpp index 3f7378efab03e8a99b4e8c554bbaae0ce3f87872..f2131da3f4d5f4da31f947e2ae11a019f39cf510 100644 --- a/tests/wasm_tests/identity_tests.cpp +++ b/tests/wasm_tests/identity_tests.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include @@ -11,6 +13,9 @@ #include #include +#include + +#include #include "test_wasts.hpp" @@ -47,6 +52,7 @@ public: abi_ser_test.set_abi(abi_test); const global_property_object &gpo = control->get_global_properties(); + FC_ASSERT(0 < gpo.active_producers.producers.size(), "No producers"); producer_name = (string)gpo.active_producers.producers.front().producer_name; } @@ -112,7 +118,7 @@ public: BOOST_REQUIRE_EQUAL(idnt, itr->primary_key); vector data; - read_only::copy_row(*itr, data); + read_only::copy_inline_row(*itr, data); return abi_ser.binary_to_variant("identrow", data); } @@ -131,17 +137,23 @@ public: fc::variant get_certrow(uint64_t identity, const string& property, uint64_t trusted, const string& certifier) { const auto& db = control->get_database(); - const auto* t_id = db.find(boost::make_tuple(N(identity), identity, N(certs))); + const auto* t_id = db.find(boost::make_tuple(N(identity), identity, N( certs ))); FC_ASSERT(t_id != 0, "certrow not found"); - uint64_t prop = string_to_name(property.c_str()); - uint64_t cert = string_to_name(certifier.c_str()); - const auto& idx = db.get_index(); - auto itr = idx.lower_bound(boost::make_tuple(t_id->id, prop, trusted, cert)); + const auto& idx = db.get_index(); + auto key = key256::make_from_word_sequence(string_to_name(property.c_str()), trusted, string_to_name(certifier.c_str())); + + auto itr = idx.lower_bound(boost::make_tuple(t_id->id, key.get_array())); + if (itr != idx.end() && itr->t_id == t_id->id && itr->secondary_key == key.get_array()) { + auto primary_key = itr->primary_key; + const auto& idx = db.get_index(); - if (itr != idx.end() && itr->t_id == t_id->id && prop == itr->primary_key && trusted == itr->secondary_key && cert == itr->tertiary_key) { + auto itr = idx.lower_bound(boost::make_tuple(t_id->id, primary_key)); + FC_ASSERT( itr != idx.end() && itr->t_id == t_id->id && primary_key == itr->primary_key, + "Record found in secondary index, but not found in primary index." + ); vector data; - read_only::copy_row(*itr, data); + read_only::copy_inline_row(*itr, data); return abi_ser.binary_to_variant("certrow", data); } else { return fc::variant(nullptr); @@ -159,7 +171,7 @@ public: auto itr = idx.lower_bound(boost::make_tuple(t_id->id, N(account))); if( itr != idx.end() && itr->t_id == t_id->id && N(account) == itr->primary_key) { vector data; - read_only::copy_row(*itr, data); + read_only::copy_inline_row(*itr, data); return abi_ser.binary_to_variant("accountrow", data); } else { return fc::variant(nullptr); @@ -658,6 +670,6 @@ BOOST_FIXTURE_TEST_CASE( ownership_contradiction, identity_tester ) try { BOOST_REQUIRE_EQUAL(0, get_identity_for_account("alice")); } FC_LOG_AND_RETHROW() //ownership_contradiction -#endif +#endif BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/wasm_tests/multi_index_tests.cpp b/tests/wasm_tests/multi_index_tests.cpp index 4f7f6c1d0cf6861e68e8d675706f3835a24cf04b..52f7d20ea4a63fdfa6e45b47e7b0f6d8dd28781d 100644 --- a/tests/wasm_tests/multi_index_tests.cpp +++ b/tests/wasm_tests/multi_index_tests.cpp @@ -9,7 +9,9 @@ #include #include +#include +using namespace eosio::chain::contracts; using namespace eosio::testing; BOOST_AUTO_TEST_SUITE(multi_index_tests) @@ -23,9 +25,47 @@ BOOST_FIXTURE_TEST_CASE( multi_index_load, tester ) try { set_code( N(multitest), multi_index_test_wast ); set_abi( N(multitest), multi_index_test_abi ); - produce_blocks(1); -} FC_LOG_AND_RETHROW() + abi_serializer abi_ser(json::from_string(multi_index_test_abi).as()); + + signed_transaction trx1; + { + auto& trx = trx1; + action trigger_act; + trigger_act.account = N(multitest); + trigger_act.name = N(trigger); + trigger_act.authorization = vector{{N(multitest), config::active_name}}; + trigger_act.data = abi_ser.variant_to_binary("trigger", mutable_variant_object() + ("what", 0) + ); + trx.actions.emplace_back(std::move(trigger_act)); + set_tapos(trx); + trx.sign(get_private_key(N(multitest), "active"), chain_id_type()); + push_transaction(trx); + } + + signed_transaction trx2; + { + auto& trx = trx2; + + action trigger_act; + trigger_act.account = N(multitest); + trigger_act.name = N(trigger); + trigger_act.authorization = vector{{N(multitest), config::active_name}}; + trigger_act.data = abi_ser.variant_to_binary("trigger", mutable_variant_object() + ("what", 1) + ); + trx.actions.emplace_back(std::move(trigger_act)); + set_tapos(trx); + trx.sign(get_private_key(N(multitest), "active"), chain_id_type()); + push_transaction(trx); + } + + produce_block(); + BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx1.id())); + BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx2.id())); + +} FC_LOG_AND_RETHROW() BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/wasm_tests/wasm_tests.cpp b/tests/wasm_tests/wasm_tests.cpp index 138b005a41d7cbf15f73b4c5da5cd131cf1e1ea9..f7098181f7460e033147d5ec5552ae05fe7c7a05 100644 --- a/tests/wasm_tests/wasm_tests.cpp +++ b/tests/wasm_tests/wasm_tests.cpp @@ -21,6 +21,9 @@ #include "test_wasts.hpp" +#include +#include + using namespace eosio; using namespace eosio::chain; using namespace eosio::chain::contracts; @@ -430,7 +433,7 @@ BOOST_FIXTURE_TEST_CASE( lotso_globals, tester ) try { //add a few immutable ones for good measure for(unsigned int i = 0; i < 10; ++i) ss << "(global $g" << i+200 << " i32 (i32.const 0))"; - + set_code(N(globals), string(ss.str() + ")") .c_str());