Skip to content

Commit

Permalink
Merge branch 'master' into steem-prerelease-v0.19.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Vandeberg committed May 19, 2017
2 parents 631c32c + deb0dac commit a3f8e73
Show file tree
Hide file tree
Showing 19 changed files with 693 additions and 115 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ tests/performance_test

doxygen

build

wallet.json
witness_node_data_dir

Expand Down
128 changes: 128 additions & 0 deletions doc/sqrt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@

# Introduction

In this document we derive the approximate integer square root function used by Steem for the curation curve
[here](https://github.com/steemit/steem/issues/1052).

# MSB function

Let function `msb(x)` be defined as follows for `x >= 1`:

- (Definition 1a) `msb(x)` is the index of the most-significant 1 bit in the binary representation of `x`

The following definitions are equivalent to Definition (1a):

- (Definition 1b) `msb(x)` is the length of the binary representation of `x` as a string of bits, minus one
- (Definition 1c) `msb(x)` is the greatest integer such that `2 ^ msb(x) <= x`
- (Definition 1d) `msb(x) = floor(log_2(x))`

Many CPU's (including Intel CPU's since the Intel 386) can compute the `msb()` function very quickly on
machine-word-size integers with a special instruction directly implemented in hardware. In C++, the
Boost library provides reasonably compiler-independent, hardware-independent access to this
functionality with `boost::multiprecision::detail::find_msb(x)`.

# Approximate logarithms

According to Definition 1d, `msb(x)` is already a (somewhat crude) approximate base-2 logarithm. The
bits below the most-significant bit provide the fractional part of the linear interpolation. The
fractional part is called the *mantissa* and the integer part is called the *exponent*; effectively we
have re-invented the floating-point representation.

Here are some Python functions to convert to/from these approximate logarithms:

```
def to_log(x, wordsize=32, ebits=5):
if x <= 1:
return x
# mantissa_bits, mantissa_mask are independent of x
mantissa_bits = wordsize - ebits
mantissa_mask = (1 << mantissa_bits)-1
msb = x.bit_length() - 1
mantissa_shift = mantissa_bits - msb
y = (msb << mantissa_bits) | ((x << mantissa_shift) & mantissa_mask)
return y
def from_log(y, wordsize=32, ebits=5):
if y <= 1:
return y
# mantissa_bits, leading_1, mantissa_mask are independent of x
mantissa_bits = wordsize - ebits
leading_1 = 1 << mantissa_bits
mantissa_mask = leading_1 - 1
msb = y >> mantissa_bits
mantissa_shift = mantissa_bits - msb
y = (leading_1 | (y & mantissa_mask)) >> mantissa_shift
return y
```

# Approximate square roots

To construct an approximate square root algorithm, start from the identity `log(sqrt(x)) = log(x) / 2`.
We can easily obtain `sqrt(x) ~ from_log(to_log(x) >> 1)`. We can proceed by manual inlining the inner
function call:

```
def approx_sqrt_v0(x, wordsize=32, ebits=5):
if x <= 1:
return x
# mantissa_bits, leading_1, mantissa_mask are independent of x
mantissa_bits = wordsize - ebits
leading_1 = 1 << mantissa_bits
mantissa_mask = leading_1 - 1
msb_x = x.bit_length() - 1
mantissa_shift_x = mantissa_bits - msb_x
to_log_x = (msb_x << mantissa_bits) | ((x << mantissa_shift_x) & mantissa_mask)
z = to_log_x >> 1
msb_z = z >> mantissa_bits
mantissa_shift_z = mantissa_bits - msb_z
result = (leading_1 | (z & mantissa_mask)) >> mantissa_shift_z
return result
```

# Optimized approximate square roots

First, consider the following simplifications:

- The exponent part of `z`, denoted here `msb_z`, is simply `msb_x >> 1`
- The MSB of the mantissa part of `z` is the low bit of `msb_x`
- The lower bits of the mantissa part of `z` are simply the bits of the mantissa part of `x` shifted once

The above simplifications enable a more fundamental improvement: We can compute
the mantissa and exponent of `z` directly from `x` and `msb_x`. Therefore, packing
the intermediate result into `to_log_x` and then immediately unpacking it, effectively
becomes a no-op and can be omitted. This makes the `wordsize` and `ebits` variables fall
out. Making choices for these parameters and allocating extra space at the top of the word
for exponent bits becomes completely unnecessary!

