Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Add payment support for kv storage #9298

Merged
merged 7 commits into from
Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 37 additions & 25 deletions libraries/chain/apply_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,32 @@ apply_context::apply_context(controller& con, transaction_context& trx_ctx, uint
context_free = trace.context_free;
}

template <typename Exception>
void apply_context::check_unprivileged_resource_usage(const char* resource, const flat_set<account_delta>& deltas) {
const size_t checktime_interval = 10;
const bool not_in_notify_context = (receiver == act->account);
size_t counter = 0;
for (const auto& entry : deltas) {
if (counter == checktime_interval) {
trx_context.checktime();
counter = 0;
}
if (entry.delta > 0 && entry.account != receiver) {
EOS_ASSERT(not_in_notify_context, Exception,
"unprivileged contract cannot increase ${resource} usage of another account within a notify context: "
"${account}",
("resource", resource)
("account", entry.account));
EOS_ASSERT(has_authorization(entry.account), Exception,
"unprivileged contract cannot increase ${resource} usage of another account that has not authorized the "
"action: ${account}",
("resource", resource)
("account", entry.account));
}
++counter;
}
}

void apply_context::exec_one()
{
auto start = fc::time_point::now();
Expand Down Expand Up @@ -95,27 +121,12 @@ void apply_context::exec_one()
} catch( const wasm_exit& ) {}
}

if( !privileged && control.is_builtin_activated( builtin_protocol_feature_t::ram_restrictions ) ) {
const size_t checktime_interval = 10;
size_t counter = 0;
bool not_in_notify_context = (receiver == act->account);
const auto end = _account_ram_deltas.end();
for( auto itr = _account_ram_deltas.begin(); itr != end; ++itr, ++counter ) {
if( counter == checktime_interval ) {
trx_context.checktime();
counter = 0;
}
if( itr->delta > 0 && itr->account != receiver ) {
EOS_ASSERT( not_in_notify_context, unauthorized_ram_usage_increase,
"unprivileged contract cannot increase RAM usage of another account within a notify context: ${account}",
("account", itr->account)
);
EOS_ASSERT( has_authorization( itr->account ), unauthorized_ram_usage_increase,
"unprivileged contract cannot increase RAM usage of another account that has not authorized the action: ${account}",
("account", itr->account)
);
}
if (!privileged) {
if (control.is_builtin_activated(builtin_protocol_feature_t::ram_restrictions)) {
check_unprivileged_resource_usage<unauthorized_ram_usage_increase>("RAM", _account_ram_deltas);
}

check_unprivileged_resource_usage<unauthorized_disk_usage_increase>("DISK", _account_disk_deltas);
}
}
} FC_RETHROW_EXCEPTIONS( warn, "pending console output: ${console}", ("console", _pending_console_output) )
Expand Down Expand Up @@ -1118,8 +1129,8 @@ int64_t apply_context::kv_erase(uint64_t db, uint64_t contract, const char* key,
return kv_get_db(db).kv_erase(contract, key, key_size);
}

