diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 1e1d8fac..7125932c 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,7 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); add_index< primary_index > >(); add_index< primary_index >(); diff --git a/libraries/chain/include/graphene/chain/file_object.hpp b/libraries/chain/include/graphene/chain/file_object.hpp new file mode 100644 index 00000000..33fc902a --- /dev/null +++ b/libraries/chain/include/graphene/chain/file_object.hpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2015, Cryptonomex, Inc. + * All rights reserved. + * + * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and + * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, + * are permitted until September 8, 2015, provided that the following conditions are met: + * + * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#pragma once + +namespace graphene { namespace chain { + + + /** + * @brief provides for persistant storage of arbitrary data + * + * Smart contracts need data to be stored persistantly that can be shared with + * other smart contracts. There is a cost associated with storing data, especially if + * that data will be kept in RAM. + * + * File objects allow smart contracts to interact with persistant storage much like + * traditional programs interact with files on disk. The cost of accessing a file + * object to modify it is much higher than the cost to simply read it because the + * the database must make a backup of the file for the undo history in the event + * of a blockchain reorganization or failure in evaluation. For this reason files + * are limited to 2^16 bytes and smart contracts will have to use multiple files if + * they need to store additional data. + * + * Every file has an automatic expiration date at which point in time it will be + * deleted unless a fee is paid to extend its life time. + * + * The contents of all files are public, but not to scripts. A smart contract attempting + * to access the contents of a file must have permission to read the file. The purpose + * of this restriction is to help users monetize the trust associated with publishing + * data. Anyone could re-publish the data under a new file, but the trust in the + * quality of the data would not be the same as the original file. + */ + class file_object : public graphene::db::annotated_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = file_object_type; + + /** + * @brief sets bits that control the permissions granted to smart contracts regarding this data. + */ + enum permision_flags + { + owner_read = 0x01, + owner_write = 0x02, + group_read = 0x04, + group_write = 0x08, + all_read = 0x10, + execute = 0x20 ///< set if data contains virtual machine instructions + }; + + /** + * The owner can access this file based upon the @ref permissions flags + * + * @note - if the owner removes write permission from himself then the file + * will be imutable thereafter. + */ + account_id_type owner; + /** any account that has been white listed by this group can read/write + * @ref data based upon the @ref permissions flags. + */ + account_id_type group; + /** + * Bits set according to @ref permission_flags + */ + uint8_t permissions = owner_read | owner_write | all_read; + + /** + * Files consume memory and thus are cleaned up unless a fee is paid to + * keep them alive. + */ + time_point_sec expiration; + + /** + * The maximum data size for a file is 2^16 bytes so that the + * undo history doesn't have to backup larger files. If a smart contract + * requires more data then it can create more file objects. + */ + vector data; + }; + + struct by_expiration; + struct by_owner; + struct by_group; + /** + * @ingroup object_index + */ + typedef multi_index_container< + file_object, + indexed_by< + hashed_unique< tag, member< object, object_id_type, &object::id > >, + hashed_non_unique< tag, member< file_object, account_id_type, &file_object::owner > >, + hashed_non_unique< tag, member< file_object, account_id_type, &file_object::group > >, + ordered_non_unique< tag, member > + > + > file_object_multi_index_type; + + typedef generic_index file_object_index; + +} } + +FC_REFLECT_ENUM( graphene::chain::file_object::permision_flags, (owner_read)(owner_write)(group_read)(group_write)(all_read)(execute) ) +FC_REFLECT_DERIVED( graphene::chain::file_object, (graphene::db::object), (owner)(group)(permissions)(expiration)(data) ) diff --git a/libraries/chain/include/graphene/chain/operations.hpp b/libraries/chain/include/graphene/chain/operations.hpp index 5b3cf85b..d53b649a 100644 --- a/libraries/chain/include/graphene/chain/operations.hpp +++ b/libraries/chain/include/graphene/chain/operations.hpp @@ -1248,6 +1248,81 @@ namespace graphene { namespace chain { } }; + /** + * @brief create/update the contents of a file. + * + * Any account may pay a fee and write no data to extend the lease seconds + * on a file. + * + * If the file size increasees, the current lease_seconds will be adjusted downward to maintain + * the same byte-days-leased. Then any new leased seconds will be added based upon the + * new file size. + * + * @see file_object + */ + struct file_write_operation + { + public: + /** + * The fee charges is proportional to @ref file_size * @ref lease_seconds + */ + asset fee; + /** + * THe account that is paying the update fee + */ + account_id_type payer; + + /** file_id 0 indicates a new file should be created */ + file_id_type file_id; + + /** may read/write accoding to flags, write permission is required to change owner/group/flags */ + account_id_type owner; + + /** may read/write according fo flags, but may not update flags or owner */ + account_id_type group; + + /** + * Must be less than or equal to 0x2f + */ + uint8_t flags = 0; + + /** + * If the file doesn't exist, it will be intialized to file_size with 0 + * before writing data. + * + * @pre data.size() + offset <= 2^16 + */ + uint16_t offset = 0; + vector data; + + /** + * The length of time to extend the lease on the file, must be less + * than 10 years. + */ + uint32_t lease_seconds = 0; + + /** + * File size must be greater than 0 + */ + uint16_t file_size = 0; + + /** + * If file_id is not 0, then precondition checksum verifies that + * the file contents are as expected prior to writing data. + */ + optional precondition_checksum; + + account_id_type fee_payer()const { return payer; } + void get_required_auth(flat_set& active_auth_set, flat_set&)const { active_auth_set.insert(fee_payer()); } + void validate()const; + share_type calculate_fee( const fee_schedule_type& k )const; + + void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const + { + acc.adjust( fee_payer(), -fee ); + } + }; + /** * @ingroup operations * @@ -1542,6 +1617,7 @@ namespace graphene { namespace chain { withdraw_permission_delete_operation, fill_order_operation, global_parameters_update_operation, + file_write_operation, vesting_balance_create_operation, vesting_balance_withdraw_operation, bond_create_offer_operation, @@ -1776,6 +1852,8 @@ FC_REFLECT( graphene::chain::withdraw_permission_claim_operation, (fee)(withdraw FC_REFLECT( graphene::chain::withdraw_permission_delete_operation, (fee)(withdraw_from_account)(authorized_account) (withdrawal_permission) ) +FC_REFLECT( graphene::chain::file_write_operation, (fee)(payer)(file_id)(owner)(group)(flags)(offset)(data)(lease_seconds)(file_size)(precondition_checksum) ) + FC_REFLECT( graphene::chain::bond_create_offer_operation, (fee)(creator)(offer_to_borrow)(amount)(min_match)(collateral_rate)(min_loan_period_sec)(loan_period_sec)(interest_apr) ) FC_REFLECT( graphene::chain::bond_cancel_offer_operation, (fee)(creator)(offer_id)(refund) ) FC_REFLECT( graphene::chain::bond_accept_offer_operation, (fee)(claimer)(lender)(borrower)(offer_id)(amount_borrowed)(amount_collateral) ) diff --git a/libraries/chain/include/graphene/chain/types.hpp b/libraries/chain/include/graphene/chain/types.hpp index 92c00b3a..4cad1f0f 100644 --- a/libraries/chain/include/graphene/chain/types.hpp +++ b/libraries/chain/include/graphene/chain/types.hpp @@ -116,6 +116,7 @@ namespace graphene { namespace chain { withdraw_permission_object_type, bond_offer_object_type, bond_object_type, + file_object_type, vesting_balance_object_type, worker_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types @@ -162,6 +163,7 @@ namespace graphene { namespace chain { class withdraw_permission_object; class bond_object; class bond_offer_object; + class file_object; class vesting_balance_object; class witness_schedule_object; class worker_object; @@ -181,6 +183,7 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, withdraw_permission_object_type,withdraw_permission_object> withdraw_permission_id_type; typedef object_id< protocol_ids, bond_offer_object_type, bond_offer_object> bond_offer_id_type; typedef object_id< protocol_ids, bond_object_type, bond_object> bond_id_type; + typedef object_id< protocol_ids, file_object_type, file_object> file_id_type; typedef object_id< protocol_ids, vesting_balance_object_type, vesting_balance_object> vesting_balance_id_type; typedef object_id< protocol_ids, worker_object_type, worker_object> worker_id_type; @@ -383,6 +386,7 @@ namespace graphene { namespace chain { uint32_t cancel_bond_offer_fee; uint32_t accept_bond_offer_fee; uint32_t claim_bond_collateral_fee; + uint32_t file_storage_fee_per_day; ///< the cost of leasing a file with 2^16 bytes for 1 day uint32_t vesting_balance_create_fee; uint32_t vesting_balance_withdraw_fee; uint32_t global_settle_fee; @@ -505,6 +509,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (withdraw_permission_object_type) (bond_offer_object_type) (bond_object_type) + (file_object_type) (vesting_balance_object_type) (worker_object_type) (OBJECT_TYPE_COUNT) @@ -563,6 +568,7 @@ FC_REFLECT( graphene::chain::fee_schedule_type, (cancel_bond_offer_fee) (accept_bond_offer_fee) (claim_bond_collateral_fee) + (file_storage_fee_per_day) (vesting_balance_create_fee) (vesting_balance_withdraw_fee) (global_settle_fee) @@ -611,6 +617,7 @@ FC_REFLECT_TYPENAME( graphene::chain::operation_history_id_type ) FC_REFLECT_TYPENAME( graphene::chain::withdraw_permission_id_type ) FC_REFLECT_TYPENAME( graphene::chain::bond_offer_id_type ) FC_REFLECT_TYPENAME( graphene::chain::bond_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::file_id_type ) FC_REFLECT_TYPENAME( graphene::chain::vesting_balance_id_type ) FC_REFLECT_TYPENAME( graphene::chain::worker_id_type ) FC_REFLECT_TYPENAME( graphene::chain::relative_key_id_type ) diff --git a/libraries/chain/operations.cpp b/libraries/chain/operations.cpp index 86073a71..8805895f 100644 --- a/libraries/chain/operations.cpp +++ b/libraries/chain/operations.cpp @@ -717,6 +717,20 @@ void asset_update_feed_producers_operation::validate() const { FC_ASSERT( fee.amount >= 0 ); } +void file_write_operation::validate()const +{ + FC_ASSERT( uint32_t(offset) + data.size() <= file_size ); + FC_ASSERT( flags <= 0x2f ); + FC_ASSERT( file_size > 0 ); + /** less than 10 years to prevent overflow of 64 bit numbers in the value*lease_seconds*file_size calculation */ + FC_ASSERT( lease_seconds < 60*60*24*365*10 ); +} + +share_type file_write_operation::calculate_fee( const fee_schedule_type& k )const +{ + return ((((k.file_storage_fee_per_day * lease_seconds)/(60*60*24))*file_size)/0xff) + ((data.size() * k.data_fee)/1024); +} + void vesting_balance_create_operation::get_required_auth(flat_set& active_auth_set, flat_set&)const { diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index 77badee4..6ecc2c4f 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -173,6 +173,9 @@ struct operation_get_impacted_accounts } void operator()( const asset_create_operation& o )const { } + void operator()( const file_write_operation& o )const { + _impacted.insert( o.owner ); + } void operator()( const asset_update_operation& o )const { if( o.new_issuer ) diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index deefe0a1..048ed44a 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -68,7 +69,7 @@ string remove_namespace( string str ) template void generate_serializer(); -template +template void register_serializer(); @@ -95,7 +96,7 @@ struct js_name> }; template struct js_name> { static std::string name(){ return "bytes "+ fc::to_string(N); }; }; template struct js_name> { static std::string name(){ return "bytes "+ fc::to_string(N); }; }; -template struct js_name< fc::optional > { static std::string name(){ return "optional " + js_name::name(); } }; +template struct js_name< fc::optional > { static std::string name(){ return "optional " + js_name::name(); } }; template<> struct js_name< object_id_type > { static std::string name(){ return "object_id_type"; } }; template struct js_name< fc::flat_set > { static std::string name(){ return "set " + js_name::name(); } }; template struct js_name< std::vector > { static std::string name(){ return "array " + js_name::name(); } }; @@ -114,8 +115,8 @@ template<> struct js_name< time_point_sec > { static std::string name(){ retu template struct js_name > { - static std::string name(){ - return "protocol_id_type \"" + remove_namespace(fc::get_typename::name()) + "\""; + static std::string name(){ + return "protocol_id_type \"" + remove_namespace(fc::get_typename::name()) + "\""; }; }; @@ -131,7 +132,7 @@ struct js_name< fc::flat_map > { static std::string name(){ return "map (" template struct js_sv_name; -template struct js_sv_name +template struct js_sv_name { static std::string name(){ return "\n " + js_name::name(); } }; template @@ -141,10 +142,10 @@ struct js_sv_name { static std::string name(){ return "\n " + js_nam template struct js_name< fc::static_variant > { - static std::string name( std::string n = ""){ + static std::string name( std::string n = ""){ static const std::string name = n; if( name == "" ) - return "static_variant [" + js_sv_name::name() + "\n]"; + return "static_variant [" + js_sv_name::name() + "\n]"; else return name; } }; @@ -289,7 +290,7 @@ class register_member_visitor } }; -template +template struct serializer { static_assert( fc::reflector::is_defined::value == reflected, "invalid template arguments" );