Skip to content

Commit

Permalink
core: dynamic fee algorithm from ArticMine
Browse files Browse the repository at this point in the history
The fee will vary based on the base reward and the current
block size limit:

fee = (R/R0) * (M0/M) * F0

R: base reward
R0: reference base reward (10 monero)
M: block size limit
M0: minimum block size limit (60000)
F0: 0.002 monero

Starts applying at v4
  • Loading branch information
moneromooo-monero committed Oct 31, 2016
1 parent 83b0511 commit 82dbba1
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/cryptonote_config.h
Expand Up @@ -64,6 +64,8 @@


#define FEE_PER_KB_OLD ((uint64_t)10000000000) // pow(10, 10) #define FEE_PER_KB_OLD ((uint64_t)10000000000) // pow(10, 10)
#define FEE_PER_KB ((uint64_t)2000000000) // 2 * pow(10, 9) #define FEE_PER_KB ((uint64_t)2000000000) // 2 * pow(10, 9)
#define DYNAMIC_FEE_PER_KB_BASE_FEE ((uint64_t)2000000000) // 2 * pow(10,9)
#define DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD ((uint64_t)10000000000000) // 10 * pow(10,12)


#define ORPHANED_BLOCKS_MAX_COUNT 100 #define ORPHANED_BLOCKS_MAX_COUNT 100


Expand Down Expand Up @@ -122,6 +124,8 @@


#define THREAD_STACK_SIZE 5 * 1024 * 1024 #define THREAD_STACK_SIZE 5 * 1024 * 1024


#define HF_VERSION_DYNAMIC_FEE 4

// New constants are intended to go here // New constants are intended to go here
namespace config namespace config
{ {
Expand Down
52 changes: 52 additions & 0 deletions src/cryptonote_core/blockchain.cpp
Expand Up @@ -46,6 +46,7 @@
#include "misc_language.h" #include "misc_language.h"
#include "profile_tools.h" #include "profile_tools.h"
#include "file_io_utils.h" #include "file_io_utils.h"
#include "common/int-util.h"
#include "common/boost_serialization_helper.h" #include "common/boost_serialization_helper.h"
#include "warnings.h" #include "warnings.h"
#include "crypto/hash.h" #include "crypto/hash.h"
Expand Down Expand Up @@ -2708,6 +2709,57 @@ void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const
result = crypto::check_ring_signature(tx_prefix_hash, key_image, p_output_keys, sig.data()) ? 1 : 0; result = crypto::check_ring_signature(tx_prefix_hash, key_image, p_output_keys, sig.data()) ? 1 : 0;
} }


//------------------------------------------------------------------
uint64_t Blockchain::get_dynamic_per_kb_fee(uint64_t block_reward, size_t median_block_size)
{
if (median_block_size < CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2)
median_block_size = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2;

uint64_t unscaled_fee_per_kb = (DYNAMIC_FEE_PER_KB_BASE_FEE * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / median_block_size);
uint64_t hi, lo = mul128(unscaled_fee_per_kb, block_reward, &hi);
static_assert(DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD % 1000000 == 0, "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD must be divisible by 1000000");
static_assert(DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000 <= std::numeric_limits<uint32_t>::max(), "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD is too large");
// divide in two steps, since the divisor must be 32 bits, but DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD isn't
div128_32(hi, lo, DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000, &hi, &lo);
div128_32(hi, lo, 1000000, &hi, &lo);
assert(hi == 0);

return lo;
}

//------------------------------------------------------------------
bool Blockchain::check_fee(size_t blob_size, uint64_t fee) const
{
const uint8_t version = get_current_hard_fork_version();

uint64_t fee_per_kb;
if (version < HF_VERSION_DYNAMIC_FEE)
{
fee_per_kb = FEE_PER_KB;
}
else
{
uint64_t median = m_current_block_cumul_sz_limit / 2;
uint64_t already_generated_coins = m_db->height() ? m_db->get_block_already_generated_coins(m_db->height() - 1) : 0;
uint64_t base_reward;
if (!get_block_reward(median, 1, already_generated_coins, base_reward, version))
return false;
fee_per_kb = get_dynamic_per_kb_fee(base_reward, median);
}
LOG_PRINT_L2("Using " << print_money(fee) << "/kB fee");

uint64_t needed_fee = blob_size / 1024;
needed_fee += (blob_size % 1024) ? 1 : 0;
needed_fee *= fee_per_kb;

if (fee < needed_fee)
{
LOG_PRINT_L1("transaction fee is not enough: " << print_money(fee) << ", minimum fee: " << print_money(needed_fee));
return false;
}
return true;
}

//------------------------------------------------------------------ //------------------------------------------------------------------
// This function checks to see if a tx is unlocked. unlock_time is either // This function checks to see if a tx is unlocked. unlock_time is either
// a block index or a unix time. // a block index or a unix time.
Expand Down
27 changes: 27 additions & 0 deletions src/cryptonote_core/blockchain.h
Expand Up @@ -512,6 +512,33 @@ namespace cryptonote
*/ */
bool check_tx_inputs(transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false); bool check_tx_inputs(transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false);