int64_t apply_context::kv_set(uint64_t db, uint64_t contract, const char* key, uint32_t key_size, const char* value, uint32_t value_size) {
return kv_get_db(db).kv_set(contract, key, key_size, value, value_size);
int64_t apply_context::kv_set(uint64_t db, uint64_t contract, const char* key, uint32_t key_size, const char* value, uint32_t value_size, account_name payer) {
return kv_get_db(db).kv_set(contract, key, key_size, value, value_size, payer);
}

bool apply_context::kv_get(uint64_t db, uint64_t contract, const char* key, uint32_t key_size, uint32_t& value_size) {
Expand Down Expand Up @@ -1241,10 +1252,11 @@ void apply_context::add_ram_usage( account_name account, int64_t ram_delta, cons
}
}

void apply_context::add_disk_usage( account_name account, int64_t disk_delta, const storage_usage_trace& trace ) {
trx_context.add_disk_usage( account, disk_delta, trace );
void apply_context::add_disk_usage( account_name payer, int64_t disk_delta, const storage_usage_trace& trace ) {

trx_context.add_disk_usage( payer, disk_delta, trace );

auto p = _account_disk_deltas.emplace( account, disk_delta );
auto p = _account_disk_deltas.emplace( payer, disk_delta );
if( !p.second ) {
p.first->delta += disk_delta;
}
Expand Down
6 changes: 3 additions & 3 deletions libraries/chain/block_log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ namespace eosio { namespace chain {
boost::iostreams::mapped_file_sink index;
};

struct bad_block_excpetion {
struct bad_block_exception {
std::exception_ptr inner;
};

Expand Down Expand Up @@ -356,7 +356,7 @@ namespace eosio { namespace chain {
try {
unpack(ds, entry);
} catch (...) {
throw bad_block_excpetion{std::current_exception()};
throw bad_block_exception{std::current_exception()};
}

const block_header& header = get_block_header(entry);
Expand Down Expand Up @@ -922,7 +922,7 @@ namespace eosio { namespace chain {
pos = ds.tellp();
}
}
catch (const bad_block_excpetion& e) {
catch (const bad_block_exception& e) {
write_incomplete_block_data(blocks_dir, now, block_num, log_data.data() + pos, log_data.size() - pos);
std::rethrow_exception(e.inner);
}
Expand Down
9 changes: 7 additions & 2 deletions libraries/chain/include/eosio/chain/apply_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,9 @@ class apply_context {
uint32_t schedule_action( uint32_t ordinal_of_action_to_schedule, account_name receiver, bool context_free );
uint32_t schedule_action( action&& act_to_schedule, account_name receiver, bool context_free );

private:
template <typename Exception>
void check_unprivileged_resource_usage(const char* resource, const flat_set<account_delta>& deltas);

/// Authorization methods:
public:
Expand Down Expand Up @@ -564,7 +567,7 @@ class apply_context {
/// KV Database methods:
public:
int64_t kv_erase(uint64_t db, uint64_t contract, const char* key, uint32_t key_size);
int64_t kv_set(uint64_t db, uint64_t contract, const char* key, uint32_t key_size, const char* value, uint32_t value_size);
int64_t kv_set(uint64_t db, uint64_t contract, const char* key, uint32_t key_size, const char* value, uint32_t value_size, account_name payer);
bool kv_get(uint64_t db, uint64_t contract, const char* key, uint32_t key_size, uint32_t& value_size);
uint32_t kv_get_data(uint64_t db, uint32_t offset, char* data, uint32_t data_size);
uint32_t kv_it_create(uint64_t db, uint64_t contract, const char* prefix, uint32_t size);
Expand All @@ -579,6 +582,8 @@ class apply_context {
int32_t kv_it_key(uint32_t itr, uint32_t offset, char* dest, uint32_t size, uint32_t& actual_size);
int32_t kv_it_value(uint32_t itr, uint32_t offset, char* dest, uint32_t size, uint32_t& actual_size);

void add_disk_usage( account_name account, int64_t disk_delta, const storage_usage_trace& trace );

private:
kv_context& kv_get_db(uint64_t db);
void kv_check_iterator(uint32_t itr);
Expand All @@ -596,7 +601,7 @@ class apply_context {
uint64_t next_auth_sequence( account_name actor );

void add_ram_usage( account_name account, int64_t ram_delta, const storage_usage_trace& trace );
void add_disk_usage( account_name account, int64_t disk_delta, const storage_usage_trace& trace );

void finalize_trace( action_trace& trace, const fc::time_point& start );

bool is_context_free()const { return context_free; }
Expand Down
2 changes: 2 additions & 0 deletions libraries/chain/include/eosio/chain/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ namespace eosio { namespace chain {
3050011, "eosio_assert_code assertion failure uses restricted error code value" )
FC_DECLARE_DERIVED_EXCEPTION( inline_action_too_big_nonprivileged, action_validate_exception,
3050012, "Inline action exceeds maximum size limit for a non-privileged account" )
FC_DECLARE_DERIVED_EXCEPTION( unauthorized_disk_usage_increase, action_validate_exception,
3050013, "Action attempts to increase disk usage of account without authorization" )

FC_DECLARE_DERIVED_EXCEPTION( database_exception, chain_exception,
3060000, "Database exception" )
Expand Down
6 changes: 4 additions & 2 deletions libraries/chain/include/eosio/chain/kv_chainbase_objects.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace eosio { namespace chain {
name contract;
shared_blob kv_key;
shared_blob kv_value;
name payer;
};

using kv_index = chainbase::shared_multi_index_container<
Expand All @@ -34,11 +35,12 @@ namespace config {
template<>
struct billable_size<kv_object> {
static constexpr uint64_t overhead = overhead_per_row_per_index_ram_bytes * 2;
static constexpr uint64_t value = 24 + (8 + 4) * 2 + overhead; ///< 24 for fixed fields 8 for vector data 4 for vector size
static constexpr uint64_t serialized_kv_object_size = sizeof(kv_object) - 2*sizeof(shared_blob) + (8 + 4) * 2; ///< 8 for vector data 4 for vector size
static constexpr uint64_t value = serialized_kv_object_size + overhead;
};
} // namespace config

}} // namespace eosio::chain

CHAINBASE_SET_INDEX_TYPE(eosio::chain::kv_object, eosio::chain::kv_index)
FC_REFLECT(eosio::chain::kv_object, (database_id)(contract)(kv_key)(kv_value))
FC_REFLECT(eosio::chain::kv_object, (database_id)(contract)(kv_key)(kv_value)(payer))
7 changes: 4 additions & 3 deletions libraries/chain/include/eosio/chain/kv_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <eosio/chain/name.hpp>
#include <eosio/chain/kv_config.hpp>
#include <eosio/chain/types.hpp>
#include <memory>
#include <stdint.h>

Expand Down Expand Up @@ -60,10 +61,10 @@ namespace eosio { namespace chain {
};

struct kv_resource_manager {
void update_table_usage(int64_t delta, const kv_resource_trace& trace) { return _update_table_usage(*_context, delta, trace); }
void update_table_usage(account_name payer, int64_t delta, const kv_resource_trace& trace) { return _update_table_usage(*_context, delta, trace, payer); }
apply_context* _context;
uint64_t billable_size;
void (*_update_table_usage)(apply_context&, int64_t delta, const kv_resource_trace& trace);
void (*_update_table_usage)(apply_context&, int64_t delta, const kv_resource_trace& trace, account_name payer);
};

kv_resource_manager create_kv_resource_manager_ram(apply_context& context);
Expand All @@ -74,7 +75,7 @@ namespace eosio { namespace chain {

virtual int64_t kv_erase(uint64_t contract, const char* key, uint32_t key_size) = 0;
virtual int64_t kv_set(uint64_t contract, const char* key, uint32_t key_size, const char* value,
uint32_t value_size) = 0;
uint32_t value_size, account_name payer) = 0;
virtual bool kv_get(uint64_t contract, const char* key, uint32_t key_size, uint32_t& value_size) = 0;
virtual uint32_t kv_get_data(uint32_t offset, char* data, uint32_t data_size) = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ namespace eosio { namespace chain { namespace webassembly {

// kv database api
int64_t kv_erase(uint64_t, uint64_t, span<const char>);
int64_t kv_set(uint64_t, uint64_t, span<const char>, span<const char>);
int64_t kv_set(uint64_t, uint64_t, span<const char>, span<const char>, account_name payer);
bool kv_get(uint64_t, uint64_t, span<const char>, uint32_t*);
uint32_t kv_get_data(uint64_t, uint32_t, span<char>);
uint32_t kv_it_create(uint64_t, uint64_t, span<const char>);
Expand Down
8 changes: 4 additions & 4 deletions libraries/chain/kv_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@
namespace eosio { namespace chain {

namespace {
void kv_resource_manager_update_ram(apply_context& context, int64_t delta, const kv_resource_trace& trace) {
void kv_resource_manager_update_ram(apply_context& context, int64_t delta, const kv_resource_trace& trace, account_name payer) {
std::string event_id;
if (context.control.get_deep_mind_logger() != nullptr) {
event_id = STORAGE_EVENT_ID("${id}", ("id", fc::to_hex(trace.key.data(), trace.key.size())));
}

context.add_ram_usage(account_name(context.get_receiver()), delta, storage_usage_trace(context.get_action_id(), event_id.c_str(), "kv", trace.op_to_string()));
context.update_db_usage(payer, delta, storage_usage_trace(context.get_action_id(), event_id.c_str(), "kv", trace.op_to_string()));
}

void kv_resource_manager_update_disk(apply_context& context, int64_t delta, const kv_resource_trace& trace) {
void kv_resource_manager_update_disk(apply_context& context, int64_t delta, const kv_resource_trace& trace, account_name payer) {
std::string event_id;
if (context.control.get_deep_mind_logger() != nullptr) {
event_id = STORAGE_EVENT_ID("${id}", ("id", fc::to_hex(trace.key.data(), trace.key.size())));
}

context.add_disk_usage(account_name(context.get_receiver()), delta, storage_usage_trace(context.get_action_id(), event_id.c_str(), "kv", trace.op_to_string()));
context.add_disk_usage(payer, delta, storage_usage_trace(context.get_action_id(), event_id.c_str(), "kv", trace.op_to_string()));
}
}
kv_resource_manager create_kv_resource_manager_ram(apply_context& context) {
Expand Down
32 changes: 24 additions & 8 deletions libraries/chain/kv_context_chainbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,14 @@ namespace eosio { namespace chain {
if (!kv)
return 0;
int64_t resource_delta = -(static_cast<int64_t>(resource_manager.billable_size) + kv->kv_key.size() + kv->kv_value.size());
resource_manager.update_table_usage(resource_delta, kv_resource_trace(key, key_size, kv_resource_trace::operation::erase));
resource_manager.update_table_usage(kv->payer, resource_delta, kv_resource_trace(key, key_size, kv_resource_trace::operation::erase));

if (auto dm_logger = resource_manager._context->control.get_deep_mind_logger()) {
fc_dlog(*dm_logger, "KV_OP REM ${action_id} ${db} ${payer} ${key} ${odata}",
("action_id", resource_manager._context->get_action_id())
("db", database_id)
("payer", name{ contract })
("contract", name{ contract })
("payer", kv->payer)
("key", fc::to_hex(kv->kv_key.data(), kv->kv_key.size()))
("odata", fc::to_hex(kv->kv_value.data(), kv->kv_value.size()))
);
Expand All @@ -182,10 +183,11 @@ namespace eosio { namespace chain {
}

int64_t kv_set(uint64_t contract, const char* key, uint32_t key_size, const char* value,
uint32_t value_size) override {
uint32_t value_size, account_name payer) override {
EOS_ASSERT(name{ contract } == receiver, table_operation_not_permitted, "Can not write to this key");
EOS_ASSERT(key_size <= limits.max_key_size, kv_limit_exceeded, "Key too large");
EOS_ASSERT(value_size <= limits.max_value_size, kv_limit_exceeded, "Value too large");

temp_data_buffer.reset();
auto* kv = db.find<kv_object, by_kv_key>(
boost::make_tuple(database_id, name{ contract }, std::string_view{ key, key_size }));
Expand All @@ -194,37 +196,51 @@ namespace eosio { namespace chain {
int64_t old_size = static_cast<int64_t>(kv->kv_key.size()) + kv->kv_value.size();
int64_t new_size = static_cast<int64_t>(value_size) + key_size;
int64_t resource_delta = new_size - old_size;
resource_manager.update_table_usage(resource_delta, kv_resource_trace(key, key_size, kv_resource_trace::operation::update));

if (kv->payer != payer) {
// refund the existing payer
resource_manager.update_table_usage(kv->payer, -(old_size + resource_manager.billable_size), kv_resource_trace(key, key_size, kv_resource_trace::operation::update));
// charge the new payer
resource_manager.update_table_usage(payer, new_size + resource_manager.billable_size, kv_resource_trace(key, key_size, kv_resource_trace::operation::update));
} else if (old_size != new_size) {
resource_manager.update_table_usage(payer, resource_delta, kv_resource_trace(key, key_size, kv_resource_trace::operation::update));
}

if (auto dm_logger = resource_manager._context->control.get_deep_mind_logger()) {
fc_dlog(*dm_logger, "KV_OP UPD ${action_id} ${db} ${payer} ${key} ${odata}:${ndata}",
("action_id", resource_manager._context->get_action_id())
("db", database_id)
("payer", name{ contract })
("contract", name{ contract })
("payer", payer)
("key", fc::to_hex(kv->kv_key.data(), kv->kv_key.size()))
("odata", fc::to_hex(kv->kv_value.data(), kv->kv_value.size()))
("ndata", fc::to_hex(value, value_size))
);
}

db.modify(*kv, [&](auto& obj) { obj.kv_value.assign(value, value_size); });
db.modify(*kv, [&](auto& obj) {
obj.kv_value.assign(value, value_size);
obj.payer = payer;
});
return resource_delta;
} else {
int64_t new_size = static_cast<int64_t>(value_size) + key_size;
int64_t resource_delta = new_size + resource_manager.billable_size;
resource_manager.update_table_usage(resource_delta, kv_resource_trace(key, key_size, kv_resource_trace::operation::create));
resource_manager.update_table_usage(payer, resource_delta, kv_resource_trace(key, key_size, kv_resource_trace::operation::create));
db.create<kv_object>([&](auto& obj) {
obj.database_id = database_id;
obj.contract = name{ contract };
obj.kv_key.assign(key, key_size);
obj.kv_value.assign(value, value_size);
obj.payer = payer;
});

if (auto dm_logger = resource_manager._context->control.get_deep_mind_logger()) {
fc_dlog(*dm_logger, "KV_OP INS ${action_id} ${db} ${payer} ${key} ${ndata}",
("action_id", resource_manager._context->get_action_id())
("db", database_id)
("payer", name{ contract })
("contract", name{ contract })
("payer", payer)
("key", fc::to_hex(key, key_size))
("ndata", fc::to_hex(value, value_size))
);
Expand Down
4 changes: 2 additions & 2 deletions libraries/chain/webassembly/kv_database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ namespace eosio { namespace chain { namespace webassembly {
return context.kv_erase(db, contract, key.data(), key.size());
}

int64_t interface::kv_set(uint64_t db, uint64_t contract, span<const char> key, span<const char> value) {
return context.kv_set(db, contract, key.data(), key.size(), value.data(), value.size());
int64_t interface::kv_set(uint64_t db, uint64_t contract, span<const char> key, span<const char> value, account_name payer) {
return context.kv_set(db, contract, key.data(), key.size(), value.data(), value.size(), payer);
}

bool interface::kv_get(uint64_t db, uint64_t contract, span<const char> key, uint32_t* value_size) {
Expand Down
2 changes: 1 addition & 1 deletion pipeline.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"test":
[
{
"commit": "6de603529d962193eb64b3b47b2bfee9fbe185b4"
"commit": "e8e91bfa38aee023818921d3576283711ced7d55"
}
]
}
Expand Down
Loading