One subtlety is that the two shift operators result in a net shift of mantissa bits. The
are shifted left by `mantissa_bits - msb_x` and then shifted right by `mantissa_bits - msb_z`. The
net shift is therefore a right-shift of `msb_x - msb_z`.

The final code looks like this:

```
def approx_sqrt_v1(x):
if x <= 1:
return x
# mantissa_bits, leading_1, mantissa_mask are independent of x
msb_x = x.bit_length() - 1
msb_z = msb_x >> 1
msb_x_bit = 1 << msb_x
msb_z_bit = 1 << msb_z
mantissa_mask = msb_x_bit-1
mantissa_x = x & mantissa_mask
if (msb_x & 1) != 0:
mantissa_z_hi = msb_z_bit
else:
mantissa_z_hi = 0
mantissa_z_lo = mantissa_x >> (msb_x - msb_z)
mantissa_z = (mantissa_z_hi | mantissa_z_lo) >> 1
result = msb_z_bit | mantissa_z
return result
```
7 changes: 5 additions & 2 deletions libraries/app/database_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1140,9 +1140,12 @@ void database_api::set_pending_payout( discussion& d )const
{
uint128_t vshares;
if( my->_db.has_hardfork( STEEMIT_HARDFORK_0_17__774 ) )
vshares = steemit::chain::util::calculate_claims( d.net_rshares.value > 0 ? d.net_rshares.value : 0 , my->_db.get_reward_fund( my->_db.get_comment( d.author, d.permlink ) ) );
{
const auto& rf = my->_db.get_reward_fund( my->_db.get_comment( d.author, d.permlink ) );
vshares = d.net_rshares.value > 0 ? steemit::chain::util::evaluate_reward_curve( d.net_rshares.value, rf.author_reward_curve, rf.content_constant ) : 0;
}
else
vshares = steemit::chain::util::calculate_claims( d.net_rshares.value > 0 ? d.net_rshares.value : 0 );
vshares = steemit::chain::util::evaluate_reward_curve( d.net_rshares.value > 0 ? d.net_rshares.value : 0 );

u256 r2 = to256(vshares); //to256(abs_net_rshares);
r2 *= pot.amount.value;
Expand Down
111 changes: 103 additions & 8 deletions libraries/chain/database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1482,8 +1482,14 @@ share_type database::cashout_comment_helper( util::comment_reward_context& ctx,
{
fill_comment_reward_context_local_state( ctx, comment );

const share_type reward = has_hardfork( STEEMIT_HARDFORK_0_17__774 ) ?
util::get_rshare_reward( ctx, get_reward_fund( comment ) ) : util::get_rshare_reward( ctx );
if( has_hardfork( STEEMIT_HARDFORK_0_17__774 ) )
{
const auto rf = get_reward_fund( comment );
ctx.reward_curve = rf.author_reward_curve;
ctx.content_constant = rf.content_constant;
}

const share_type reward = util::get_rshare_reward( ctx );
uint128_t reward_tokens = uint128_t( reward.value );

if( reward_tokens > 0 )
Expand Down Expand Up @@ -1532,7 +1538,7 @@ share_type database::cashout_comment_helper( util::comment_reward_context& ctx,
}

if( !has_hardfork( STEEMIT_HARDFORK_0_17__774 ) )
adjust_rshares2( comment, util::calculate_claims( comment.net_rshares.value ), 0 );
adjust_rshares2( comment, util::evaluate_reward_curve( comment.net_rshares.value ), 0 );
}

modify( comment, [&]( comment_object& c )
Expand Down Expand Up @@ -1613,7 +1619,15 @@ void database::process_comment_cashout()
// Add all reward funds to the local cache and decay their recent rshares
modify( *itr, [&]( reward_fund_object& rfo )
{
rfo.recent_claims -= ( rfo.recent_claims * ( head_block_time() - rfo.last_update ).to_seconds() ) / STEEMIT_RECENT_RSHARES_DECAY_RATE.to_seconds();
fc::microseconds decay_rate;

// TODO: Remove temp fund after HF 19
if( rfo.name == STEEMIT_TEMP_LINEAR_REWARD_FUND_NAME || has_hardfork( STEEMIT_HARDFORK_0_19__1051 ) )
decay_rate = STEEMIT_RECENT_RSHARES_DECAY_RATE_HF19;
else
decay_rate = STEEMIT_RECENT_RSHARES_DECAY_RATE_HF17;

rfo.recent_claims -= ( rfo.recent_claims * ( head_block_time() - rfo.last_update ).to_seconds() ) / decay_rate.to_seconds();
rfo.last_update = head_block_time();
});

Expand All @@ -1634,12 +1648,15 @@ void database::process_comment_cashout()
// add all rshares about to be cashed out to the reward funds. This ensures equal satoshi per rshare payment
if( has_hardfork( STEEMIT_HARDFORK_0_17__771 ) )
{
const auto& linear = get< reward_fund_object, by_name >( STEEMIT_TEMP_LINEAR_REWARD_FUND_NAME );

while( current != cidx.end() && current->cashout_time <= head_block_time() )
{
if( current->net_rshares > 0 )
{
const auto& rf = get_reward_fund( *current );
funds[ rf.id._id ].recent_claims += util::calculate_claims( current->net_rshares.value, rf );
funds[ rf.id._id ].recent_claims += util::evaluate_reward_curve( current->net_rshares.value, rf.author_reward_curve, rf.content_constant );
funds[ STEEMIT_TEMP_LINEAR_REWARD_FUND_ID ].recent_claims += util::evaluate_reward_curve( current->net_rshares.value, linear.author_reward_curve, linear.content_constant );
}

++current;
Expand Down Expand Up @@ -2678,7 +2695,15 @@ try {
for( int i = 0; i < wso.num_scheduled_witnesses; i++ )
{
const auto& wit = get_witness( wso.current_shuffled_witnesses[i] );
if( wit.last_sbd_exchange_update < now + STEEMIT_MAX_FEED_AGE &&
if( has_hardfork( STEEMIT_HARDFORK_0_19__822 ) )
{
if( now < wit.last_sbd_exchange_update + STEEMIT_MAX_FEED_AGE_SECONDS
&& !wit.sbd_exchange_rate.is_null() )
{
feeds.push_back( wit.sbd_exchange_rate );
}
}
else if( wit.last_sbd_exchange_update < now + STEEMIT_MAX_FEED_AGE_SECONDS &&
!wit.sbd_exchange_rate.is_null() )
{
feeds.push_back( wit.sbd_exchange_rate );
Expand Down Expand Up @@ -3519,6 +3544,9 @@ void database::init_hardforks()
FC_ASSERT( STEEMIT_HARDFORK_0_18 == 18, "Invalid hardfork configuration" );
_hardfork_times[ STEEMIT_HARDFORK_0_18 ] = fc::time_point_sec( STEEMIT_HARDFORK_0_18_TIME );
_hardfork_versions[ STEEMIT_HARDFORK_0_18 ] = STEEMIT_HARDFORK_0_18_VERSION;
FC_ASSERT( STEEMIT_HARDFORK_0_19 == 19, "Invalid hardfork configuration" );
_hardfork_times[ STEEMIT_HARDFORK_0_19 ] = fc::time_point_sec( STEEMIT_HARDFORK_0_19_TIME );
_hardfork_versions[ STEEMIT_HARDFORK_0_19 ] = STEEMIT_HARDFORK_0_19_VERSION;


const auto& hardforks = get_hardfork_property_object();
Expand Down Expand Up @@ -3745,11 +3773,27 @@ void database::apply_hardfork( uint32_t hardfork )
#ifndef IS_TEST_NET
rfo.recent_claims = STEEMIT_HF_17_RECENT_CLAIMS;
#endif
rfo.author_reward_curve = curve_id::quadratic;
rfo.curation_reward_curve = curve_id::quadratic_curation;
});

auto linear_rf = create< reward_fund_object >( [&]( reward_fund_object& rfo )
{
rfo.name = STEEMIT_TEMP_LINEAR_REWARD_FUND_NAME;
rfo.last_update = head_block_time();
rfo.content_constant = 0;
rfo.percent_curation_rewards = STEEMIT_1_PERCENT * 25;
rfo.percent_content_rewards = 0;
rfo.reward_balance = asset( 0, STEEM_SYMBOL );
rfo.recent_claims = 0;
rfo.author_reward_curve = curve_id::linear;
rfo.curation_reward_curve = curve_id::square_root;
});

// As a shortcut in payout processing, we use the id as an array index.
// The IDs must be assigned this way. The assertion is a dummy check to ensure this happens.
FC_ASSERT( post_rf.id._id == 0 );
FC_ASSERT( linear_rf.id._id == 1 );

modify( gpo, [&]( dynamic_global_property_object& g )
{
Expand Down Expand Up @@ -3805,6 +3849,57 @@ void database::apply_hardfork( uint32_t hardfork )
break;
case STEEMIT_HARDFORK_0_18:
break;
case STEEMIT_HARDFORK_0_19:
{
modify( get_dynamic_global_properties(), [&]( dynamic_global_property_object& gpo )
{
gpo.vote_power_reserve_rate = 10;
});

const auto& linear = get< reward_fund_object, by_name >( STEEMIT_TEMP_LINEAR_REWARD_FUND_NAME );
modify( get< reward_fund_object, by_name >( STEEMIT_POST_REWARD_FUND_NAME ), [&]( reward_fund_object &rfo )
{
rfo.recent_claims = linear.recent_claims;
rfo.author_reward_curve = curve_id::linear;
rfo.curation_reward_curve = curve_id::square_root;
});

const auto& cidx = get_index< comment_index, by_cashout_time >();
const auto& vidx = get_index< comment_vote_index, by_comment_voter >();
for( auto c_itr = cidx.begin(); c_itr != cidx.end() && c_itr->cashout_time != fc::time_point_sec::maximum(); ++c_itr )
{
modify( *c_itr, [&]( comment_object& c )
{
c.total_vote_weight = c.total_sqrt_vote_weight;
});

for( auto v_itr = vidx.lower_bound( c_itr->id ); v_itr != vidx.end() && v_itr->comment == c_itr->id; ++v_itr )
{
modify( *v_itr, [&]( comment_vote_object& v )
{
v.weight = v.sqrt_weight;
});
}
}

vector< const vesting_delegation_object* > to_remove;
const auto& delegation_idx = get_index< vesting_delegation_index, by_id >();
auto delegation_itr = delegation_idx.begin();

while( delegation_itr != delegation_idx.end() )
{
if( delegation_itr->vesting_shares.amount == 0 )
to_remove.push_back( &(*delegation_itr) );

++delegation_itr;
}

for( const vesting_delegation_object* delegation_ptr: to_remove )
{
remove( *delegation_ptr );
}
}
break;
default:
break;
}
Expand Down Expand Up @@ -3930,7 +4025,7 @@ void database::validate_invariants()const
{
if( itr->net_rshares.value > 0 )
{
auto delta = util::calculate_claims( itr->net_rshares.value );
auto delta = util::evaluate_reward_curve( itr->net_rshares.value );
total_rshares2 += delta;
}
}
Expand Down Expand Up @@ -4001,7 +4096,7 @@ void database::perform_vesting_share_split( uint32_t magnitude )
for( const auto& c : comments )
{
if( c.net_rshares.value > 0 )
adjust_rshares2( c, 0, util::calculate_claims( c.net_rshares.value ) );
adjust_rshares2( c, 0, util::evaluate_reward_curve( c.net_rshares.value ) );
}

}
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/hardfork.d/0-preamble.hf
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ FC_REFLECT( steemit::chain::hardfork_property_object,
(next_hardfork)(next_hardfork_time) )
CHAINBASE_SET_INDEX_TYPE( steemit::chain::hardfork_property_object, steemit::chain::hardfork_property_index )

#define STEEMIT_NUM_HARDFORKS 18
#define STEEMIT_NUM_HARDFORKS 19
18 changes: 18 additions & 0 deletions libraries/chain/hardfork.d/0_19.hf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef STEEMIT_HARDFORK_0_19
#define STEEMIT_HARDFORK_0_19 19

#define STEEMIT_HARDFORK_0_19_TIME 1590886000 // TBD

#define STEEMIT_HARDFORK_0_19__822 (STEEMIT_HARDFORK_0_19)
#define STEEMIT_HARDFORK_0_19__876 (STEEMIT_HARDFORK_0_19)
#define STEEMIT_HARDFORK_0_19__971 (STEEMIT_HARDFORK_0_19)
#define STEEMIT_HARDFORK_0_19__977 (STEEMIT_HARDFORK_0_19)
#define STEEMIT_HARDFORK_0_19__987 (STEEMIT_HARDFORK_0_19)
#define STEEMIT_HARDFORK_0_19__997 (STEEMIT_HARDFORK_0_19)
#define STEEMIT_HARDFORK_0_19__1051 (STEEMIT_HARDFORK_0_19)
#define STEEMIT_HARDFORK_0_19__1052 (STEEMIT_HARDFORK_0_19)
#define STEEMIT_HARDFORK_0_19__1053 (STEEMIT_HARDFORK_0_19)

#define STEEMIT_HARDFORK_0_19_VERSION hardfork_version( 0, 19 )

#endif
Loading

0 comments on commit a3f8e73

Please sign in to comment.