/**
* @brief get dynamic per kB fee for a given block size
*
* The dynamic fee is based on the block size in a past window, and
* the current block reward. It is expressed by kB.
*
* @param block_reward the current block reward
* @param median_block_size the median blob's size in the past window
*
* @return the per kB fee
*/
static uint64_t get_dynamic_per_kb_fee(uint64_t block_reward, size_t median_block_size);

/**
* @brief validate a transaction's fee
*
* This function validates the fee is enough for the transaction.
* This is based on the size of the transaction blob, and, after a
* height threshold, on the average size of transaction in a past window
*
* @param blob_size the transaction blob's size
* @param fee the fee
*
* @return true if the fee is enough, false otherwise
*/
bool check_fee(size_t blob_size, uint64_t fee) const;

/** /**
* @brief check that a transaction's outputs conform to current standards * @brief check that a transaction's outputs conform to current standards
* *
Expand Down
6 changes: 1 addition & 5 deletions src/cryptonote_core/tx_pool.cpp
Expand Up @@ -133,12 +133,8 @@ namespace cryptonote
fee = tx.rct_signatures.txnFee; fee = tx.rct_signatures.txnFee;
} }


uint64_t needed_fee = blob_size / 1024; if (!kept_by_block && !m_blockchain.check_fee(blob_size, fee))
needed_fee += (blob_size % 1024) ? 1 : 0;
needed_fee *= FEE_PER_KB;
if (!kept_by_block && fee < needed_fee)
{ {
LOG_PRINT_L1("transaction fee is not enough: " << print_money(fee) << ", minimum fee: " << print_money(needed_fee));
tvc.m_verifivation_failed = true; tvc.m_verifivation_failed = true;
tvc.m_fee_too_low = true; tvc.m_fee_too_low = true;
return false; return false;
Expand Down
1 change: 1 addition & 0 deletions tests/unit_tests/CMakeLists.txt
Expand Up @@ -39,6 +39,7 @@ set(unit_tests_sources
dns_resolver.cpp dns_resolver.cpp
epee_boosted_tcp_server.cpp epee_boosted_tcp_server.cpp
epee_levin_protocol_handler_async.cpp epee_levin_protocol_handler_async.cpp
fee.cpp
get_xtype_from_string.cpp get_xtype_from_string.cpp
main.cpp main.cpp
mnemonics.cpp mnemonics.cpp
Expand Down
121 changes: 121 additions & 0 deletions tests/unit_tests/fee.cpp
@@ -0,0 +1,121 @@
// Copyright (c) 2014-2016, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers

#include "gtest/gtest.h"

#include "cryptonote_core/blockchain.h"

using namespace cryptonote;

namespace
{
//--------------------------------------------------------------------------------------------------------------------
class fee : public ::testing::Test
{
};

TEST_F(fee, 10xmr)
{
// CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 and lower are clamped
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2), 2000000000);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 2), 2000000000);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 100), 2000000000);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, 1), 2000000000);

// higher is inverse proportional
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 2), 2000000000 / 2);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 10), 2000000000 / 10);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000), 2000000000 / 1000);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000000ull), 2000000000 / 1000000);
}

TEST_F(fee, 1xmr)
{
// CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 and lower are clamped
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2), 200000000);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 2), 200000000);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 100), 200000000);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, 1), 200000000);

// higher is inverse proportional
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 2), 200000000 / 2);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 10), 200000000 / 10);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000), 200000000 / 1000);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000000ull), 200000000 / 1000000);
}

TEST_F(fee, dot3xmr)
{
// CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 and lower are clamped
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2), 60000000);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 2), 60000000);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 100), 60000000);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, 1), 60000000);

// higher is inverse proportional
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 2), 60000000 / 2);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 10), 60000000 / 10);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000), 60000000 / 1000);
ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000000ull), 60000000 / 1000000);
}

static bool is_more_or_less(double x, double y)
{
return fabs(y - x) < 0.001;
}

static const double MAX_MULTIPLIER = 166.f;

TEST_F(fee, double_at_full)
{
static const uint64_t block_rewards[] = {
20000000000000ull, // 20 monero
13000000000000ull,
1000000000000ull,
600000000000ull, // .6 monero, minimum reward per block at 2min
300000000000ull, // .3 monero, minimum reward per block at 1min
};
static const uint64_t median_block_sizes[] = {
CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2,
CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 2,
CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 10,
CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000,
CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000000ull
};

for (uint64_t block_reward: block_rewards)
{
for (uint64_t median_block_size: median_block_sizes)
{
ASSERT_TRUE(is_more_or_less(Blockchain::get_dynamic_per_kb_fee(block_reward, median_block_size) * (median_block_size / 1024.) * MAX_MULTIPLIER / (double)block_reward, 1.992 * 1000 / 1024));
}
}
}
}

0 comments on commit 82dbba1

Please sign in to comment.